diff --git a/examples/with-nextjs-and-clerk-auth/src/themes/monite.ts b/examples/with-nextjs-and-clerk-auth/src/themes/monite.ts index d5af24490..184855453 100644 --- a/examples/with-nextjs-and-clerk-auth/src/themes/monite.ts +++ b/examples/with-nextjs-and-clerk-auth/src/themes/monite.ts @@ -806,11 +806,6 @@ const defaultMoniteComponents: Components> = { }, }, }, - MoniteTablePagination: { - defaultProps: { - pageSizeOptions: [15, 30, 100], - }, - }, MuiDataGrid: { defaultProps: { columnHeaderHeight: 55, @@ -1052,21 +1047,6 @@ const defaultMoniteComponents: Components> = { }, }, }, - MonitePayableTable: { - defaultProps: { - isShowingSummaryCards: true, - fieldOrder: [ - 'document_id', - 'counterpart_id', - 'created_at', - 'issued_at', - 'due_date', - 'status', - 'amount', - 'pay', - ], - }, - }, MuiFormHelperText: { styleOverrides: { root: { diff --git a/packages/sdk-drop-in/src/apps/MoniteIframeApp.tsx b/packages/sdk-drop-in/src/apps/MoniteIframeApp.tsx index ec8031760..8235c08d1 100644 --- a/packages/sdk-drop-in/src/apps/MoniteIframeApp.tsx +++ b/packages/sdk-drop-in/src/apps/MoniteIframeApp.tsx @@ -7,15 +7,12 @@ import { moniteIframeAppComponents } from '@/lib/moniteIframeAppComponents'; import { useMoniteIframeAppSlots } from '@/lib/useIframeAppSlots'; import { css, Global } from '@emotion/react'; import { type APISchema } from '@monite/sdk-react'; -import { createTheme, CssBaseline, ThemeProvider } from '@mui/material'; +import { CssBaseline } from '@mui/material'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { EntityIdLoader, SDKDemoAPIProvider } from '@team-monite/sdk-demo'; import { DropInMoniteProvider } from '../lib/DropInMoniteProvider'; -// todo::implement google fonts support -// import { getFontFaceStyles } from './fontStyles.ts'; - export const MoniteIframeApp = () => { const queryClient = useMemo(() => new QueryClient(), []); @@ -76,7 +73,7 @@ const MoniteIframeAppComponent = ({ fetchToken, }} > - + <> - + ); }; diff --git a/packages/sdk-react/mui-styles.d.ts b/packages/sdk-react/mui-styles.d.ts index 0ae4b6e56..665730d35 100644 --- a/packages/sdk-react/mui-styles.d.ts +++ b/packages/sdk-react/mui-styles.d.ts @@ -7,10 +7,7 @@ import { type MoniteInvoiceStatusChipProps, type MonitePayableDetailsInfoProps, type MonitePayableStatusChipProps, - type MonitePayableTableProps, - type MoniteTablePaginationProps, } from '@/core/theme/types'; -import { type MoniteIconWrapperProps } from '@/ui/iconWrapper/IconWrapper'; import { ComponentsOverrides, ComponentsPropsList, @@ -39,14 +36,11 @@ declare module '@mui/material/styles' { MoniteInvoiceStatusChip: 'root'; MonitePayableStatusChip: 'root'; MoniteApprovalRequestStatusChip: 'root'; - MoniteTablePagination: 'root' | 'menu'; MonitePayableDetailsInfo: 'never'; MoniteInvoiceRecurrenceStatusChip: 'root'; MoniteInvoiceRecurrenceIterationStatusChip: 'root'; MoniteCounterpartStatusChip: 'root'; MoniteApprovalStatusChip: 'root'; - MonitePayableTable: 'never'; - MoniteReceivablesTable: 'never'; } /** @@ -56,15 +50,11 @@ declare module '@mui/material/styles' { MoniteInvoiceStatusChip: Partial; MonitePayableStatusChip: Partial; MoniteApprovalRequestStatusChip: Partial; - MoniteTablePagination: Partial; MonitePayableDetailsInfo: Partial; MoniteInvoiceRecurrenceStatusChip: Partial; MoniteInvoiceRecurrenceIterationStatusChip: Partial; MoniteCounterpartStatusChip: Partial; - MonitePayableTable: Partial; MoniteApprovalStatusChip: Partial; - MoniteReceivablesTable: Partial; - MoniteIconWrapper: Partial; } /** @@ -74,14 +64,10 @@ declare module '@mui/material/styles' { MoniteInvoiceStatusChip?: ComponentType<'MoniteInvoiceStatusChip'>; MonitePayableStatusChip?: ComponentType<'MonitePayableStatusChip'>; MoniteApprovalRequestStatusChip?: ComponentType<'MoniteApprovalRequestStatusChip'>; - MoniteTablePagination?: ComponentType<'MoniteTablePagination'>; MonitePayableDetailsInfo?: ComponentType<'MonitePayableDetailsInfo'>; - MonitePayableTable?: ComponentType<'MonitePayableTable'>; MoniteInvoiceRecurrenceStatusChip?: ComponentType<'MoniteInvoiceRecurrenceStatusChip'>; MoniteInvoiceRecurrenceIterationStatusChip?: ComponentType<'MoniteInvoiceRecurrenceIterationStatusChip'>; MoniteCounterpartStatusChip?: ComponentType<'MoniteCounterpartStatusChip'>; MoniteApprovalStatusChip?: ComponentType<'MoniteApprovalStatusChip'>; - MoniteReceivablesTable?: ComponentType<'MoniteReceivablesTable'>; - MoniteIconWrapper?: ComponentType<'MoniteIconWrapper'>; } } diff --git a/packages/sdk-react/src/components/approvalPolicies/ApprovalPoliciesTable/ApprovalPoliciesTable.tsx b/packages/sdk-react/src/components/approvalPolicies/ApprovalPoliciesTable/ApprovalPoliciesTable.tsx index d1d89f81d..4cf7ed2f2 100644 --- a/packages/sdk-react/src/components/approvalPolicies/ApprovalPoliciesTable/ApprovalPoliciesTable.tsx +++ b/packages/sdk-react/src/components/approvalPolicies/ApprovalPoliciesTable/ApprovalPoliciesTable.tsx @@ -9,10 +9,7 @@ import { useMoniteContext } from '@/core/context/MoniteContext'; import { MoniteScopedProviders } from '@/core/context/MoniteScopedProviders'; import { DataGridEmptyState } from '@/ui/DataGridEmptyState'; import { GetNoRowsOverlay } from '@/ui/DataGridEmptyState/GetNoRowsOverlay'; -import { - TablePagination, - useTablePaginationThemeDefaultPageSize, -} from '@/ui/table/TablePagination'; +import { TablePagination } from '@/ui/table/TablePagination'; import { hasSelectedText } from '@/utils/text-selection'; import { t } from '@lingui/macro'; import { useLingui } from '@lingui/react'; @@ -98,14 +95,14 @@ const ApprovalPoliciesTableBase = ({ onCreateClick, }: ApprovalPoliciesTableProps) => { const { i18n } = useLingui(); + const { api, locale, componentSettings } = useMoniteContext(); const [currentPaginationToken, setCurrentPaginationToken] = useState< string | null >(null); const [pageSize, setPageSize] = useState( - useTablePaginationThemeDefaultPageSize() + componentSettings.approvalPolicies.pageSizeOptions[0] ); const [currentFilters, setCurrentFilters] = useState({}); - const { api, locale } = useMoniteContext(); const { data: approvalPolicies, @@ -277,6 +274,9 @@ const ApprovalPoliciesTableBase = ({ slots={{ pagination: () => ( { - const { api, locale } = useMoniteContext(); + const { api, locale, componentSettings } = useMoniteContext(); const { i18n } = useLingui(); const { formatCurrencyToDisplay } = useCurrencies(); const { data: user } = useEntityUserByAuthToken(); @@ -108,7 +105,7 @@ const ApprovalRequestsTableBase = ({ string | null >(null); const [pageSize, setPageSize] = useState( - useTablePaginationThemeDefaultPageSize() + componentSettings.approvalRequests.pageSizeOptions[0] ); const [currentFilter, setCurrentFilter] = useState({}); @@ -319,6 +316,9 @@ const ApprovalRequestsTableBase = ({ slots={{ pagination: () => ( { const { i18n } = useLingui(); + const { componentSettings } = useMoniteContext(); const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); const [selectedCounterpart, setSelectedCounterpart] = useState< CounterpartResponse | undefined @@ -130,7 +129,7 @@ const CounterpartsTableBase = ({ string | null >(null); const [pageSize, setPageSize] = useState( - useTablePaginationThemeDefaultPageSize() + componentSettings.counterparts.pageSizeOptions[0] ); const [currentSort] = useState(null); const [currentFilter, setCurrentFilter] = useState({}); @@ -415,6 +414,7 @@ const CounterpartsTableBase = ({ slots={{ pagination: () => ( { const { i18n } = useLingui(); - const { api, locale, queryClient } = useMoniteContext(); + const { api, locale, queryClient, componentSettings } = useMoniteContext(); const { isShowingSummaryCards, fieldOrder, summaryCardFilters } = usePayableTableThemeProps(inProps); @@ -164,7 +160,7 @@ const PayablesTableBase = ({ string | null >(null); const [pageSize, setPageSize] = useState( - useTablePaginationThemeDefaultPageSize() + componentSettings.payables.pageSizeOptions?.[0] ?? 15 ); const [sortModel, setSortModel] = useState({ field: 'created_at', @@ -480,6 +476,7 @@ const PayablesTableBase = ({ slots={{ pagination: () => ( -): MonitePayableTableProps => - useThemeProps({ - props: inProps, - name: 'MonitePayableTable', - }); +): MonitePayableTableProps => { + const { componentSettings } = useMoniteContext(); + + return { + isShowingSummaryCards: + inProps?.isShowingSummaryCards ?? + componentSettings?.payables?.isShowingSummaryCards, + fieldOrder: inProps?.fieldOrder ?? componentSettings?.payables?.fieldOrder, + summaryCardFilters: + inProps?.summaryCardFilters ?? + componentSettings?.payables?.summaryCardFilters, + }; +}; diff --git a/packages/sdk-react/src/components/payables/PayablesTable/types.ts b/packages/sdk-react/src/components/payables/PayablesTable/types.ts index a8b3dd109..66ed71392 100644 --- a/packages/sdk-react/src/components/payables/PayablesTable/types.ts +++ b/packages/sdk-react/src/components/payables/PayablesTable/types.ts @@ -38,7 +38,7 @@ export type FieldValueTypes = export interface MonitePayableTableProps { isShowingSummaryCards?: boolean; - fieldOrder?: Array; + fieldOrder?: FieldValueTypes[]; summaryCardFilters?: Record; } diff --git a/packages/sdk-react/src/components/products/ProductsTable/ProductsTable.tsx b/packages/sdk-react/src/components/products/ProductsTable/ProductsTable.tsx index 69c55ebb9..603bc8913 100755 --- a/packages/sdk-react/src/components/products/ProductsTable/ProductsTable.tsx +++ b/packages/sdk-react/src/components/products/ProductsTable/ProductsTable.tsx @@ -13,10 +13,7 @@ import { useIsActionAllowed } from '@/core/queries/usePermissions'; import { AccessRestriction } from '@/ui/accessRestriction'; import { GetNoRowsOverlay } from '@/ui/DataGridEmptyState/GetNoRowsOverlay'; import { LoadingPage } from '@/ui/loadingPage'; -import { - TablePagination, - useTablePaginationThemeDefaultPageSize, -} from '@/ui/table/TablePagination'; +import { TablePagination } from '@/ui/table/TablePagination'; import { hasSelectedText } from '@/utils/text-selection'; import { t } from '@lingui/macro'; import { useLingui } from '@lingui/react'; @@ -108,11 +105,12 @@ const ProductsTableBase = ({ openCreateModal, }: ProductTableProps) => { const { i18n } = useLingui(); + const { api, componentSettings } = useMoniteContext(); const [currentPaginationToken, setCurrentPaginationToken] = useState< string | null >(null); const [pageSize, setPageSize] = useState( - useTablePaginationThemeDefaultPageSize() + componentSettings.products.pageSizeOptions[0] ); const [currentFilter, setCurrentFilter] = useState({}); const [sortModel, setSortModel] = useState< @@ -144,8 +142,6 @@ const ProductsTableBase = ({ entityUserId: user?.id, }); - const { api } = useMoniteContext(); - const { data: products, isLoading, @@ -312,6 +308,7 @@ const ProductsTableBase = ({ slots={{ pagination: () => ( { const { i18n } = useLingui(); - const { locale } = useMoniteContext(); + const { locale, componentSettings } = useMoniteContext(); const [paginationToken, setPaginationToken] = useState( undefined ); const [pageSize, setPageSize] = useState( - useTablePaginationThemeDefaultPageSize() + componentSettings.receivables.pageSizeOptions[0] ); const [sortModel, setSortModel] = useState({ @@ -258,6 +255,7 @@ const CreditNotesTableBase = ({ slots={{ pagination: () => ( { const { i18n } = useLingui(); - const { locale } = useMoniteContext(); + const { locale, componentSettings } = useMoniteContext(); const [paginationToken, setPaginationToken] = useState( undefined ); const [pageSize, setPageSize] = useState( - useTablePaginationThemeDefaultPageSize() + componentSettings.receivables.pageSizeOptions[0] ); const [sortModel, setSortModel] = useState({ @@ -250,6 +247,7 @@ const FinancedInvoicesTableBase = ({ slots={{ pagination: () => ( { - const { locale } = useMoniteContext(); + const { locale, componentSettings } = useMoniteContext(); const { i18n } = useLingui(); const [paginationToken, setPaginationToken] = useState( @@ -101,7 +98,7 @@ const InvoicesTableBase = ({ ); const [pageSize, setPageSize] = useState( - useTablePaginationThemeDefaultPageSize() + componentSettings.receivables.pageSizeOptions[0] ); const [sortModel, setSortModel] = useState({ @@ -340,6 +337,7 @@ const InvoicesTableBase = ({ slots={{ pagination: () => ( { const { i18n } = useLingui(); - const { locale } = useMoniteContext(); + const { locale, componentSettings } = useMoniteContext(); const [paginationToken, setPaginationToken] = useState( undefined ); const [pageSize, setPageSize] = useState( - useTablePaginationThemeDefaultPageSize() + componentSettings.receivables.pageSizeOptions[0] ); const [sortModel, setSortModel] = useState({ @@ -272,6 +269,7 @@ const QuotesTableBase = ({ slots={{ pagination: () => ( ( const ReceivablesBase = () => { const { i18n } = useLingui(); + const { componentSettings } = useMoniteContext(); const [invoiceId, setInvoiceId] = useState(''); const [isCreateInvoiceDialogOpen, setIsCreateInvoiceDialogOpen] = useState(false); const [activeTab, setActiveTab] = useState( - ReceivablesTableTabEnum.Invoices + componentSettings.receivables.tab ?? ReceivablesTableTabEnum.Invoices ); const openInvoiceModal = (id: string) => { diff --git a/packages/sdk-react/src/components/receivables/ReceivablesTable/ReceivablesTable.stories.tsx b/packages/sdk-react/src/components/receivables/ReceivablesTable/ReceivablesTable.stories.tsx index 410a6c997..79a82ac9a 100644 --- a/packages/sdk-react/src/components/receivables/ReceivablesTable/ReceivablesTable.stories.tsx +++ b/packages/sdk-react/src/components/receivables/ReceivablesTable/ReceivablesTable.stories.tsx @@ -1,4 +1,4 @@ -import { ExtendThemeProvider } from '@/utils/ExtendThemeProvider'; +import { withGlobalStorybookDecorator } from '@/utils/storybook-utils'; import { action } from '@storybook/addon-actions'; import type { Meta, StoryObj } from '@storybook/react'; @@ -26,68 +26,66 @@ export const WithCustomTabs: Story = { args: { onRowClick: action('onRowClick'), }, - render: (args) => ( -
- ({ + componentSettings: { + receivables: { + tab: 1, + tabs: [ + { + label: 'Draft Invoices', + query: { + type: 'invoice', + sort: 'created_at', + order: 'desc', + status__in: ['draft'], }, + filters: [ + 'document_id__contains', + 'counterpart_id', + 'due_date__lte', + ], }, - }} - > - - -
- ), + { + label: 'Recurring invoices', + query: { + type: 'invoice', + status__in: ['recurring'], + }, + filters: ['document_id__contains', 'counterpart_id'], + }, + { + label: 'Other Invoices', + query: { + type: 'invoice', + sort: 'created_at', + order: 'desc', + status__in: [ + 'issued', + 'overdue', + 'partially_paid', + 'paid', + 'uncollectible', + 'canceled', + ], + }, + }, + { + label: 'Credit notes', + query: { + type: 'credit_note', + }, + }, + ], + }, + }, + })), + render: (args) => { + return ( +
+ +
+ ); + }, }; export default meta; diff --git a/packages/sdk-react/src/components/receivables/ReceivablesTable/ReceivablesTable.tsx b/packages/sdk-react/src/components/receivables/ReceivablesTable/ReceivablesTable.tsx index 2cf31d189..732b4d65e 100644 --- a/packages/sdk-react/src/components/receivables/ReceivablesTable/ReceivablesTable.tsx +++ b/packages/sdk-react/src/components/receivables/ReceivablesTable/ReceivablesTable.tsx @@ -3,20 +3,19 @@ import { useId, useState } from 'react'; import { CreditNotesTable } from '@/components'; import { InvoicesTable } from '@/components'; import { QuotesTable } from '@/components'; +import { FinanceTab } from '@/components'; import { ScopedCssBaselineContainerClassName } from '@/components/ContainerCssBaseline'; import { ReceivableFilterType, ReceivablesTabFilter, } from '@/components/receivables/ReceivablesTable/types'; +import { useMoniteContext } from '@/core/context/MoniteContext'; import { MoniteScopedProviders } from '@/core/context/MoniteScopedProviders'; import { FINANCING_LABEL, useFinancing } from '@/core/queries/useFinancing'; import { classNames } from '@/utils/css-utils'; import { t } from '@lingui/macro'; import { useLingui } from '@lingui/react'; import { Box, Tab, Tabs } from '@mui/material'; -import { useThemeProps } from '@mui/material/styles'; - -import { FinanceTab } from '../Financing/FinanceTab/FinanceTab'; interface ReceivablesTableControlledProps { /** Event handler for tab change */ @@ -33,7 +32,7 @@ interface ReceivablesTableUncontrolledProps { } /** - * Receivables Table props for MUI theming + * Receivables Table props for customisation via Monite Provider */ export interface MoniteReceivablesTableProps { /** Active-selected tab */ @@ -77,15 +76,13 @@ export type ReceivablesTableProps = * ReceivablesTable component * Displays Invoices, Quotes, Credit Notes * - * @example MUI theming + * @example Monite Provider customisation * ```ts - * // You can configure the component through MUI theming like this: - * const theme = createTheme(myTheme, { - * components: { - * MoniteReceivablesTable: { - * defaultProps: { - * tab: 0, // The default tab index to display - * tabs: [ + * // You can configure the component through Monite Provider property `componentSettings` like this: + * const componentSettings = { + * receivables: { + * tab: 0, // The default tab index to display + * tabs: [ * { * label: 'Draft Invoices', // The label of the Tab * query: { // The query parameters for the Tab @@ -131,9 +128,7 @@ export type ReceivablesTableProps = * type: 'credit_note', * }, * }, - * ], - * }, - * }, + * ], * }, * }); * ``` @@ -289,8 +284,10 @@ const ReceivablesTableBase = ({ export const useReceivablesTableProps = ( inProps?: Partial ) => { - return useThemeProps({ - props: inProps, - name: 'MoniteReceivablesTable', - }); + const { componentSettings } = useMoniteContext(); + + return { + tab: inProps?.tab ?? componentSettings?.receivables?.tab, + tabs: inProps?.tabs ?? componentSettings?.receivables?.tabs, + }; }; diff --git a/packages/sdk-react/src/components/tags/TagsTable/TagsTable.tsx b/packages/sdk-react/src/components/tags/TagsTable/TagsTable.tsx index 909cf00a0..e5e327449 100644 --- a/packages/sdk-react/src/components/tags/TagsTable/TagsTable.tsx +++ b/packages/sdk-react/src/components/tags/TagsTable/TagsTable.tsx @@ -11,10 +11,7 @@ import { useIsActionAllowed } from '@/core/queries/usePermissions'; import { getAPIErrorMessage } from '@/core/utils/getAPIErrorMessage'; import { DataGridEmptyState } from '@/ui/DataGridEmptyState'; import { GetNoRowsOverlay } from '@/ui/DataGridEmptyState/GetNoRowsOverlay'; -import { - TablePagination, - useTablePaginationThemeDefaultPageSize, -} from '@/ui/table/TablePagination'; +import { TablePagination } from '@/ui/table/TablePagination'; import { t } from '@lingui/macro'; import { useLingui } from '@lingui/react'; import DeleteIcon from '@mui/icons-material/DeleteForever'; @@ -52,11 +49,12 @@ const TagsTableBase = ({ showCreationModal, }: TagsTableProps) => { const { i18n } = useLingui(); + const { api, locale, componentSettings } = useMoniteContext(); const [currentPaginationToken, setCurrentPaginationToken] = useState< string | null >(null); const [pageSize, setPageSize] = useState( - useTablePaginationThemeDefaultPageSize() + componentSettings.tags.pageSizeOptions[0] ); const [selectedTag, setSelectedTag] = useState< components['schemas']['TagReadSchema'] | undefined @@ -81,7 +79,6 @@ const TagsTableBase = ({ const closeDeleteModal = useCallback(() => { setDeleteModalOpened(false); }, []); - const { api, locale } = useMoniteContext(); const { data: tags, @@ -240,6 +237,7 @@ const TagsTableBase = ({ slots={{ pagination: () => ( { const { i18n } = useLingui(); - const { api, locale } = useMoniteContext(); + const { api, locale, componentSettings } = useMoniteContext(); const [currentPaginationToken, setCurrentPaginationToken] = useState< string | null >(null); const [pageSize, setPageSize] = useState( - useTablePaginationThemeDefaultPageSize() + componentSettings.userRoles.pageSizeOptions[0] ); const [currentFilter, setCurrentFilter] = useState({}); const [sortModel, setSortModel] = useState({ @@ -249,6 +246,7 @@ const UserRolesTableBase = ({ slots={{ pagination: () => ( ; + }; + approvalPolicies: { + pageSizeOptions: number[]; + }; + approvalRequests: { + pageSizeOptions: number[]; + }; + counterparts: { + pageSizeOptions: number[]; + }; + payables: Partial; + products: { + pageSizeOptions: number[]; + }; + receivables: Partial; + tags: { + pageSizeOptions: number[]; + }; + userRoles: { + pageSizeOptions: number[]; + }; +} + +const defaultPageSizeOptions = [15, 30, 100]; + +const defaultPayableFieldOrder = [ + 'document_id', + 'counterpart_id', + 'due_date', + 'amount', + 'was_created_by_user_id', + 'pay', +]; + +export const getDefaultComponentSettings = ( + i18n: I18n, + componentSettings?: Partial +) => ({ + general: { + iconWrapper: { + defaultProps: { + showCloseIcon: true, + }, + ...componentSettings?.general?.iconWrapper, + }, + }, + approvalPolicies: { + pageSizeOptions: + componentSettings?.approvalPolicies?.pageSizeOptions || + defaultPageSizeOptions, + }, + approvalRequests: { + pageSizeOptions: + componentSettings?.approvalRequests?.pageSizeOptions || + defaultPageSizeOptions, + }, + counterparts: { + pageSizeOptions: + componentSettings?.counterparts?.pageSizeOptions || + defaultPageSizeOptions, + }, + payables: { + pageSizeOptions: + componentSettings?.payables?.pageSizeOptions || defaultPageSizeOptions, + isShowingSummaryCards: + componentSettings?.payables?.isShowingSummaryCards ?? true, + fieldOrder: + componentSettings?.payables?.fieldOrder || defaultPayableFieldOrder, + }, + products: { + pageSizeOptions: + componentSettings?.products?.pageSizeOptions || defaultPageSizeOptions, + }, + receivables: { + pageSizeOptions: + componentSettings?.receivables?.pageSizeOptions || defaultPageSizeOptions, + tab: componentSettings?.receivables?.tab || 0, + tabs: componentSettings?.receivables?.tabs || [ + { + label: t(i18n)`Invoices`, + query: { type: 'invoice' }, + }, + { + label: t(i18n)`Quotes`, + query: { type: 'quote' }, + }, + { + label: t(i18n)`Credit notes`, + query: { type: 'credit_note' }, + }, + { + label: FINANCING_LABEL, + }, + ], + }, + tags: { + pageSizeOptions: + componentSettings?.tags?.pageSizeOptions || defaultPageSizeOptions, + }, + userRoles: { + pageSizeOptions: + componentSettings?.userRoles?.pageSizeOptions || defaultPageSizeOptions, + }, +}); diff --git a/packages/sdk-react/src/core/context/MoniteContext.tsx b/packages/sdk-react/src/core/context/MoniteContext.tsx index 1be3e47e3..52bcc2c75 100644 --- a/packages/sdk-react/src/core/context/MoniteContext.tsx +++ b/packages/sdk-react/src/core/context/MoniteContext.tsx @@ -7,6 +7,8 @@ import { } from 'react'; import { createAPIClient, CreateMoniteAPIClientResult } from '@/api/client'; +import { getDefaultComponentSettings } from '@/core/componentSettings'; +import type { ComponentSettings } from '@/core/componentSettings'; import { createQueryClient } from '@/core/context/createQueryClient'; import { MoniteQraftContext } from '@/core/context/MoniteAPIProvider'; import { @@ -42,6 +44,7 @@ export interface MoniteContextValue queryClient: QueryClient; apiUrl: string; theme: Theme; + componentSettings: ComponentSettings; fetchToken: () => Promise<{ access_token: string; expires_in: number; @@ -73,6 +76,7 @@ interface MoniteContextProviderProps { monite: MoniteSettings; locale: Partial | undefined; theme: ThemeConfig | undefined; + componentSettings: Partial | undefined; children: ReactNode; } @@ -106,6 +110,7 @@ interface ContextProviderProps extends MoniteContextBaseValue { monite: MoniteSettings; children: ReactNode; theme: ThemeConfig | undefined; + componentSettings?: Partial; } const ContextProvider = ({ @@ -114,6 +119,7 @@ const ContextProvider = ({ i18n, dateFnsLocale, theme: userTheme, + componentSettings, children, }: ContextProviderProps) => { const { entityId, apiUrl, fetchToken } = monite; @@ -154,10 +160,7 @@ const ContextProvider = ({ [entityId] ); - const theme = useMemo( - () => createThemeWithDefaults(i18n, userTheme), - [i18n, userTheme] - ); + const theme = useMemo(() => createThemeWithDefaults(userTheme), [userTheme]); useEffect(() => { queryClient.mount(); @@ -170,6 +173,7 @@ const ContextProvider = ({ environment, entityId, theme, + componentSettings: getDefaultComponentSettings(i18n, componentSettings), queryClient, sentryHub, i18n, diff --git a/packages/sdk-react/src/core/context/MoniteProvider.tsx b/packages/sdk-react/src/core/context/MoniteProvider.tsx index 75d03eb38..4be642cbf 100644 --- a/packages/sdk-react/src/core/context/MoniteProvider.tsx +++ b/packages/sdk-react/src/core/context/MoniteProvider.tsx @@ -2,6 +2,7 @@ import { ReactNode } from 'react'; import { components } from '@/api'; import { ContainerCssBaseline } from '@/components/ContainerCssBaseline'; +import type { ComponentSettings } from '@/core/componentSettings'; import { EmotionCacheProvider } from '@/core/context/EmotionCacheProvider'; import { MoniteAPIProvider, @@ -37,16 +38,27 @@ export interface MoniteProviderProps { * of all Widgets provided. */ locale?: MoniteLocale; + + /** + * Component settings + */ + componentSettings?: Partial; } export const MoniteProvider = ({ monite, theme, + componentSettings, children, locale, }: MoniteProviderProps) => { return ( - + diff --git a/packages/sdk-react/src/core/theme/mui-monite/index.ts b/packages/sdk-react/src/core/theme/mui-monite/index.ts index 050b76ca4..1383035cb 100644 --- a/packages/sdk-react/src/core/theme/mui-monite/index.ts +++ b/packages/sdk-react/src/core/theme/mui-monite/index.ts @@ -712,11 +712,6 @@ export const getTheme = (theme: ThemeConfig): ThemeOptions => { }, }, }, - MoniteTablePagination: { - defaultProps: { - pageSizeOptions: [15, 30, 100], - }, - }, MuiDataGrid: { defaultProps: { columnHeaderHeight: 55, @@ -988,26 +983,6 @@ export const getTheme = (theme: ThemeConfig): ThemeOptions => { }, }, }, - MoniteIconWrapper: { - defaultProps: { - showCloseIcon: true, - }, - }, - MonitePayableTable: { - defaultProps: { - isShowingSummaryCards: true, - fieldOrder: [ - 'document_id', - 'counterpart_id', - 'created_at', - 'issued_at', - 'due_date', - 'status', - 'amount', - 'pay', - ], - }, - }, MuiFormHelperText: { styleOverrides: { root: { diff --git a/packages/sdk-react/src/core/theme/types.ts b/packages/sdk-react/src/core/theme/types.ts index 766492750..78bedfa04 100644 --- a/packages/sdk-react/src/core/theme/types.ts +++ b/packages/sdk-react/src/core/theme/types.ts @@ -1,5 +1,5 @@ -import { components, Services } from '@/api'; -import { ChipProps, SelectProps } from '@mui/material'; +import { components } from '@/api'; +import { ChipProps } from '@mui/material'; type TypographyStyle = { fontSize?: string | number; @@ -93,23 +93,6 @@ export type MonitePalette = { divider: string; }; -interface MoniteTablePaginationRootSlotProps { - pageSizeOptions?: number[]; -} - -interface MoniteTablePaginationSlotProps { - slotProps?: { - pageSizeSelect?: Omit< - SelectProps, - 'value' | 'defaultValue' | 'aria-label' | 'ref' | 'components' - >; - }; -} - -export interface MoniteTablePaginationProps - extends MoniteTablePaginationSlotProps, - MoniteTablePaginationRootSlotProps {} - interface BaseChipProps { /** The variant of the Chip. */ variant?: ChipProps['variant']; @@ -162,27 +145,6 @@ export interface MoniteApprovalStatusChipProps extends BaseStatusChipProps { status: components['schemas']['ApprovalPolicyStatus']; } -export type PayablesTabFilter = NonNullable< - Services['payables']['getPayables']['types']['parameters']['query'] ->; - -//TODO: better to map it with schema.json keyof values -export type FieldValueTypes = - | 'document_id' - | 'counterpart_id' - | 'created_at' - | 'issued_at' - | 'due_date' - | 'status' - | 'amount' - | 'pay'; - -export interface MonitePayableTableProps { - isShowingSummaryCards?: boolean; - fieldOrder?: FieldValueTypes[]; - summaryCardFilters?: Record; -} - export type OptionalFields = { invoiceDate?: boolean; tags?: boolean; diff --git a/packages/sdk-react/src/core/utils/createThemeWithDefaults.ts b/packages/sdk-react/src/core/utils/createThemeWithDefaults.ts index a26a2b9a8..381177d5f 100644 --- a/packages/sdk-react/src/core/utils/createThemeWithDefaults.ts +++ b/packages/sdk-react/src/core/utils/createThemeWithDefaults.ts @@ -1,8 +1,6 @@ import { ScopedCssBaselineContainerClassName } from '@/components/ContainerCssBaseline'; import { getTheme } from '@/core/theme/mui-monite'; import { ThemeConfig } from '@/core/theme/types'; -import type { I18n } from '@lingui/core'; -import { t } from '@lingui/macro'; import { createTheme, type Theme, @@ -10,43 +8,16 @@ import { type ThemeOptions, } from '@mui/material'; -import { FINANCING_LABEL } from '../queries/useFinancing'; - /** * Create a theme with the default component's `defaultProps` */ -export const createThemeWithDefaults = ( - i18n: I18n, - theme: ThemeConfig = {} -) => { +export const createThemeWithDefaults = (theme: ThemeConfig = {}) => { const themeOptions = getTheme(theme); return createTheme( themeOptions, { - components: { - MoniteReceivablesTable: { - defaultProps: { - tabs: [ - { - label: t(i18n)`Invoices`, - query: { type: 'invoice' }, - }, - { - label: t(i18n)`Quotes`, - query: { type: 'quote' }, - }, - { - label: t(i18n)`Credit notes`, - query: { type: 'credit_note' }, - }, - { - label: FINANCING_LABEL, - }, - ], - }, - }, - }, + components: {}, } satisfies ThemeOptions, { components: { diff --git a/packages/sdk-react/src/ui/iconWrapper/IconWrapper.tsx b/packages/sdk-react/src/ui/iconWrapper/IconWrapper.tsx index 9ff586b34..34c931502 100644 --- a/packages/sdk-react/src/ui/iconWrapper/IconWrapper.tsx +++ b/packages/sdk-react/src/ui/iconWrapper/IconWrapper.tsx @@ -7,9 +7,10 @@ import React, { FocusEvent, } from 'react'; +import { useMoniteContext } from '@/core/context/MoniteContext'; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import CloseIcon from '@mui/icons-material/Close'; -import { SxProps, useThemeProps } from '@mui/material'; +import { SxProps } from '@mui/material'; import IconButton, { IconButtonProps } from '@mui/material/IconButton'; import { Theme } from '@mui/material/styles'; import Tooltip from '@mui/material/Tooltip'; @@ -94,20 +95,18 @@ export const IconWrapper = forwardRef< }, ref ) => { - const themeProps = useThemeProps({ - props: { icon, fallbackIcon }, - name: 'MoniteIconWrapper', - }); + const { componentSettings } = useMoniteContext(); + const providerProps = componentSettings?.general?.iconWrapper; const [displayIcon, setDisplayIcon] = useState( - themeProps.icon || themeProps.fallbackIcon || + providerProps.icon || providerProps.fallbackIcon || ); useEffect(() => { setDisplayIcon( - themeProps.icon || themeProps.fallbackIcon || + providerProps.icon || providerProps.fallbackIcon || ); - }, [themeProps.icon, themeProps.fallbackIcon]); + }, [providerProps.icon, providerProps.fallbackIcon]); const handleMouseEnter = (event: MouseEvent) => { onHover?.(event); @@ -118,7 +117,7 @@ export const IconWrapper = forwardRef< const handleMouseLeave = () => { setDisplayIcon( - themeProps.icon || themeProps.fallbackIcon || + providerProps.icon || providerProps.fallbackIcon || ); }; diff --git a/packages/sdk-react/src/ui/table/TablePagination.stories.tsx b/packages/sdk-react/src/ui/table/TablePagination.stories.tsx index 33b98800a..fefb14bfc 100644 --- a/packages/sdk-react/src/ui/table/TablePagination.stories.tsx +++ b/packages/sdk-react/src/ui/table/TablePagination.stories.tsx @@ -1,5 +1,3 @@ -import { ExtendThemeProvider } from '@/utils/ExtendThemeProvider'; -import { Alert } from '@mui/material'; import type { Meta, StoryObj } from '@storybook/react'; import { TablePagination as TablePaginationComponent } from './TablePagination'; @@ -27,17 +25,14 @@ export const TablePaginationDefault: Story = {
- - {''} could be customized through MUI - theming - ), }; -export const TablePaginationThemed: Story = { - name: 'slots customization', +export const TablePaginationCustomized: Story = { + name: 'props customization', args: { + pageSizeOptions: [55, 10, 155, 200], paginationModel: { pageSize: 10, page: 1, @@ -48,30 +43,9 @@ export const TablePaginationThemed: Story = { }, render: (args) => (
- -
- -
-
- - {''} could be customized through MUI - theming - +
+ +
), }; diff --git a/packages/sdk-react/src/ui/table/TablePagination.test.tsx b/packages/sdk-react/src/ui/table/TablePagination.test.tsx index 3bd33a5e1..adbdba6fd 100644 --- a/packages/sdk-react/src/ui/table/TablePagination.test.tsx +++ b/packages/sdk-react/src/ui/table/TablePagination.test.tsx @@ -1,4 +1,3 @@ -import { ExtendThemeProvider } from '@/utils/ExtendThemeProvider'; import { renderWithClient } from '@/utils/test-utils'; import { screen, fireEvent, act, within } from '@testing-library/react'; @@ -91,27 +90,16 @@ describe('TablePagination', () => { it('does not show page size Select if there are less than two options', async () => { renderWithClient( - - {}} - /> - + onPaginationModelChange={() => {}} + /> ); expect( @@ -140,88 +128,4 @@ describe('TablePagination', () => { }) ).not.toBeInTheDocument(); }); - - it('supports custom `pageSizeOptions` via MUI theming', async () => { - renderWithClient( - - {}} - /> - - ); - - fireEvent.mouseDown(screen.getByRole('combobox')); - - const dropdown = screen.getByRole('listbox', { name: '' }); - const { findByRole } = within(dropdown); - - expect( - await findByRole('option', { - name: '111', - }) - ).toBeInTheDocument(); - - expect( - await findByRole('option', { - name: '222', - }) - ).toBeInTheDocument(); - - expect( - await findByRole('option', { - name: '333', - }) - ).toBeInTheDocument(); - }); - - it('supports custom `slotProps` via MUI theming', async () => { - renderWithClient( - - {}} - /> - - ); - - const element = screen.getByRole('combobox'); - - expect(element.closest('.test-class-name')).toBeInTheDocument(); - }); }); diff --git a/packages/sdk-react/src/ui/table/TablePagination.tsx b/packages/sdk-react/src/ui/table/TablePagination.tsx index a0fb4b6a3..6860bad24 100644 --- a/packages/sdk-react/src/ui/table/TablePagination.tsx +++ b/packages/sdk-react/src/ui/table/TablePagination.tsx @@ -16,7 +16,6 @@ import { } from '@mui/material'; import { styled, useThemeProps } from '@mui/material/styles'; -// eslint-disable-next-line lingui/no-unlocalized-strings const componentName = 'MoniteTablePagination' as const; const DEFAULT_PAGE_SIZE = 10 as const; @@ -56,24 +55,7 @@ interface TablePaginationProps extends MoniteTablePaginationProps { * @param paginationModel The current pagination model. It should contain the current page and the page size. * @param nextPage The next page number. If undefined, the next page button will be disabled. * @param prevPage The previous page number. If undefined, the previous page button will be disabled. - * @param pageSizeOptions The page size options. If not provided, will be used from MUI theme or hidden if only one option is available. - * @example MUI theming - * // You can configure the component through MUI theming like this: - * createTheme(myTheme, { - * components: { - * MoniteTablePagination: { - * defaultProps: { - * // The default page size options - * pageSizeOptions: [5, 10, 15, 20], - * slotProps: { - * pageSizeSelect: { - * size: 'small', - * }, - * }, - * }, - * } - * } - * } + * @param pageSizeOptions The page size options. If not provided, will be used from theme or hidden if only one option is available. */ export const TablePagination = ({ onPaginationModelChange, @@ -91,12 +73,10 @@ export const TablePagination = ({ name: componentName, }); - const defaultPageSize = useTablePaginationThemeDefaultPageSize(); - const pageSize = 'pageSize' in paginationModel ? paginationModel.pageSize - : pageSizeOptions?.[0] ?? defaultPageSize; + : pageSizeOptions?.[0] ?? DEFAULT_PAGE_SIZE; const hasPageSizeSelect = pageSizeOptions && pageSizeOptions.length > 1; @@ -197,16 +177,3 @@ const StyledSelect = styled( shouldForwardProp: () => true, } )({}); - -/** - * Returns the default `pageSize` from the Theme. - * If not specified, it will return a fallback value. - */ -export const useTablePaginationThemeDefaultPageSize = () => { - const { pageSizeOptions } = useThemeProps({ - props: {} as MoniteTablePaginationRootSlotProps, - name: componentName, - }); - - return pageSizeOptions?.length ? pageSizeOptions[0] : DEFAULT_PAGE_SIZE; -}; diff --git a/packages/sdk-react/src/utils/storybook-utils.tsx b/packages/sdk-react/src/utils/storybook-utils.tsx index 05397eb45..720151db5 100644 --- a/packages/sdk-react/src/utils/storybook-utils.tsx +++ b/packages/sdk-react/src/utils/storybook-utils.tsx @@ -1,6 +1,7 @@ import { ReactNode, useMemo } from 'react'; import { apiVersion } from '@/api/api-version'; +import { ComponentSettings } from '@/core/componentSettings'; import { useMoniteContext } from '@/core/context/MoniteContext'; import { MoniteProvider, MoniteSettings } from '@/core/context/MoniteProvider'; import { messages as enLocaleMessages } from '@/core/i18n/locales/en/messages'; @@ -9,6 +10,7 @@ import { createThemeWithDefaults } from '@/core/utils/createThemeWithDefaults'; import { entityIds } from '@/mocks/entities'; import { setupI18n } from '@lingui/core'; import { I18nProvider } from '@lingui/react'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import { ThemeProvider } from '@mui/material'; // eslint-disable-next-line import/no-extraneous-dependencies import { deepmerge } from '@mui/utils'; @@ -49,16 +51,23 @@ export function getRandomNumber(min = 0, max = 100) { export const withGlobalStorybookDecorator = ( cb?: () => { - monite: MoniteSettings; + monite?: MoniteSettings; + componentSettings?: Partial; } ): any => { - const { monite } = cb?.() ?? { monite: undefined }; + const { monite, componentSettings } = cb?.() ?? { + monite: undefined, + componentSettings: undefined, + }; return withThemeFromJSXProvider({ Provider: (...args: any[]) => { - const updatedArgs = monite ? { ...args[0], monite } : args[0]; - - return GlobalStorybookDecorator(updatedArgs); + return GlobalStorybookDecorator({ + ...args, + children: args[0].children, + monite, + componentSettings, + }); }, }); }; @@ -76,10 +85,35 @@ const defaultThemeConfig: ThemeConfig = { }, }; +/** + * Default component settings for storybook stories. + * + * These settings are used to configure default functionality of the SDK components in storybook stories. + */ +const defaultComponentSettings: Partial = { + general: { + iconWrapper: { + icon: , + showCloseIcon: true, + }, + }, + payables: { + fieldOrder: [ + 'amount', + 'document_id', + 'counterpart_id', + 'was_created_by_user_id', + 'due_date', + 'pay', + ], + }, +}; + export const GlobalStorybookDecorator = (props: { children: ReactNode; theme?: ThemeConfig; monite?: MoniteSettings; + componentSettings?: Partial; }) => { const apiUrl = 'https://api.sandbox.monite.com/v1'; @@ -116,6 +150,10 @@ export const GlobalStorybookDecorator = (props: { {props.children} @@ -154,10 +192,7 @@ function FallbackProviders({ return (