From 581df3d0a613957d1a6fda0111d8e7f873393549 Mon Sep 17 00:00:00 2001 From: Matthieu Hochlander Date: Mon, 9 Jun 2025 10:11:47 +0200 Subject: [PATCH 01/18] Allow to set default props for DeleteButton. --- .../src/button/DeleteButton.tsx | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/ra-ui-materialui/src/button/DeleteButton.tsx b/packages/ra-ui-materialui/src/button/DeleteButton.tsx index 9d0aad98ae9..917d4383836 100644 --- a/packages/ra-ui-materialui/src/button/DeleteButton.tsx +++ b/packages/ra-ui-materialui/src/button/DeleteButton.tsx @@ -11,6 +11,7 @@ import { useResourceContext, useCanAccess, } from 'ra-core'; +import { useThemeProps } from '@mui/material/styles'; import { ButtonProps } from './Button'; import { DeleteWithUndoButton } from './DeleteWithUndoButton'; @@ -28,7 +29,7 @@ import { DeleteWithConfirmButton } from './DeleteWithConfirmButton'; * @prop {string} variant Material UI variant for the button. Defaults to 'contained'. * @prop {ReactElement} icon Override the icon. Defaults to the Delete icon from Material UI. * - * @param {Props} props + * @param {Props} inProps * * @example Usage in the of an form * @@ -51,8 +52,13 @@ import { DeleteWithConfirmButton } from './DeleteWithConfirmButton'; * }; */ export const DeleteButton = ( - props: DeleteButtonProps + inProps: DeleteButtonProps ) => { + const props = useThemeProps({ + name: PREFIX, + props: inProps, + }); + const { mutationMode, ...rest } = props; const record = useRecordContext(props); const resource = useResourceContext(props); @@ -109,3 +115,17 @@ export interface DeleteButtonProps< resource?: string; successMessage?: string; } + +const PREFIX = 'RaDeleteButton'; + +declare module '@mui/material/styles' { + interface ComponentsPropsList { + [PREFIX]: Partial; + } + + interface Components { + [PREFIX]?: { + defaultProps?: ComponentsPropsList[typeof PREFIX]; + }; + } +} From 3db03301a7d0aa8c91d1e2c9cbe0fb8e08b5df15 Mon Sep 17 00:00:00 2001 From: Matthieu Hochlander Date: Mon, 9 Jun 2025 10:29:00 +0200 Subject: [PATCH 02/18] Improve delete button props type. --- .../src/button/DeleteButton.tsx | 39 +++++++------------ 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/packages/ra-ui-materialui/src/button/DeleteButton.tsx b/packages/ra-ui-materialui/src/button/DeleteButton.tsx index 917d4383836..f959e8f4ac5 100644 --- a/packages/ra-ui-materialui/src/button/DeleteButton.tsx +++ b/packages/ra-ui-materialui/src/button/DeleteButton.tsx @@ -1,21 +1,22 @@ import * as React from 'react'; -import { UseMutationOptions } from '@tanstack/react-query'; import { RaRecord, - MutationMode, - DeleteParams, useRecordContext, useSaveContext, SaveContextValue, - RedirectionSideEffect, useResourceContext, useCanAccess, } from 'ra-core'; import { useThemeProps } from '@mui/material/styles'; -import { ButtonProps } from './Button'; -import { DeleteWithUndoButton } from './DeleteWithUndoButton'; -import { DeleteWithConfirmButton } from './DeleteWithConfirmButton'; +import { + DeleteWithUndoButton, + DeleteWithUndoButtonProps, +} from './DeleteWithUndoButton'; +import { + DeleteWithConfirmButton, + DeleteWithConfirmButtonProps, +} from './DeleteWithConfirmButton'; /** * Button used to delete a single record. Added by default by the of edit and show views. @@ -95,26 +96,14 @@ export const DeleteButton = ( ); }; -export interface DeleteButtonProps< +export type DeleteButtonProps< RecordType extends RaRecord = any, MutationOptionsError = unknown, -> extends ButtonProps, - SaveContextValue { - confirmTitle?: React.ReactNode; - confirmContent?: React.ReactNode; - confirmColor?: 'primary' | 'warning'; - icon?: React.ReactNode; - mutationMode?: MutationMode; - mutationOptions?: UseMutationOptions< - RecordType, - MutationOptionsError, - DeleteParams - >; - record?: RecordType; - redirect?: RedirectionSideEffect; - resource?: string; - successMessage?: string; -} +> = SaveContextValue & + ( + | DeleteWithUndoButtonProps + | DeleteWithConfirmButtonProps + ); const PREFIX = 'RaDeleteButton'; From 522e58906f21df0c7f9883a56915d77216e7fe7a Mon Sep 17 00:00:00 2001 From: Matthieu Hochlander Date: Mon, 9 Jun 2025 11:01:07 +0200 Subject: [PATCH 03/18] Allow to set default props for Create, Edit and Show. --- .../ra-ui-materialui/src/detail/Create.tsx | 10 ++- .../src/detail/CreateView.tsx | 3 +- packages/ra-ui-materialui/src/detail/Edit.tsx | 11 ++- .../ra-ui-materialui/src/detail/EditView.tsx | 3 +- packages/ra-ui-materialui/src/detail/Show.tsx | 71 +++++++++++-------- .../ra-ui-materialui/src/detail/ShowView.tsx | 3 +- 6 files changed, 68 insertions(+), 33 deletions(-) diff --git a/packages/ra-ui-materialui/src/detail/Create.tsx b/packages/ra-ui-materialui/src/detail/Create.tsx index 80517d67e11..34468e938f6 100644 --- a/packages/ra-ui-materialui/src/detail/Create.tsx +++ b/packages/ra-ui-materialui/src/detail/Create.tsx @@ -7,6 +7,7 @@ import { RaRecord, useCheckMinimumRequiredProps, } from 'ra-core'; +import { useThemeProps } from '@mui/material/styles'; import { CreateView, CreateViewProps } from './CreateView'; import { Loading } from '../layout'; @@ -58,8 +59,13 @@ export const Create = < RecordType extends Omit = any, ResultRecordType extends RaRecord = RecordType & { id: Identifier }, >( - props: CreateProps + inProps: CreateProps ): ReactElement => { + const props = useThemeProps({ + props: inProps, + name: PREFIX, + }); + useCheckMinimumRequiredProps('Create', ['children'], props); const { resource, @@ -100,3 +106,5 @@ export interface CreateProps< Omit {} const defaultLoading = ; + +const PREFIX = 'RaCreate'; // Types declared in CreateView. diff --git a/packages/ra-ui-materialui/src/detail/CreateView.tsx b/packages/ra-ui-materialui/src/detail/CreateView.tsx index 9a912e6911d..c8adbe49f15 100644 --- a/packages/ra-ui-materialui/src/detail/CreateView.tsx +++ b/packages/ra-ui-materialui/src/detail/CreateView.tsx @@ -12,6 +12,7 @@ import { useCreateContext } from 'ra-core'; import clsx from 'clsx'; import { Title } from '../layout'; +import { CreateProps } from './Create'; export const CreateView = (inProps: CreateViewProps) => { const props = useThemeProps({ @@ -94,7 +95,7 @@ declare module '@mui/material/styles' { } interface ComponentsPropsList { - RaCreate: Partial; + RaCreate: Partial; } interface Components { diff --git a/packages/ra-ui-materialui/src/detail/Edit.tsx b/packages/ra-ui-materialui/src/detail/Edit.tsx index 4f78cc70fb4..be155172461 100644 --- a/packages/ra-ui-materialui/src/detail/Edit.tsx +++ b/packages/ra-ui-materialui/src/detail/Edit.tsx @@ -5,6 +5,8 @@ import { RaRecord, EditBaseProps, } from 'ra-core'; +import { useThemeProps } from '@mui/material/styles'; + import { EditView, EditViewProps } from './EditView'; import { Loading } from '../layout'; @@ -54,8 +56,13 @@ import { Loading } from '../layout'; * export default App; */ export const Edit = ( - props: EditProps + inProps: EditProps ) => { + const props = useThemeProps({ + props: inProps, + name: PREFIX, + }); + useCheckMinimumRequiredProps('Edit', ['children'], props); const { resource, @@ -91,3 +98,5 @@ export interface EditProps Omit {} const defaultLoading = ; + +const PREFIX = 'RaEdit'; // Types declared in EditView. diff --git a/packages/ra-ui-materialui/src/detail/EditView.tsx b/packages/ra-ui-materialui/src/detail/EditView.tsx index c40ef18c952..67d8b7dd314 100644 --- a/packages/ra-ui-materialui/src/detail/EditView.tsx +++ b/packages/ra-ui-materialui/src/detail/EditView.tsx @@ -14,6 +14,7 @@ import { useEditContext, useResourceDefinition } from 'ra-core'; import { EditActions } from './EditActions'; import { Title } from '../layout'; +import { EditProps } from './Edit'; const defaultActions = ; @@ -106,7 +107,7 @@ declare module '@mui/material/styles' { } interface ComponentsPropsList { - RaEdit: Partial; + RaEdit: Partial; } interface Components { diff --git a/packages/ra-ui-materialui/src/detail/Show.tsx b/packages/ra-ui-materialui/src/detail/Show.tsx index 58df5bb7a05..850bdfe0d2c 100644 --- a/packages/ra-ui-materialui/src/detail/Show.tsx +++ b/packages/ra-ui-materialui/src/detail/Show.tsx @@ -1,6 +1,8 @@ import * as React from 'react'; import { ReactElement } from 'react'; import { ShowBase, RaRecord, ShowBaseProps } from 'ra-core'; +import { useThemeProps } from '@mui/material/styles'; + import { ShowView, ShowViewProps } from './ShowView'; import { Loading } from '../layout'; @@ -43,40 +45,53 @@ import { Loading } from '../layout'; * ); * export default App; * - * @param {ShowProps} props - * @param {ReactElement|false} props.actions An element to display above the page content, or false to disable actions. - * @param {string} props.className A className to apply to the page content. - * @param {ElementType} props.component The component to use as root component (div by default). - * @param {boolean} props.emptyWhileLoading Do not display the page content while loading the initial data. - * @param {string} props.id The id of the resource to display (grabbed from the route params if not defined). - * @param {Object} props.queryClient Options to pass to the react-query useQuery hook. - * @param {string} props.resource The resource to fetch from the data provider (grabbed from the ResourceContext if not defined). - * @param {Object} props.sx Custom style object. - * @param {ElementType|string} props.title The title of the page. Defaults to `#{resource} #${id}`. + * @param {ShowProps} inProps + * @param {ReactElement|false} inProps.actions An element to display above the page content, or false to disable actions. + * @param {string} inProps.className A className to apply to the page content. + * @param {ElementType} inProps.component The component to use as root component (div by default). + * @param {boolean} inProps.emptyWhileLoading Do not display the page content while loading the initial data. + * @param {string} inProps.id The id of the resource to display (grabbed from the route params if not defined). + * @param {Object} inProps.queryClient Options to pass to the react-query useQuery hook. + * @param {string} inProps.resource The resource to fetch from the data provider (grabbed from the ResourceContext if not defined). + * @param {Object} inProps.sx Custom style object. + * @param {ElementType|string} inProps.title The title of the page. Defaults to `#{resource} #${id}`. * * @see ShowView for the actual rendering */ -export const Show = ({ - id, - resource, - queryOptions, - disableAuthentication, - loading = defaultLoading, - ...rest -}: ShowProps): ReactElement => ( - - id={id} - disableAuthentication={disableAuthentication} - queryOptions={queryOptions} - resource={resource} - loading={loading} - > - - -); +export const Show = ( + inProps: ShowProps +): ReactElement => { + const props = useThemeProps({ + props: inProps, + name: PREFIX, + }); + + const { + id, + resource, + queryOptions, + disableAuthentication, + loading = defaultLoading, + ...rest + } = props; + + return ( + + id={id} + disableAuthentication={disableAuthentication} + queryOptions={queryOptions} + resource={resource} + loading={loading} + > + + + ); +}; export interface ShowProps extends ShowBaseProps, Omit {} const defaultLoading = ; + +const PREFIX = 'RaShow'; // Types declared in ShowView. diff --git a/packages/ra-ui-materialui/src/detail/ShowView.tsx b/packages/ra-ui-materialui/src/detail/ShowView.tsx index 5e8d02d35a6..4fb840a8019 100644 --- a/packages/ra-ui-materialui/src/detail/ShowView.tsx +++ b/packages/ra-ui-materialui/src/detail/ShowView.tsx @@ -12,6 +12,7 @@ import clsx from 'clsx'; import { useShowContext, useResourceDefinition } from 'ra-core'; import { ShowActions } from './ShowActions'; import { Title } from '../layout'; +import { ShowProps } from './Show'; const defaultActions = ; @@ -101,7 +102,7 @@ declare module '@mui/material/styles' { } interface ComponentsPropsList { - RaShow: Partial; + RaShow: Partial; } interface Components { From a85c9b6d7c849096d32c09a0db369b9d66208a2a Mon Sep 17 00:00:00 2001 From: Matthieu Hochlander Date: Mon, 9 Jun 2025 11:11:36 +0200 Subject: [PATCH 04/18] Improve delete button props type. --- packages/ra-ui-materialui/src/button/DeleteButton.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/ra-ui-materialui/src/button/DeleteButton.tsx b/packages/ra-ui-materialui/src/button/DeleteButton.tsx index f959e8f4ac5..e1d9b688550 100644 --- a/packages/ra-ui-materialui/src/button/DeleteButton.tsx +++ b/packages/ra-ui-materialui/src/button/DeleteButton.tsx @@ -101,8 +101,13 @@ export type DeleteButtonProps< MutationOptionsError = unknown, > = SaveContextValue & ( - | DeleteWithUndoButtonProps - | DeleteWithConfirmButtonProps + | ({ mutationMode?: 'undoable' } & DeleteWithUndoButtonProps< + RecordType, + MutationOptionsError + >) + | ({ + mutationMode?: 'pessimistic' | 'optimistic'; + } & DeleteWithConfirmButtonProps) ); const PREFIX = 'RaDeleteButton'; From 34810f8084be5ecadc328752416eb439a8d5ab8b Mon Sep 17 00:00:00 2001 From: Matthieu Hochlander Date: Mon, 9 Jun 2025 11:23:30 +0200 Subject: [PATCH 05/18] Allow to set default props for UpdateButton, BulkDeleteButton and BulkUpdateButton. --- .../src/button/BulkDeleteButton.tsx | 28 +++++++++++++++---- .../src/button/BulkUpdateButton.tsx | 27 ++++++++++++++++-- .../src/button/UpdateButton.tsx | 20 ++++++++++++- 3 files changed, 67 insertions(+), 8 deletions(-) diff --git a/packages/ra-ui-materialui/src/button/BulkDeleteButton.tsx b/packages/ra-ui-materialui/src/button/BulkDeleteButton.tsx index 49c437bd840..378ef180d48 100644 --- a/packages/ra-ui-materialui/src/button/BulkDeleteButton.tsx +++ b/packages/ra-ui-materialui/src/button/BulkDeleteButton.tsx @@ -1,4 +1,7 @@ import * as React from 'react'; +import { MutationMode, useCanAccess, useResourceContext } from 'ra-core'; +import { useThemeProps } from '@mui/material/styles'; + import { BulkDeleteWithConfirmButton, BulkDeleteWithConfirmButtonProps, @@ -7,7 +10,6 @@ import { BulkDeleteWithUndoButton, BulkDeleteWithUndoButtonProps, } from './BulkDeleteWithUndoButton'; -import { MutationMode, useCanAccess, useResourceContext } from 'ra-core'; /** * Deletes the selected rows. @@ -32,10 +34,12 @@ import { MutationMode, useCanAccess, useResourceContext } from 'ra-core'; * * ); */ -export const BulkDeleteButton = ({ - mutationMode = 'undoable', - ...props -}: BulkDeleteButtonProps) => { +export const BulkDeleteButton = (inProps: BulkDeleteButtonProps) => { + const { mutationMode = 'undoable', ...props } = useThemeProps({ + name: PREFIX, + props: inProps, + }); + const resource = useResourceContext(props); if (!resource) { throw new Error( @@ -62,3 +66,17 @@ interface Props { export type BulkDeleteButtonProps = Props & (BulkDeleteWithUndoButtonProps | BulkDeleteWithConfirmButtonProps); + +const PREFIX = 'RaBulkDeleteButton'; + +declare module '@mui/material/styles' { + interface ComponentsPropsList { + [PREFIX]: Partial; + } + + interface Components { + [PREFIX]?: { + defaultProps?: ComponentsPropsList[typeof PREFIX]; + }; + } +} diff --git a/packages/ra-ui-materialui/src/button/BulkUpdateButton.tsx b/packages/ra-ui-materialui/src/button/BulkUpdateButton.tsx index 95ac44fd767..904f7ca98e4 100644 --- a/packages/ra-ui-materialui/src/button/BulkUpdateButton.tsx +++ b/packages/ra-ui-materialui/src/button/BulkUpdateButton.tsx @@ -1,4 +1,7 @@ import * as React from 'react'; +import { MutationMode } from 'ra-core'; +import { useThemeProps } from '@mui/material/styles'; + import { BulkUpdateWithConfirmButton, BulkUpdateWithConfirmButtonProps, @@ -7,7 +10,6 @@ import { BulkUpdateWithUndoButton, BulkUpdateWithUndoButtonProps, } from './BulkUpdateWithUndoButton'; -import { MutationMode } from 'ra-core'; /** * Updates the selected rows. @@ -33,7 +35,14 @@ import { MutationMode } from 'ra-core'; * ); */ export const BulkUpdateButton = (props: BulkUpdateButtonProps) => { - const { mutationMode = 'undoable', data = defaultData, ...rest } = props; + const { + mutationMode = 'undoable', + data = defaultData, + ...rest + } = useThemeProps({ + name: PREFIX, + props: props, + }); return mutationMode === 'undoable' ? ( @@ -54,3 +63,17 @@ export type BulkUpdateButtonProps = Props & (BulkUpdateWithUndoButtonProps | BulkUpdateWithConfirmButtonProps); const defaultData = []; + +const PREFIX = 'RaBulkUpdateButton'; + +declare module '@mui/material/styles' { + interface ComponentsPropsList { + [PREFIX]: Partial; + } + + interface Components { + [PREFIX]?: { + defaultProps?: ComponentsPropsList[typeof PREFIX]; + }; + } +} diff --git a/packages/ra-ui-materialui/src/button/UpdateButton.tsx b/packages/ra-ui-materialui/src/button/UpdateButton.tsx index 643aa2cb8d7..19b06477aa4 100644 --- a/packages/ra-ui-materialui/src/button/UpdateButton.tsx +++ b/packages/ra-ui-materialui/src/button/UpdateButton.tsx @@ -7,6 +7,7 @@ import { UpdateWithUndoButton, UpdateWithUndoButtonProps, } from './UpdateWithUndoButton'; +import { useThemeProps } from '@mui/material/styles'; /** * Updates the current record. @@ -30,7 +31,10 @@ import { * ); */ export const UpdateButton = (props: UpdateButtonProps) => { - const { mutationMode = 'undoable', ...rest } = props; + const { mutationMode = 'undoable', ...rest } = useThemeProps({ + name: PREFIX, + props: props, + }); return mutationMode === 'undoable' ? ( @@ -46,3 +50,17 @@ export type UpdateButtonProps = | ({ mutationMode?: 'pessimistic' | 'optimistic'; } & UpdateWithConfirmButtonProps); + +const PREFIX = 'RaUpdateButton'; + +declare module '@mui/material/styles' { + interface ComponentsPropsList { + [PREFIX]: Partial; + } + + interface Components { + [PREFIX]?: { + defaultProps?: ComponentsPropsList[typeof PREFIX]; + }; + } +} From 4c636287e52a397001ffcbf16f826e2e13e3e038 Mon Sep 17 00:00:00 2001 From: Matthieu Hochlander Date: Mon, 9 Jun 2025 11:27:54 +0200 Subject: [PATCH 06/18] Allow to theme ShowButton and set its default props. --- .../src/button/ShowButton.tsx | 42 +++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/packages/ra-ui-materialui/src/button/ShowButton.tsx b/packages/ra-ui-materialui/src/button/ShowButton.tsx index 6c5d596a508..ac42adf8dea 100644 --- a/packages/ra-ui-materialui/src/button/ShowButton.tsx +++ b/packages/ra-ui-materialui/src/button/ShowButton.tsx @@ -9,6 +9,11 @@ import { useCreatePath, useCanAccess, } from 'ra-core'; +import { + ComponentsOverrides, + styled, + useThemeProps, +} from '@mui/material/styles'; import { Button, ButtonProps } from './Button'; @@ -26,8 +31,13 @@ import { Button, ButtonProps } from './Button'; * }; */ const ShowButton = ( - props: ShowButtonProps + inProps: ShowButtonProps ) => { + const props = useThemeProps({ + props: inProps, + name: PREFIX, + }); + const { icon = defaultIcon, label = 'ra.action.show', @@ -51,7 +61,7 @@ const ShowButton = ( }); if (!record || !canAccess || isPending) return null; return ( - + ); }; @@ -98,3 +108,29 @@ const PureShowButton = memo( ); export default PureShowButton; + +const PREFIX = 'RaShowButton'; + +const StyledButton = styled(Button, { + name: PREFIX, + overridesResolver: (props, styles) => styles.root, +})({}); + +declare module '@mui/material/styles' { + interface ComponentNameToClassKey { + [PREFIX]: 'root'; + } + + interface ComponentsPropsList { + [PREFIX]: Partial; + } + + interface Components { + [PREFIX]?: { + defaultProps?: ComponentsPropsList[typeof PREFIX]; + styleOverrides?: ComponentsOverrides< + Omit + >[typeof PREFIX]; + }; + } +} From 4ea1e015e1447727add62dda3cb40d29759adbe9 Mon Sep 17 00:00:00 2001 From: Matthieu Hochlander Date: Mon, 9 Jun 2025 11:32:03 +0200 Subject: [PATCH 07/18] Allow to theme ToggleThemeButton. --- .../src/button/ToggleThemeButton.tsx | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/packages/ra-ui-materialui/src/button/ToggleThemeButton.tsx b/packages/ra-ui-materialui/src/button/ToggleThemeButton.tsx index f0977468f14..e86d12cb77b 100644 --- a/packages/ra-ui-materialui/src/button/ToggleThemeButton.tsx +++ b/packages/ra-ui-materialui/src/button/ToggleThemeButton.tsx @@ -1,5 +1,10 @@ import React from 'react'; import { Tooltip, IconButton, useMediaQuery } from '@mui/material'; +import { + ComponentsOverrides, + styled, + useThemeProps, +} from '@mui/material/styles'; import Brightness4Icon from '@mui/icons-material/Brightness4'; import Brightness7Icon from '@mui/icons-material/Brightness7'; import { useTranslate } from 'ra-core'; @@ -25,6 +30,11 @@ import { useThemesContext, useTheme } from '../theme'; * ); */ export const ToggleThemeButton = () => { + const props = useThemeProps({ + props: {}, + name: PREFIX, + }); + const translate = useTranslate(); const { darkTheme, defaultTheme } = useThemesContext(); const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)', { @@ -43,13 +53,35 @@ export const ToggleThemeButton = () => { return ( - {theme === 'dark' ? : } - + ); }; + +const PREFIX = 'RaToggleThemeButton'; + +const StyledIconButton = styled(IconButton, { + name: PREFIX, + overridesResolver: (props, styles) => styles.root, +})({}); + +declare module '@mui/material/styles' { + interface ComponentNameToClassKey { + [PREFIX]: 'root'; + } + + interface Components { + [PREFIX]?: { + styleOverrides?: ComponentsOverrides< + Omit + >[typeof PREFIX]; + }; + } +} From 4f4c7f748a1d21acc66fef50f64ca69d098e158d Mon Sep 17 00:00:00 2001 From: Matthieu Hochlander Date: Mon, 9 Jun 2025 11:34:27 +0200 Subject: [PATCH 08/18] Allow to set default props for List. --- packages/ra-ui-materialui/src/list/List.tsx | 76 +++++++++++-------- .../ra-ui-materialui/src/list/ListView.tsx | 3 +- 2 files changed, 46 insertions(+), 33 deletions(-) diff --git a/packages/ra-ui-materialui/src/list/List.tsx b/packages/ra-ui-materialui/src/list/List.tsx index e562bf7ad50..503e64272da 100644 --- a/packages/ra-ui-materialui/src/list/List.tsx +++ b/packages/ra-ui-materialui/src/list/List.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { ReactElement } from 'react'; import { ListBase, ListBaseProps, RaRecord } from 'ra-core'; +import { useThemeProps } from '@mui/material/styles'; import { ListView, ListViewProps } from './ListView'; import { Loading } from '../layout'; @@ -55,38 +56,47 @@ import { Loading } from '../layout'; * * ); */ -export const List = ({ - debounce, - disableAuthentication, - disableSyncWithLocation, - exporter, - filter = defaultFilter, - filterDefaultValues, - loading = defaultLoading, - perPage = 10, - queryOptions, - resource, - sort, - storeKey, - ...rest -}: ListProps): ReactElement => ( - - debounce={debounce} - disableAuthentication={disableAuthentication} - disableSyncWithLocation={disableSyncWithLocation} - exporter={exporter} - filter={filter} - filterDefaultValues={filterDefaultValues} - loading={loading} - perPage={perPage} - queryOptions={queryOptions} - resource={resource} - sort={sort} - storeKey={storeKey} - > - {...rest} /> - -); +export const List = ( + props: ListProps +): ReactElement => { + const { + debounce, + disableAuthentication, + disableSyncWithLocation, + exporter, + filter = defaultFilter, + filterDefaultValues, + loading = defaultLoading, + perPage = 10, + queryOptions, + resource, + sort, + storeKey, + ...rest + } = useThemeProps({ + props: props, + name: PREFIX, + }); + + return ( + + debounce={debounce} + disableAuthentication={disableAuthentication} + disableSyncWithLocation={disableSyncWithLocation} + exporter={exporter} + filter={filter} + filterDefaultValues={filterDefaultValues} + loading={loading} + perPage={perPage} + queryOptions={queryOptions} + resource={resource} + sort={sort} + storeKey={storeKey} + > + {...rest} /> + + ); +}; export interface ListProps extends ListBaseProps, @@ -94,3 +104,5 @@ export interface ListProps const defaultFilter = {}; const defaultLoading = ; + +const PREFIX = 'RaList'; // Types declared in ListView. diff --git a/packages/ra-ui-materialui/src/list/ListView.tsx b/packages/ra-ui-materialui/src/list/ListView.tsx index 1e5fc999fef..1838795287d 100644 --- a/packages/ra-ui-materialui/src/list/ListView.tsx +++ b/packages/ra-ui-materialui/src/list/ListView.tsx @@ -16,6 +16,7 @@ import { ListToolbar } from './ListToolbar'; import { Pagination as DefaultPagination } from './pagination'; import { ListActions as DefaultActions } from './ListActions'; import { Empty } from './Empty'; +import { ListProps } from './List'; const defaultActions = ; const defaultPagination = ; @@ -372,7 +373,7 @@ declare module '@mui/material/styles' { } interface ComponentsPropsList { - RaList: Partial; + RaList: Partial; } interface Components { From 288b84e0bc1c132c41cac6faba7b139ed72e2436 Mon Sep 17 00:00:00 2001 From: Matthieu Hochlander Date: Mon, 9 Jun 2025 11:36:49 +0200 Subject: [PATCH 09/18] Allow to theme BulkExportButton and set its default props. --- .../src/button/BulkExportButton.tsx | 42 +++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/packages/ra-ui-materialui/src/button/BulkExportButton.tsx b/packages/ra-ui-materialui/src/button/BulkExportButton.tsx index b1055bbe497..58a353d324a 100644 --- a/packages/ra-ui-materialui/src/button/BulkExportButton.tsx +++ b/packages/ra-ui-materialui/src/button/BulkExportButton.tsx @@ -9,6 +9,11 @@ import { useListContext, useResourceContext, } from 'ra-core'; +import { + ComponentsOverrides, + styled, + useThemeProps, +} from '@mui/material/styles'; import { Button, ButtonProps } from './Button'; @@ -35,7 +40,12 @@ import { Button, ButtonProps } from './Button'; * * ); */ -export const BulkExportButton = (props: BulkExportButtonProps) => { +export const BulkExportButton = (inProps: BulkExportButtonProps) => { + const props = useThemeProps({ + props: inProps, + name: PREFIX, + }); + const { onClick, label = 'ra.action.export', @@ -77,13 +87,13 @@ export const BulkExportButton = (props: BulkExportButtonProps) => { ); return ( - + ); }; @@ -104,3 +114,29 @@ interface Props { } export type BulkExportButtonProps = Props & ButtonProps; + +const PREFIX = 'RaBulkExportButton'; + +const StyledButton = styled(Button, { + name: PREFIX, + overridesResolver: (props, styles) => styles.root, +})({}); + +declare module '@mui/material/styles' { + interface ComponentNameToClassKey { + [PREFIX]: 'root'; + } + + interface ComponentsPropsList { + [PREFIX]: Partial; + } + + interface Components { + [PREFIX]?: { + defaultProps?: ComponentsPropsList[typeof PREFIX]; + styleOverrides?: ComponentsOverrides< + Omit + >[typeof PREFIX]; + }; + } +} From 6f6e223b0046702892306405c7eca8e0b474692e Mon Sep 17 00:00:00 2001 From: Matthieu Hochlander Date: Mon, 9 Jun 2025 11:50:10 +0200 Subject: [PATCH 10/18] Allow to set default props for InfiniteList. --- .../src/list/InfiniteList.tsx | 90 ++++++++++++------- 1 file changed, 57 insertions(+), 33 deletions(-) diff --git a/packages/ra-ui-materialui/src/list/InfiniteList.tsx b/packages/ra-ui-materialui/src/list/InfiniteList.tsx index 97395664ffc..e27ea5d683b 100644 --- a/packages/ra-ui-materialui/src/list/InfiniteList.tsx +++ b/packages/ra-ui-materialui/src/list/InfiniteList.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { ReactElement } from 'react'; import { InfiniteListBase, InfiniteListBaseProps, RaRecord } from 'ra-core'; +import { useThemeProps } from '@mui/material/styles'; import { InfinitePagination } from './pagination'; import { ListView, ListViewProps } from './ListView'; @@ -59,39 +60,48 @@ import { Loading } from '../layout'; * * ); */ -export const InfiniteList = ({ - debounce, - disableAuthentication, - disableSyncWithLocation, - exporter, - filter = defaultFilter, - filterDefaultValues, - loading = defaultLoading, - pagination = defaultPagination, - perPage = 10, - queryOptions, - resource, - sort, - storeKey, - ...rest -}: InfiniteListProps): ReactElement => ( - - debounce={debounce} - disableAuthentication={disableAuthentication} - disableSyncWithLocation={disableSyncWithLocation} - exporter={exporter} - filter={filter} - filterDefaultValues={filterDefaultValues} - loading={loading} - perPage={perPage} - queryOptions={queryOptions} - resource={resource} - sort={sort} - storeKey={storeKey} - > - {...rest} pagination={pagination} /> - -); +export const InfiniteList = ( + props: InfiniteListProps +): ReactElement => { + const { + debounce, + disableAuthentication, + disableSyncWithLocation, + exporter, + filter = defaultFilter, + filterDefaultValues, + loading = defaultLoading, + pagination = defaultPagination, + perPage = 10, + queryOptions, + resource, + sort, + storeKey, + ...rest + } = useThemeProps({ + props: props, + name: PREFIX, + }); + + return ( + + debounce={debounce} + disableAuthentication={disableAuthentication} + disableSyncWithLocation={disableSyncWithLocation} + exporter={exporter} + filter={filter} + filterDefaultValues={filterDefaultValues} + loading={loading} + perPage={perPage} + queryOptions={queryOptions} + resource={resource} + sort={sort} + storeKey={storeKey} + > + {...rest} pagination={pagination} /> + + ); +}; const defaultPagination = ; const defaultFilter = {}; @@ -100,3 +110,17 @@ const defaultLoading = ; export interface InfiniteListProps extends InfiniteListBaseProps, ListViewProps {} + +const PREFIX = 'RaInfiniteList'; + +declare module '@mui/material/styles' { + interface ComponentsPropsList { + [PREFIX]: Partial; + } + + interface Components { + [PREFIX]?: { + defaultProps?: ComponentsPropsList[typeof PREFIX]; + }; + } +} From 19be41b02652f75e34c0688f7de9fee4137f7b3c Mon Sep 17 00:00:00 2001 From: Matthieu Hochlander Date: Mon, 9 Jun 2025 11:58:11 +0200 Subject: [PATCH 11/18] Allow to theme ListButton and set its default props. --- .../src/button/ListButton.tsx | 44 +++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/packages/ra-ui-materialui/src/button/ListButton.tsx b/packages/ra-ui-materialui/src/button/ListButton.tsx index 0450ceb150e..a24cdc4e311 100644 --- a/packages/ra-ui-materialui/src/button/ListButton.tsx +++ b/packages/ra-ui-materialui/src/button/ListButton.tsx @@ -2,6 +2,11 @@ import * as React from 'react'; import ActionList from '@mui/icons-material/List'; import { Link } from 'react-router-dom'; import { useResourceContext, useCreatePath, useCanAccess } from 'ra-core'; +import { + ComponentsOverrides, + styled, + useThemeProps, +} from '@mui/material/styles'; import { Button, ButtonProps } from './Button'; @@ -31,7 +36,12 @@ import { Button, ButtonProps } from './Button'; * * ); */ -export const ListButton = (props: ListButtonProps) => { +export const ListButton = (inProps: ListButtonProps) => { + const props = useThemeProps({ + props: inProps, + name: PREFIX, + }); + const { icon = defaultIcon, label = 'ra.action.list', @@ -56,15 +66,15 @@ export const ListButton = (props: ListButtonProps) => { } return ( - + ); }; @@ -84,3 +94,29 @@ interface Props { } export type ListButtonProps = Props & ButtonProps; + +const PREFIX = 'RaListButton'; + +const StyledButton = styled(Button, { + name: PREFIX, + overridesResolver: (props, styles) => styles.root, +})({}); + +declare module '@mui/material/styles' { + interface ComponentNameToClassKey { + [PREFIX]: 'root'; + } + + interface ComponentsPropsList { + [PREFIX]: Partial; + } + + interface Components { + [PREFIX]?: { + defaultProps?: ComponentsPropsList[typeof PREFIX]; + styleOverrides?: ComponentsOverrides< + Omit + >[typeof PREFIX]; + }; + } +} From ded3f48c50e14538a853b3564e03e9bdd5d6a180 Mon Sep 17 00:00:00 2001 From: Matthieu Hochlander Date: Mon, 9 Jun 2025 12:02:44 +0200 Subject: [PATCH 12/18] Allow to theme RefreshButton and set its default props. --- .../src/button/RefreshButton.tsx | 42 +++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/packages/ra-ui-materialui/src/button/RefreshButton.tsx b/packages/ra-ui-materialui/src/button/RefreshButton.tsx index 571957ee86c..5b494b1e492 100644 --- a/packages/ra-ui-materialui/src/button/RefreshButton.tsx +++ b/packages/ra-ui-materialui/src/button/RefreshButton.tsx @@ -2,10 +2,20 @@ import * as React from 'react'; import { MouseEvent, useCallback } from 'react'; import NavigationRefresh from '@mui/icons-material/Refresh'; import { useRefresh } from 'ra-core'; +import { + ComponentsOverrides, + styled, + useThemeProps, +} from '@mui/material/styles'; import { Button, ButtonProps } from './Button'; -export const RefreshButton = (props: RefreshButtonProps) => { +export const RefreshButton = (inProps: RefreshButtonProps) => { + const props = useThemeProps({ + props: inProps, + name: PREFIX, + }); + const { label = 'ra.action.refresh', icon = defaultIcon, @@ -25,9 +35,9 @@ export const RefreshButton = (props: RefreshButtonProps) => { ); return ( - + ); }; @@ -40,3 +50,29 @@ interface Props { } export type RefreshButtonProps = Props & ButtonProps; + +const PREFIX = 'RaRefreshButton'; + +const StyledButton = styled(Button, { + name: PREFIX, + overridesResolver: (props, styles) => styles.root, +})({}); + +declare module '@mui/material/styles' { + interface ComponentNameToClassKey { + [PREFIX]: 'root'; + } + + interface ComponentsPropsList { + [PREFIX]: Partial; + } + + interface Components { + [PREFIX]?: { + defaultProps?: ComponentsPropsList[typeof PREFIX]; + styleOverrides?: ComponentsOverrides< + Omit + >[typeof PREFIX]; + }; + } +} From ea76c72daf6e501e343583205d2c48b5221cf3c7 Mon Sep 17 00:00:00 2001 From: Matthieu Hochlander Date: Mon, 9 Jun 2025 12:04:15 +0200 Subject: [PATCH 13/18] Allow to theme CloneButton and set its default props. --- .../src/button/CloneButton.tsx | 42 +++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/packages/ra-ui-materialui/src/button/CloneButton.tsx b/packages/ra-ui-materialui/src/button/CloneButton.tsx index 8ba06de05b1..c8d9bd93b66 100644 --- a/packages/ra-ui-materialui/src/button/CloneButton.tsx +++ b/packages/ra-ui-materialui/src/button/CloneButton.tsx @@ -4,10 +4,20 @@ import Queue from '@mui/icons-material/Queue'; import { Link } from 'react-router-dom'; import { stringify } from 'query-string'; import { useResourceContext, useRecordContext, useCreatePath } from 'ra-core'; +import { + ComponentsOverrides, + styled, + useThemeProps, +} from '@mui/material/styles'; import { Button, ButtonProps } from './Button'; -export const CloneButton = (props: CloneButtonProps) => { +export const CloneButton = (inProps: CloneButtonProps) => { + const props = useThemeProps({ + props: inProps, + name: PREFIX, + }); + const { label = 'ra.action.clone', scrollToTop = true, @@ -19,7 +29,7 @@ export const CloneButton = (props: CloneButtonProps) => { const createPath = useCreatePath(); const pathname = createPath({ resource, type: 'create' }); return ( - + ); }; @@ -63,3 +73,29 @@ interface Props { export type CloneButtonProps = Props & Omit, 'to'>; export default memo(CloneButton); + +const PREFIX = 'RaCloneButton'; + +const StyledButton = styled(Button, { + name: PREFIX, + overridesResolver: (props, styles) => styles.root, +})({}); + +declare module '@mui/material/styles' { + interface ComponentNameToClassKey { + [PREFIX]: 'root'; + } + + interface ComponentsPropsList { + [PREFIX]: Partial; + } + + interface Components { + [PREFIX]?: { + defaultProps?: ComponentsPropsList[typeof PREFIX]; + styleOverrides?: ComponentsOverrides< + Omit + >[typeof PREFIX]; + }; + } +} From db112ae8569a2b9343e14149280e1f0826257211 Mon Sep 17 00:00:00 2001 From: Matthieu Hochlander Date: Mon, 9 Jun 2025 14:08:56 +0200 Subject: [PATCH 14/18] Add missing useThemeProps in SimpleList. --- .../src/list/SimpleList/SimpleList.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/ra-ui-materialui/src/list/SimpleList/SimpleList.tsx b/packages/ra-ui-materialui/src/list/SimpleList/SimpleList.tsx index 187d4829eef..afb4c1d69d8 100644 --- a/packages/ra-ui-materialui/src/list/SimpleList/SimpleList.tsx +++ b/packages/ra-ui-materialui/src/list/SimpleList/SimpleList.tsx @@ -7,7 +7,11 @@ import { ListItemText, ListProps, } from '@mui/material'; -import { type ComponentsOverrides, styled } from '@mui/material/styles'; +import { + type ComponentsOverrides, + styled, + useThemeProps, +} from '@mui/material/styles'; import { type RaRecord, RecordContextProvider, @@ -67,8 +71,13 @@ import { * ); */ export const SimpleList = ( - props: SimpleListProps + inProps: SimpleListProps ) => { + const props = useThemeProps({ + props: inProps, + name: PREFIX, + }); + const { className, empty = DefaultEmpty, From 88749f50ca85079c2f37741159e44136843713b5 Mon Sep 17 00:00:00 2001 From: Matthieu Hochlander Date: Mon, 9 Jun 2025 14:23:57 +0200 Subject: [PATCH 15/18] Add stories to test lists themes. --- .../src/list/InfiniteList.spec.tsx | 14 ++++++ .../src/list/InfiniteList.stories.tsx | 35 +++++++++++++- .../ra-ui-materialui/src/list/List.spec.tsx | 10 +++- .../src/list/List.stories.tsx | 47 ++++++++++++++++++- .../src/list/SimpleList/SimpleList.spec.tsx | 8 ++++ .../list/SimpleList/SimpleList.stories.tsx | 47 ++++++++++++++++++- 6 files changed, 156 insertions(+), 5 deletions(-) create mode 100644 packages/ra-ui-materialui/src/list/InfiniteList.spec.tsx diff --git a/packages/ra-ui-materialui/src/list/InfiniteList.spec.tsx b/packages/ra-ui-materialui/src/list/InfiniteList.spec.tsx new file mode 100644 index 00000000000..9fecc9facf5 --- /dev/null +++ b/packages/ra-ui-materialui/src/list/InfiniteList.spec.tsx @@ -0,0 +1,14 @@ +import * as React from 'react'; +import expect from 'expect'; +import { render, screen } from '@testing-library/react'; + +import { Themed } from './List.stories'; + +describe('', () => { + it('should be customized by a theme', async () => { + render(); + expect(screen.queryByTestId('themed-list').classList).toContain( + 'custom-class' + ); + }); +}); diff --git a/packages/ra-ui-materialui/src/list/InfiniteList.stories.tsx b/packages/ra-ui-materialui/src/list/InfiniteList.stories.tsx index a19630c7dad..0be4e22c6a5 100644 --- a/packages/ra-ui-materialui/src/list/InfiniteList.stories.tsx +++ b/packages/ra-ui-materialui/src/list/InfiniteList.stories.tsx @@ -8,7 +8,7 @@ import { useInfinitePaginationContext, TestMemoryRouter, } from 'ra-core'; -import { Box, Button, Card, Typography } from '@mui/material'; +import { Box, Button, Card, ThemeOptions, Typography } from '@mui/material'; import { InfiniteList } from './InfiniteList'; import { SimpleList } from './SimpleList'; @@ -24,6 +24,8 @@ import { SearchInput } from '../input'; import { BulkDeleteButton, SelectAllButton, SortButton } from '../button'; import { TopToolbar, Layout } from '../layout'; import { BulkActionsToolbar } from './BulkActionsToolbar'; +import { deepmerge } from '@mui/utils'; +import { defaultLightTheme } from '../theme'; export default { title: 'ra-ui-materialui/list/InfiniteList', @@ -89,11 +91,12 @@ const dataProvider = fakeRestProvider( 500 ); -const Admin = ({ children, dataProvider, layout }: any) => ( +const Admin = ({ children, dataProvider, layout, ...props }: any) => ( defaultMessages, 'en')} + {...props} > {children} @@ -437,3 +440,31 @@ export const PartialPagination = () => ( /> ); + +export const Themed = () => ( + + ( + + + + )} + /> + +); diff --git a/packages/ra-ui-materialui/src/list/List.spec.tsx b/packages/ra-ui-materialui/src/list/List.spec.tsx index a26abdb22a2..a4da84e28df 100644 --- a/packages/ra-ui-materialui/src/list/List.spec.tsx +++ b/packages/ra-ui-materialui/src/list/List.spec.tsx @@ -9,7 +9,7 @@ import { } from 'ra-core'; import { createTheme, ThemeProvider } from '@mui/material/styles'; -import { defaultTheme } from '../theme/defaultTheme'; +import { defaultTheme } from '../theme'; import { List } from './List'; import { Filter } from './filter'; import { TextInput } from '../input'; @@ -22,6 +22,7 @@ import { PartialPagination, Default, SelectAllLimit, + Themed, } from './List.stories'; const theme = createTheme(defaultTheme); @@ -112,6 +113,13 @@ describe('', () => { expect(screen.queryAllByText('Hello')).toHaveLength(1); }); + it('should be customized by a theme', async () => { + render(); + expect(screen.queryByTestId('themed-list').classList).toContain( + 'custom-class' + ); + }); + describe('empty', () => { it('should render an invite when the list is empty', async () => { const Dummy = () => { diff --git a/packages/ra-ui-materialui/src/list/List.stories.tsx b/packages/ra-ui-materialui/src/list/List.stories.tsx index 31f3bed3cde..4bf5ba41d29 100644 --- a/packages/ra-ui-materialui/src/list/List.stories.tsx +++ b/packages/ra-ui-materialui/src/list/List.stories.tsx @@ -8,7 +8,14 @@ import { DataProvider, } from 'ra-core'; import fakeRestDataProvider from 'ra-data-fakerest'; -import { Box, Card, Typography, Button, Link as MuiLink } from '@mui/material'; +import { + Box, + Card, + Typography, + Button, + Link as MuiLink, + ThemeOptions, +} from '@mui/material'; import { List } from './List'; import { SimpleList } from './SimpleList'; @@ -22,6 +29,8 @@ import { BulkDeleteButton, ListButton, SelectAllButton } from '../button'; import { ShowGuesser } from '../detail'; import TopToolbar from '../layout/TopToolbar'; import { BulkActionsToolbar } from './BulkActionsToolbar'; +import { deepmerge } from '@mui/utils'; +import { defaultLightTheme, RaThemeOptions } from '../theme'; export default { title: 'ra-ui-materialui/list/List' }; @@ -801,3 +810,39 @@ export const ResponseMetadata = () => ( ); + +export const Themed = () => ( + + + ( + + + + )} + /> + + +); diff --git a/packages/ra-ui-materialui/src/list/SimpleList/SimpleList.spec.tsx b/packages/ra-ui-materialui/src/list/SimpleList/SimpleList.spec.tsx index 043985cc520..2db79e94d09 100644 --- a/packages/ra-ui-materialui/src/list/SimpleList/SimpleList.spec.tsx +++ b/packages/ra-ui-materialui/src/list/SimpleList/SimpleList.spec.tsx @@ -22,6 +22,7 @@ import { RowClick, Standalone, StandaloneEmpty, + Themed, } from './SimpleList.stories'; import { Basic } from '../filter/FilterButton.stories'; @@ -259,6 +260,13 @@ describe('', () => { await screen.findByText('War and Peace'); }); + it('should be customized by a theme', async () => { + render(); + expect(screen.queryByTestId('themed-list').classList).toContain( + 'custom-class' + ); + }); + describe('standalone', () => { it('should work without a ListContext', async () => { render(); diff --git a/packages/ra-ui-materialui/src/list/SimpleList/SimpleList.stories.tsx b/packages/ra-ui-materialui/src/list/SimpleList/SimpleList.stories.tsx index 3ce34f44646..7096b5b44d5 100644 --- a/packages/ra-ui-materialui/src/list/SimpleList/SimpleList.stories.tsx +++ b/packages/ra-ui-materialui/src/list/SimpleList/SimpleList.stories.tsx @@ -11,7 +11,14 @@ import { } from 'ra-core'; import defaultMessages from 'ra-language-english'; import polyglotI18nProvider from 'ra-i18n-polyglot'; -import { Alert, Box, FormControlLabel, FormGroup, Switch } from '@mui/material'; +import { + Alert, + Box, + FormControlLabel, + FormGroup, + Switch, + ThemeOptions, +} from '@mui/material'; import { Location } from 'react-router'; import { AdminUI } from '../../AdminUI'; @@ -21,6 +28,8 @@ import { List, ListProps } from '../List'; import { RowClickFunction } from '../types'; import { SimpleList } from './SimpleList'; import { FunctionLinkType } from './SimpleListItem'; +import { deepmerge } from '@mui/utils'; +import { defaultLightTheme } from '../../theme'; export default { title: 'ra-ui-materialui/list/SimpleList' }; @@ -430,3 +439,39 @@ export const StandaloneEmpty = () => ( ); + +export const Themed = () => ( + + + + record.title} + secondaryText={record => record.author} + tertiaryText={record => record.year} + /> + + + +); From 0cf7e1ec60c87837540a4c08059170ab82d831c2 Mon Sep 17 00:00:00 2001 From: Matthieu Hochlander Date: Mon, 9 Jun 2025 14:35:34 +0200 Subject: [PATCH 16/18] Add stories to test detail views themes. --- .../src/detail/Create.spec.tsx | 8 +++++ .../src/detail/Create.stories.tsx | 30 +++++++++++++++++- .../ra-ui-materialui/src/detail/Edit.spec.tsx | 8 +++++ .../src/detail/Edit.stories.tsx | 30 +++++++++++++++++- .../ra-ui-materialui/src/detail/Show.spec.tsx | 8 +++++ .../src/detail/Show.stories.tsx | 31 ++++++++++++++++++- 6 files changed, 112 insertions(+), 3 deletions(-) diff --git a/packages/ra-ui-materialui/src/detail/Create.spec.tsx b/packages/ra-ui-materialui/src/detail/Create.spec.tsx index 49a3a3aded8..abffa876427 100644 --- a/packages/ra-ui-materialui/src/detail/Create.spec.tsx +++ b/packages/ra-ui-materialui/src/detail/Create.spec.tsx @@ -11,6 +11,7 @@ import { TitleElement, NotificationDefault, NotificationTranslated, + Themed, } from './Create.stories'; describe('', () => { @@ -49,6 +50,13 @@ describe('', () => { expect(screen.queryAllByText('help')).toHaveLength(1); }); + it('should be customized by a theme', async () => { + render(); + expect(screen.queryByTestId('themed-view').classList).toContain( + 'custom-class' + ); + }); + describe('title', () => { it('should display by default the title of the resource', async () => { render(); diff --git a/packages/ra-ui-materialui/src/detail/Create.stories.tsx b/packages/ra-ui-materialui/src/detail/Create.stories.tsx index 70e53bdae84..e725efdf7fa 100644 --- a/packages/ra-ui-materialui/src/detail/Create.stories.tsx +++ b/packages/ra-ui-materialui/src/detail/Create.stories.tsx @@ -9,13 +9,15 @@ import { } from 'ra-core'; import polyglotI18nProvider from 'ra-i18n-polyglot'; import englishMessages from 'ra-language-english'; -import { Box, Card, Stack } from '@mui/material'; +import { Box, Card, Stack, ThemeOptions } from '@mui/material'; import { TextInput } from '../input'; import { SimpleForm } from '../form/SimpleForm'; import { ListButton, SaveButton } from '../button'; import TopToolbar from '../layout/TopToolbar'; import { Create } from './Create'; +import { deepmerge } from '@mui/utils'; +import { defaultLightTheme } from '../theme'; export default { title: 'ra-ui-materialui/detail/Create' }; @@ -270,3 +272,29 @@ export const Default = () => ( ); + +export const Themed = () => ( + + + ( + + + + )} + /> + + +); diff --git a/packages/ra-ui-materialui/src/detail/Edit.spec.tsx b/packages/ra-ui-materialui/src/detail/Edit.spec.tsx index 39369cc9738..58c7fbd5845 100644 --- a/packages/ra-ui-materialui/src/detail/Edit.spec.tsx +++ b/packages/ra-ui-materialui/src/detail/Edit.spec.tsx @@ -28,6 +28,7 @@ import { NotificationDefault, NotificationTranslated, EmptyWhileLoading, + Themed, } from './Edit.stories'; describe('', () => { @@ -140,6 +141,13 @@ describe('', () => { expect(screen.queryByText('Something went wrong')).toBeNull(); }); + it('should be customized by a theme', async () => { + render(); + expect(screen.queryByTestId('themed-view').classList).toContain( + 'custom-class' + ); + }); + describe('mutationMode prop', () => { it('should be undoable by default', async () => { let post = { id: 1234, title: 'lorem' }; diff --git a/packages/ra-ui-materialui/src/detail/Edit.stories.tsx b/packages/ra-ui-materialui/src/detail/Edit.stories.tsx index a043c913fef..3ff2c151d0a 100644 --- a/packages/ra-ui-materialui/src/detail/Edit.stories.tsx +++ b/packages/ra-ui-materialui/src/detail/Edit.stories.tsx @@ -9,13 +9,15 @@ import { } from 'ra-core'; import polyglotI18nProvider from 'ra-i18n-polyglot'; import englishMessages from 'ra-language-english'; -import { Box, Card, Stack, Typography } from '@mui/material'; +import { Box, Card, Stack, ThemeOptions, Typography } from '@mui/material'; import { TextInput } from '../input'; import { SimpleForm } from '../form/SimpleForm'; import { ShowButton, SaveButton } from '../button'; import TopToolbar from '../layout/TopToolbar'; import { Edit } from './Edit'; +import { deepmerge } from '@mui/utils'; +import { defaultLightTheme } from '../theme'; export default { title: 'ra-ui-materialui/detail/Edit' }; @@ -362,3 +364,29 @@ const AsideComponentWithRecord = () => { ); }; + +export const Themed = () => ( + + + ( + + + + )} + /> + + +); diff --git a/packages/ra-ui-materialui/src/detail/Show.spec.tsx b/packages/ra-ui-materialui/src/detail/Show.spec.tsx index 18d6453735b..64c3a0a475f 100644 --- a/packages/ra-ui-materialui/src/detail/Show.spec.tsx +++ b/packages/ra-ui-materialui/src/detail/Show.spec.tsx @@ -24,6 +24,7 @@ import { Title, TitleFalse, TitleElement, + Themed, } from './Show.stories'; import { Show } from './Show'; @@ -129,6 +130,13 @@ describe('', () => { expect(screen.getByTestId('custom-component')).toBeDefined(); }); + it('should be customized by a theme', async () => { + render(); + expect(screen.queryByTestId('themed-view').classList).toContain( + 'custom-class' + ); + }); + describe('title', () => { it('should display by default the title of the resource', async () => { render(); diff --git a/packages/ra-ui-materialui/src/detail/Show.stories.tsx b/packages/ra-ui-materialui/src/detail/Show.stories.tsx index 4e9ecc5e71a..bf9d9b166ec 100644 --- a/packages/ra-ui-materialui/src/detail/Show.stories.tsx +++ b/packages/ra-ui-materialui/src/detail/Show.stories.tsx @@ -1,13 +1,16 @@ import * as React from 'react'; import { Admin } from 'react-admin'; import { Resource, useRecordContext, TestMemoryRouter } from 'ra-core'; -import { Box, Card, Stack } from '@mui/material'; +import { Box, Card, Stack, ThemeOptions } from '@mui/material'; + import { TextField } from '../field'; import { Labeled } from '../Labeled'; import { SimpleShowLayout } from './SimpleShowLayout'; import { EditButton } from '../button'; import TopToolbar from '../layout/TopToolbar'; import { Show } from './Show'; +import { deepmerge } from '@mui/utils'; +import { defaultLightTheme } from '../theme'; export default { title: 'ra-ui-materialui/detail/Show' }; @@ -244,3 +247,29 @@ export const Default = () => ( ); + +export const Themed = () => ( + + + ( + + + + )} + /> + + +); From 4938e259cfb30ee76cd6e0519205a9bb83ed3b97 Mon Sep 17 00:00:00 2001 From: Matthieu Hochlander Date: Mon, 9 Jun 2025 15:34:14 +0200 Subject: [PATCH 17/18] Test buttons default props. --- .../src/button/BulkDeleteButton.spec.tsx | 53 +++++++++++++++++++ .../src/button/BulkExportButton.spec.tsx | 47 +++++++++++++--- .../src/button/BulkUpdateButton.spec.tsx | 15 +++++- .../src/button/BulkUpdateButton.stories.tsx | 29 +++++++++- .../src/button/CloneButton.spec.tsx | 30 +++++++++++ .../src/button/DeleteButton.spec.tsx | 9 ++++ .../src/button/DeleteButton.stories.tsx | 23 +++++++- .../src/button/ListButton.spec.tsx | 9 +++- .../src/button/ListButton.stories.tsx | 29 ++++++++++ .../src/button/ShowButton.spec.tsx | 9 +++- .../src/button/ShowButton.stories.tsx | 29 ++++++++++ .../src/button/UpdateButton.spec.tsx | 13 +++++ .../src/button/UpdateButton.stories.tsx | 26 +++++++++ 13 files changed, 309 insertions(+), 12 deletions(-) create mode 100644 packages/ra-ui-materialui/src/button/BulkDeleteButton.spec.tsx create mode 100644 packages/ra-ui-materialui/src/button/UpdateButton.spec.tsx diff --git a/packages/ra-ui-materialui/src/button/BulkDeleteButton.spec.tsx b/packages/ra-ui-materialui/src/button/BulkDeleteButton.spec.tsx new file mode 100644 index 00000000000..20390f7473b --- /dev/null +++ b/packages/ra-ui-materialui/src/button/BulkDeleteButton.spec.tsx @@ -0,0 +1,53 @@ +import * as React from 'react'; +import { render, screen } from '@testing-library/react'; +import expect from 'expect'; +import { deepmerge } from '@mui/utils'; +import { ThemeOptions } from '@mui/material'; +import { ListContextProvider, testDataProvider } from 'ra-core'; + +import { AdminContext } from '../AdminContext'; +import { defaultLightTheme } from '../theme'; +import { BulkDeleteButton } from './BulkDeleteButton'; + +describe('', () => { + const dataProvider = testDataProvider({ + deleteMany: async () => ({ data: [{ id: 123 }] as any }), + }); + + it('should be customized by a theme', () => { + render( + + {}, + } as any + } + > + + + + ); + + const button = screen.getByTestId('themed-button'); + expect(button.textContent).toBe('Bulk Delete'); + expect(button.classList).toContain('custom-class'); + }); +}); diff --git a/packages/ra-ui-materialui/src/button/BulkExportButton.spec.tsx b/packages/ra-ui-materialui/src/button/BulkExportButton.spec.tsx index cc9d9e25518..fd54e633d62 100644 --- a/packages/ra-ui-materialui/src/button/BulkExportButton.spec.tsx +++ b/packages/ra-ui-materialui/src/button/BulkExportButton.spec.tsx @@ -6,19 +6,20 @@ import { testDataProvider, ListContextProvider, } from 'ra-core'; -import { createTheme, ThemeProvider } from '@mui/material/styles'; +import { createTheme, ThemeProvider, ThemeOptions } from '@mui/material/styles'; +import { deepmerge } from '@mui/utils'; import { BulkExportButton } from './BulkExportButton'; const theme = createTheme(); describe('', () => { - it('should invoke dataProvider with meta', async () => { - const exporter = jest.fn().mockName('exporter'); - const dataProvider = testDataProvider({ - getMany: jest.fn().mockResolvedValueOnce({ data: [], total: 0 }), - }); + const exporter = jest.fn().mockName('exporter'); + const dataProvider = testDataProvider({ + getMany: jest.fn().mockResolvedValueOnce({ data: [], total: 0 }), + }); + it('should invoke dataProvider with meta', async () => { render( @@ -46,4 +47,38 @@ describe('', () => { expect(exporter).toHaveBeenCalled(); }); }); + + it('should be customized by a theme', () => { + render( + + + + + + + + ); + + const button = screen.getByTestId('themed-button'); + expect(button.textContent).toBe('Bulk Export'); + expect(button.classList).toContain('custom-class'); + }); }); diff --git a/packages/ra-ui-materialui/src/button/BulkUpdateButton.spec.tsx b/packages/ra-ui-materialui/src/button/BulkUpdateButton.spec.tsx index b8aed5e8303..0be39ff8a7f 100644 --- a/packages/ra-ui-materialui/src/button/BulkUpdateButton.spec.tsx +++ b/packages/ra-ui-materialui/src/button/BulkUpdateButton.spec.tsx @@ -1,9 +1,22 @@ import * as React from 'react'; import expect from 'expect'; import { render, screen } from '@testing-library/react'; -import { MutationMode } from './BulkUpdateButton.stories'; +import { MutationMode, Themed } from './BulkUpdateButton.stories'; describe('BulkUpdateButton', () => { + it('should be customized by a theme', async () => { + render(); + + const checkbox = await screen.findByRole('checkbox', { + name: 'Select all', + }); + checkbox.click(); + + const button = screen.queryByTestId('themed-button'); + expect(button.textContent).toBe('Bulk Update'); + expect(button.classList).toContain('custom-class'); + }); + describe('mutationMode', () => { it('should ask confirmation before updating in pessimistic mode', async () => { render(); diff --git a/packages/ra-ui-materialui/src/button/BulkUpdateButton.stories.tsx b/packages/ra-ui-materialui/src/button/BulkUpdateButton.stories.tsx index b2ea441e5ad..e7fe4ee1e36 100644 --- a/packages/ra-ui-materialui/src/button/BulkUpdateButton.stories.tsx +++ b/packages/ra-ui-materialui/src/button/BulkUpdateButton.stories.tsx @@ -9,6 +9,9 @@ import { AdminContext } from '../AdminContext'; import { AdminUI } from '../AdminUI'; import { List, Datagrid } from '../list'; import { TextField, NumberField } from '../field'; +import { deepmerge } from '@mui/utils'; +import { defaultLightTheme } from '../theme'; +import { ThemeOptions } from '@mui/material'; export default { title: 'ra-ui-materialui/button/BulkUpdateButton' }; @@ -89,9 +92,13 @@ const dataProvider = fakeRestDataProvider({ authors: [], }); -const Wrapper = ({ bulkActionButtons }) => ( +const Wrapper = ({ bulkActionButtons, theme = undefined }) => ( - + ( } /> ); + +export const Themed = () => ( + } + theme={deepmerge(defaultLightTheme, { + components: { + RaBulkUpdateButton: { + defaultProps: { + label: 'Bulk Update', + mutationMode: 'optimistic', + className: 'custom-class', + 'data-testid': 'themed-button', + }, + }, + }, + } as ThemeOptions)} + /> +); diff --git a/packages/ra-ui-materialui/src/button/CloneButton.spec.tsx b/packages/ra-ui-materialui/src/button/CloneButton.spec.tsx index 5e87820c22c..32fc0a15654 100644 --- a/packages/ra-ui-materialui/src/button/CloneButton.spec.tsx +++ b/packages/ra-ui-materialui/src/button/CloneButton.spec.tsx @@ -4,6 +4,9 @@ import { render, screen } from '@testing-library/react'; import { AdminContext } from '../AdminContext'; import { CloneButton } from './CloneButton'; +import { deepmerge } from '@mui/utils'; +import { defaultLightTheme } from '../theme'; +import { ThemeOptions } from '@mui/material'; const invalidButtonDomProps = { record: { id: 123, foo: 'bar' }, @@ -42,4 +45,31 @@ describe('', () => { spy.mockRestore(); }); + + it('should be customized by a theme', () => { + render( + + + + ); + + const button = screen.getByTestId('themed-button'); + expect(button.textContent).toBe('Clone'); + expect(button.classList).toContain('custom-class'); + }); }); diff --git a/packages/ra-ui-materialui/src/button/DeleteButton.spec.tsx b/packages/ra-ui-materialui/src/button/DeleteButton.spec.tsx index cd6b365b871..a7e14481472 100644 --- a/packages/ra-ui-materialui/src/button/DeleteButton.spec.tsx +++ b/packages/ra-ui-materialui/src/button/DeleteButton.spec.tsx @@ -5,6 +5,7 @@ import { NotificationDefault, NotificationTranslated, FullApp, + Themed, } from './DeleteButton.stories'; describe('', () => { @@ -28,6 +29,14 @@ describe('', () => { expect(screen.queryAllByLabelText('Delete')).toHaveLength(1); }); }); + + it('should be customized by a theme', async () => { + render(); + const button = screen.queryByTestId('themed-button'); + expect(button.classList).toContain('custom-class'); + expect(button.textContent).toBe('Delete'); + }); + describe('success notification', () => { it('should use a generic success message by default', async () => { render(); diff --git a/packages/ra-ui-materialui/src/button/DeleteButton.stories.tsx b/packages/ra-ui-materialui/src/button/DeleteButton.stories.tsx index 959493baf24..3e5983bc2c1 100644 --- a/packages/ra-ui-materialui/src/button/DeleteButton.stories.tsx +++ b/packages/ra-ui-materialui/src/button/DeleteButton.stories.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { colors, createTheme, Alert } from '@mui/material'; +import { colors, createTheme, Alert, ThemeOptions } from '@mui/material'; import polyglotI18nProvider from 'ra-i18n-polyglot'; import englishMessages from 'ra-language-english'; import frenchMessages from 'ra-language-french'; @@ -18,6 +18,8 @@ import { Datagrid } from '../list/datagrid/Datagrid'; import { TextField } from '../field/TextField'; import { AdminUI } from '../AdminUI'; import { Notification } from '../layout'; +import { deepmerge } from '@mui/utils'; +import { defaultLightTheme } from '../theme'; const theme = createTheme({ palette: { @@ -385,3 +387,22 @@ export const SuccessMessage = () => { ); }; + +export const Themed = () => ( + + + + + +); diff --git a/packages/ra-ui-materialui/src/button/ListButton.spec.tsx b/packages/ra-ui-materialui/src/button/ListButton.spec.tsx index d7bb59e0da1..f40616041d2 100644 --- a/packages/ra-ui-materialui/src/button/ListButton.spec.tsx +++ b/packages/ra-ui-materialui/src/button/ListButton.spec.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { fireEvent, render, screen } from '@testing-library/react'; import expect from 'expect'; -import { Basic, AccessControl } from './ListButton.stories'; +import { Basic, AccessControl, Themed } from './ListButton.stories'; const invalidButtonDomProps = { redirect: 'list', @@ -30,4 +30,11 @@ describe('', () => { fireEvent.click(screen.getByLabelText('Allow accessing books')); await screen.findByLabelText('List'); }); + + it('should be customized by a theme', async () => { + render(); + const button = screen.queryByTestId('themed-button'); + expect(button.classList).toContain('custom-class'); + expect(button.textContent).toBe('List'); + }); }); diff --git a/packages/ra-ui-materialui/src/button/ListButton.stories.tsx b/packages/ra-ui-materialui/src/button/ListButton.stories.tsx index ae34365664a..ef9e0631201 100644 --- a/packages/ra-ui-materialui/src/button/ListButton.stories.tsx +++ b/packages/ra-ui-materialui/src/button/ListButton.stories.tsx @@ -21,6 +21,9 @@ import { TextInput } from '../input/TextInput'; import { ListButton } from './ListButton'; import { Edit } from '../detail/Edit'; import { TopToolbar } from '../layout'; +import { deepmerge } from '@mui/utils'; +import { defaultLightTheme } from '../theme'; +import { ThemeOptions } from '@mui/material'; export default { title: 'ra-ui-materialui/button/ListButton' }; @@ -234,3 +237,29 @@ const dataProvider = fakeRestDataProvider({ ], authors: [], }); + +export const Themed = ({ buttonProps }: { buttonProps?: any }) => ( + + + + + + + + + +); diff --git a/packages/ra-ui-materialui/src/button/ShowButton.spec.tsx b/packages/ra-ui-materialui/src/button/ShowButton.spec.tsx index 8edb3163ef8..7404ba141ae 100644 --- a/packages/ra-ui-materialui/src/button/ShowButton.spec.tsx +++ b/packages/ra-ui-materialui/src/button/ShowButton.spec.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import expect from 'expect'; -import { Basic, AccessControl } from './ShowButton.stories'; +import { Basic, AccessControl, Themed } from './ShowButton.stories'; const invalidButtonDomProps = { redirect: 'list', @@ -43,4 +43,11 @@ describe('', () => { expect(screen.queryAllByLabelText('Show')).toHaveLength(1); }); }); + + it('should be customized by a theme', async () => { + render(); + const button = screen.queryByTestId('themed-button'); + expect(button.classList).toContain('custom-class'); + expect(button.textContent).toBe('Show'); + }); }); diff --git a/packages/ra-ui-materialui/src/button/ShowButton.stories.tsx b/packages/ra-ui-materialui/src/button/ShowButton.stories.tsx index c61d1060cf5..6919214e543 100644 --- a/packages/ra-ui-materialui/src/button/ShowButton.stories.tsx +++ b/packages/ra-ui-materialui/src/button/ShowButton.stories.tsx @@ -19,6 +19,9 @@ import { TextField } from '../field/TextField'; import ShowButton from './ShowButton'; import { Show } from '../detail/Show'; import { SimpleShowLayout } from '../detail/SimpleShowLayout'; +import { deepmerge } from '@mui/utils'; +import { defaultLightTheme } from '../theme'; +import { ThemeOptions } from '@mui/material'; export default { title: 'ra-ui-materialui/button/ShowButton' }; @@ -248,3 +251,29 @@ const dataProvider = fakeRestDataProvider({ ], authors: [], }); + +export const Themed = ({ buttonProps }: { buttonProps?: any }) => ( + + + + + + + + + +); diff --git a/packages/ra-ui-materialui/src/button/UpdateButton.spec.tsx b/packages/ra-ui-materialui/src/button/UpdateButton.spec.tsx new file mode 100644 index 00000000000..7f2efec2b13 --- /dev/null +++ b/packages/ra-ui-materialui/src/button/UpdateButton.spec.tsx @@ -0,0 +1,13 @@ +import { render, screen } from '@testing-library/react'; +import expect from 'expect'; +import * as React from 'react'; +import { Themed } from './UpdateButton.stories'; + +describe('UpdateButton', () => { + it('should be customized by a theme', async () => { + render(); + expect(screen.queryByTestId('themed-button').classList).toContain( + 'custom-class' + ); + }); +}); diff --git a/packages/ra-ui-materialui/src/button/UpdateButton.stories.tsx b/packages/ra-ui-materialui/src/button/UpdateButton.stories.tsx index c462db62fed..cbb1b30f928 100644 --- a/packages/ra-ui-materialui/src/button/UpdateButton.stories.tsx +++ b/packages/ra-ui-materialui/src/button/UpdateButton.stories.tsx @@ -17,6 +17,9 @@ import { NumberField, TextField } from '../field'; import { Show, SimpleShowLayout } from '../detail'; import { TopToolbar } from '../layout'; import { Datagrid, List } from '../list'; +import { deepmerge } from '@mui/utils'; +import { defaultLightTheme } from '../theme'; +import { ThemeOptions } from '@mui/material'; export default { title: 'ra-ui-materialui/button/UpdateButton' }; @@ -309,3 +312,26 @@ export const SideEffects = () => ( ); + +export const Themed = () => ( + + + + } /> + + + +); From 669995be224e3449b3888f6d8099b03b558e9257 Mon Sep 17 00:00:00 2001 From: Matthieu Hochlander Date: Thu, 12 Jun 2025 10:26:56 +0200 Subject: [PATCH 18/18] Add missing stories to test buttons. --- .../src/button/BulkDeleteButton.spec.tsx | 46 +----- .../src/button/BulkDeleteButton.stories.tsx | 148 +++++++++++++++++ .../src/button/BulkExportButton.spec.tsx | 36 +---- .../src/button/BulkExportButton.stories.tsx | 152 ++++++++++++++++++ .../src/button/CloneButton.spec.tsx | 38 +---- .../src/button/CloneButton.stories.tsx | 41 +++++ 6 files changed, 355 insertions(+), 106 deletions(-) create mode 100644 packages/ra-ui-materialui/src/button/BulkDeleteButton.stories.tsx create mode 100644 packages/ra-ui-materialui/src/button/BulkExportButton.stories.tsx create mode 100644 packages/ra-ui-materialui/src/button/CloneButton.stories.tsx diff --git a/packages/ra-ui-materialui/src/button/BulkDeleteButton.spec.tsx b/packages/ra-ui-materialui/src/button/BulkDeleteButton.spec.tsx index 20390f7473b..50656f32ec0 100644 --- a/packages/ra-ui-materialui/src/button/BulkDeleteButton.spec.tsx +++ b/packages/ra-ui-materialui/src/button/BulkDeleteButton.spec.tsx @@ -1,52 +1,14 @@ import * as React from 'react'; import { render, screen } from '@testing-library/react'; import expect from 'expect'; -import { deepmerge } from '@mui/utils'; -import { ThemeOptions } from '@mui/material'; -import { ListContextProvider, testDataProvider } from 'ra-core'; -import { AdminContext } from '../AdminContext'; -import { defaultLightTheme } from '../theme'; -import { BulkDeleteButton } from './BulkDeleteButton'; +import { Themed } from './BulkDeleteButton.stories'; describe('', () => { - const dataProvider = testDataProvider({ - deleteMany: async () => ({ data: [{ id: 123 }] as any }), - }); - - it('should be customized by a theme', () => { - render( - - {}, - } as any - } - > - - - - ); + it('should be customized by a theme', async () => { + render(); - const button = screen.getByTestId('themed-button'); + const button = await screen.findByTestId('themed'); expect(button.textContent).toBe('Bulk Delete'); expect(button.classList).toContain('custom-class'); }); diff --git a/packages/ra-ui-materialui/src/button/BulkDeleteButton.stories.tsx b/packages/ra-ui-materialui/src/button/BulkDeleteButton.stories.tsx new file mode 100644 index 00000000000..14a963c3df1 --- /dev/null +++ b/packages/ra-ui-materialui/src/button/BulkDeleteButton.stories.tsx @@ -0,0 +1,148 @@ +import React from 'react'; +import { ThemeOptions } from '@mui/material'; +import { deepmerge } from '@mui/utils'; +import { Resource } from 'ra-core'; +import polyglotI18nProvider from 'ra-i18n-polyglot'; +import englishMessages from 'ra-language-english'; +import fakeRestDataProvider from 'ra-data-fakerest'; + +import { AdminContext } from '../AdminContext'; +import { BulkDeleteButton } from './BulkDeleteButton'; +import { defaultLightTheme } from '../theme'; +import { Datagrid, List } from '../list'; +import { NumberField, TextField } from '../field'; +import { AdminUI } from '../AdminUI'; + +export default { title: 'ra-ui-materialui/button/BulkDeleteButton' }; + +const i18nProvider = polyglotI18nProvider( + () => englishMessages, + 'en' // Default locale +); + +const dataProvider = fakeRestDataProvider({ + books: [ + { + id: 1, + title: 'War and Peace', + author: 'Leo Tolstoy', + reads: 23, + }, + { + id: 2, + title: 'Pride and Predjudice', + author: 'Jane Austen', + reads: 854, + }, + { + id: 3, + title: 'The Picture of Dorian Gray', + author: 'Oscar Wilde', + reads: 126, + }, + { + id: 4, + title: 'Le Petit Prince', + author: 'Antoine de Saint-Exupéry', + reads: 86, + }, + { + id: 5, + title: "Alice's Adventures in Wonderland", + author: 'Lewis Carroll', + reads: 125, + }, + { + id: 6, + title: 'Madame Bovary', + author: 'Gustave Flaubert', + reads: 452, + }, + { + id: 7, + title: 'The Lord of the Rings', + author: 'J. R. R. Tolkien', + reads: 267, + }, + { + id: 8, + title: "Harry Potter and the Philosopher's Stone", + author: 'J. K. Rowling', + reads: 1294, + }, + { + id: 9, + title: 'The Alchemist', + author: 'Paulo Coelho', + reads: 23, + }, + { + id: 10, + title: 'A Catcher in the Rye', + author: 'J. D. Salinger', + reads: 209, + }, + { + id: 11, + title: 'Ulysses', + author: 'James Joyce', + reads: 12, + }, + ], + authors: [], +}); + +const Wrapper = ({ children, ...props }) => { + return ( + + + ( + + + + + + + + + )} + /> + + + ); +}; + +export const Basic = () => { + return ( + + + + ); +}; + +export const Themed = () => { + return ( + + + + ); +}; diff --git a/packages/ra-ui-materialui/src/button/BulkExportButton.spec.tsx b/packages/ra-ui-materialui/src/button/BulkExportButton.spec.tsx index fd54e633d62..111e0e68967 100644 --- a/packages/ra-ui-materialui/src/button/BulkExportButton.spec.tsx +++ b/packages/ra-ui-materialui/src/button/BulkExportButton.spec.tsx @@ -6,10 +6,10 @@ import { testDataProvider, ListContextProvider, } from 'ra-core'; -import { createTheme, ThemeProvider, ThemeOptions } from '@mui/material/styles'; -import { deepmerge } from '@mui/utils'; +import { createTheme, ThemeProvider } from '@mui/material/styles'; import { BulkExportButton } from './BulkExportButton'; +import { Themed } from './BulkExportButton.stories'; const theme = createTheme(); @@ -48,36 +48,10 @@ describe('', () => { }); }); - it('should be customized by a theme', () => { - render( - - - - - - - - ); + it('should be customized by a theme', async () => { + render(); - const button = screen.getByTestId('themed-button'); + const button = await screen.findByTestId('themed'); expect(button.textContent).toBe('Bulk Export'); expect(button.classList).toContain('custom-class'); }); diff --git a/packages/ra-ui-materialui/src/button/BulkExportButton.stories.tsx b/packages/ra-ui-materialui/src/button/BulkExportButton.stories.tsx new file mode 100644 index 00000000000..ca636ed1fd5 --- /dev/null +++ b/packages/ra-ui-materialui/src/button/BulkExportButton.stories.tsx @@ -0,0 +1,152 @@ +import React from 'react'; +import { ThemeOptions } from '@mui/material'; +import { deepmerge } from '@mui/utils'; +import { Resource } from 'ra-core'; +import polyglotI18nProvider from 'ra-i18n-polyglot'; +import englishMessages from 'ra-language-english'; +import fakeRestDataProvider from 'ra-data-fakerest'; + +import { AdminContext } from '../AdminContext'; +import { BulkExportButton } from './BulkExportButton'; +import { defaultLightTheme } from '../theme'; +import { Datagrid, List } from '../list'; +import { NumberField, TextField } from '../field'; +import { AdminUI } from '../AdminUI'; + +export default { title: 'ra-ui-materialui/button/BulkExportButton' }; + +const i18nProvider = polyglotI18nProvider( + () => englishMessages, + 'en' // Default locale +); + +const dataProvider = fakeRestDataProvider({ + books: [ + { + id: 1, + title: 'War and Peace', + author: 'Leo Tolstoy', + reads: 23, + }, + { + id: 2, + title: 'Pride and Predjudice', + author: 'Jane Austen', + reads: 854, + }, + { + id: 3, + title: 'The Picture of Dorian Gray', + author: 'Oscar Wilde', + reads: 126, + }, + { + id: 4, + title: 'Le Petit Prince', + author: 'Antoine de Saint-Exupéry', + reads: 86, + }, + { + id: 5, + title: "Alice's Adventures in Wonderland", + author: 'Lewis Carroll', + reads: 125, + }, + { + id: 6, + title: 'Madame Bovary', + author: 'Gustave Flaubert', + reads: 452, + }, + { + id: 7, + title: 'The Lord of the Rings', + author: 'J. R. R. Tolkien', + reads: 267, + }, + { + id: 8, + title: "Harry Potter and the Philosopher's Stone", + author: 'J. K. Rowling', + reads: 1294, + }, + { + id: 9, + title: 'The Alchemist', + author: 'Paulo Coelho', + reads: 23, + }, + { + id: 10, + title: 'A Catcher in the Rye', + author: 'J. D. Salinger', + reads: 209, + }, + { + id: 11, + title: 'Ulysses', + author: 'James Joyce', + reads: 12, + }, + ], + authors: [], +}); + +const Wrapper = ({ children, ...props }) => { + return ( + + + ( + + + + + + + + + )} + /> + + + ); +}; + +export const Basic = () => { + return ( + + + + ); +}; + +export const Themed = () => { + return ( + + + + ); +}; diff --git a/packages/ra-ui-materialui/src/button/CloneButton.spec.tsx b/packages/ra-ui-materialui/src/button/CloneButton.spec.tsx index 32fc0a15654..239a815ef5f 100644 --- a/packages/ra-ui-materialui/src/button/CloneButton.spec.tsx +++ b/packages/ra-ui-materialui/src/button/CloneButton.spec.tsx @@ -4,9 +4,7 @@ import { render, screen } from '@testing-library/react'; import { AdminContext } from '../AdminContext'; import { CloneButton } from './CloneButton'; -import { deepmerge } from '@mui/utils'; -import { defaultLightTheme } from '../theme'; -import { ThemeOptions } from '@mui/material'; +import { Basic, Themed } from './CloneButton.stories'; const invalidButtonDomProps = { record: { id: 123, foo: 'bar' }, @@ -15,14 +13,7 @@ const invalidButtonDomProps = { describe('', () => { it('should pass a clone of the record in the location state', () => { - render( - - - - ); + render(); expect( screen.getByLabelText('ra.action.clone').getAttribute('href') @@ -46,29 +37,10 @@ describe('', () => { spy.mockRestore(); }); - it('should be customized by a theme', () => { - render( - - - - ); + it('should be customized by a theme', async () => { + render(); - const button = screen.getByTestId('themed-button'); + const button = await screen.findByTestId('themed'); expect(button.textContent).toBe('Clone'); expect(button.classList).toContain('custom-class'); }); diff --git a/packages/ra-ui-materialui/src/button/CloneButton.stories.tsx b/packages/ra-ui-materialui/src/button/CloneButton.stories.tsx new file mode 100644 index 00000000000..6450fba0936 --- /dev/null +++ b/packages/ra-ui-materialui/src/button/CloneButton.stories.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { deepmerge } from '@mui/utils'; +import { ThemeOptions } from '@mui/material'; + +import { defaultLightTheme } from '../theme'; +import { CloneButton } from './CloneButton'; +import { AdminContext } from '../AdminContext'; + +export default { title: 'ra-ui-materialui/button/CloneButton' }; + +const Wrapper = ({ children, ...props }) => { + return {children}; +}; + +export const Basic = () => { + return ( + + + + ); +}; + +export const Themed = () => { + return ( + + + + ); +};