diff --git a/packages/ra-ui-materialui/src/input/BooleanInput.spec.tsx b/packages/ra-ui-materialui/src/input/BooleanInput.spec.tsx index 1f3e422b487..9e5a62c0192 100644 --- a/packages/ra-ui-materialui/src/input/BooleanInput.spec.tsx +++ b/packages/ra-ui-materialui/src/input/BooleanInput.spec.tsx @@ -5,6 +5,7 @@ import { ResourceContextProvider, testDataProvider } from 'ra-core'; import { AdminContext } from '../AdminContext'; import { SimpleForm } from '../form'; import { BooleanInput } from './BooleanInput'; +import { Themed } from './BooleanInput.stories'; describe('', () => { const defaultProps = { @@ -185,4 +186,9 @@ describe('', () => { expect(screen.queryByText('ra.validation.error')).not.toBeNull(); }); }); + + it('should be customized by a theme', async () => { + render(); + await screen.findByTestId('themed'); + }); }); diff --git a/packages/ra-ui-materialui/src/input/BooleanInput.stories.tsx b/packages/ra-ui-materialui/src/input/BooleanInput.stories.tsx index e3e7b675584..10f42e4c137 100644 --- a/packages/ra-ui-materialui/src/input/BooleanInput.stories.tsx +++ b/packages/ra-ui-materialui/src/input/BooleanInput.stories.tsx @@ -3,6 +3,7 @@ import polyglotI18nProvider from 'ra-i18n-polyglot'; import englishMessages from 'ra-language-english'; import { useFormContext } from 'react-hook-form'; import FavoriteIcon from '@mui/icons-material/Favorite'; +import { createTheme } from '@mui/material/styles'; import { AdminContext } from '../AdminContext'; import { Create } from '../detail'; @@ -44,10 +45,11 @@ export const Dark = () => ( const i18nProvider = polyglotI18nProvider(() => englishMessages); -const Wrapper = ({ children, defaultTheme = 'light' }) => ( +const Wrapper = ({ children, defaultTheme = 'light', theme = undefined }) => ( {children} @@ -73,3 +75,24 @@ export const SetFocus = () => ( ); + +export const Themed = () => ( + + + +); diff --git a/packages/ra-ui-materialui/src/input/BooleanInput.tsx b/packages/ra-ui-materialui/src/input/BooleanInput.tsx index 6b661b78517..bc5b121a35e 100644 --- a/packages/ra-ui-materialui/src/input/BooleanInput.tsx +++ b/packages/ra-ui-materialui/src/input/BooleanInput.tsx @@ -6,6 +6,11 @@ import FormHelperText from '@mui/material/FormHelperText'; import FormGroup, { FormGroupProps } from '@mui/material/FormGroup'; import Switch, { SwitchProps } from '@mui/material/Switch'; import { FieldTitle, useInput } from 'ra-core'; +import { + ComponentsOverrides, + styled, + useThemeProps, +} from '@mui/material/styles'; import { CommonInputProps } from './CommonInputProps'; import { sanitizeInputRestProps } from './sanitizeInputRestProps'; @@ -32,7 +37,10 @@ export const BooleanInput = (props: BooleanInputProps) => { options = defaultOptions, sx, ...rest - } = props; + } = useThemeProps({ + props: props, + name: PREFIX, + }); const { id, field, @@ -65,7 +73,7 @@ export const BooleanInput = (props: BooleanInputProps) => { const renderHelperText = helperText !== false || invalid; return ( - { /> ) : null} - + ); }; @@ -113,3 +121,29 @@ export type BooleanInputProps = CommonInputProps & }; const defaultOptions = {}; + +const PREFIX = 'RaBooleanInput'; + +const StyledFormGroup = styled(FormGroup, { + 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]; + }; + } +} diff --git a/packages/ra-ui-materialui/src/input/DatagridInput.spec.tsx b/packages/ra-ui-materialui/src/input/DatagridInput.spec.tsx new file mode 100644 index 00000000000..39a1cdc0174 --- /dev/null +++ b/packages/ra-ui-materialui/src/input/DatagridInput.spec.tsx @@ -0,0 +1,11 @@ +import * as React from 'react'; +import { render, screen } from '@testing-library/react'; + +import { Themed } from './DatagridInput.stories'; + +describe('', () => { + it('should be customized by a theme', async () => { + render(); + await screen.findByTestId('themed'); + }); +}); diff --git a/packages/ra-ui-materialui/src/input/DatagridInput.stories.tsx b/packages/ra-ui-materialui/src/input/DatagridInput.stories.tsx index 659734e4c5f..888b76127b3 100644 --- a/packages/ra-ui-materialui/src/input/DatagridInput.stories.tsx +++ b/packages/ra-ui-materialui/src/input/DatagridInput.stories.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { Admin } from 'react-admin'; import { Resource, TestMemoryRouter } from 'ra-core'; +import { createTheme } from '@mui/material/styles'; import { Edit } from '../detail'; import { SimpleForm } from '../form'; @@ -157,3 +158,30 @@ export const InsideReferenceInput = () => ( ); + +export const Themed = () => ( + + + + + +); diff --git a/packages/ra-ui-materialui/src/input/DatagridInput.tsx b/packages/ra-ui-materialui/src/input/DatagridInput.tsx index 078e3c73c10..b649fce16f1 100644 --- a/packages/ra-ui-materialui/src/input/DatagridInput.tsx +++ b/packages/ra-ui-materialui/src/input/DatagridInput.tsx @@ -8,13 +8,24 @@ import { useChoicesContext, useInput, } from 'ra-core'; +import { + ComponentsOverrides, + styled, + useThemeProps, +} from '@mui/material/styles'; + import { CommonInputProps } from './CommonInputProps'; import { InputHelperText } from './InputHelperText'; import { SupportCreateSuggestionOptions } from './useSupportCreateSuggestion'; -import { Datagrid, DatagridProps } from '../list/datagrid'; -import { FilterButton, FilterForm } from '../list/filter'; -import { FilterContext } from '../list/FilterContext'; +import { + Datagrid, + DatagridProps, + FilterButton, + FilterForm, + FilterContext, +} from '../list'; import { Pagination as DefaultPagination } from '../list/pagination'; +import { sanitizeInputRestProps } from './sanitizeInputRestProps'; const defaultPagination = ; @@ -49,7 +60,12 @@ const defaultPagination = ; * * ); */ -export const DatagridInput = (props: DatagridInputProps) => { +export const DatagridInput = (inProps: DatagridInputProps) => { + const props = useThemeProps({ + props: inProps, + name: PREFIX, + }); + const { choices, className, @@ -121,7 +137,7 @@ export const DatagridInput = (props: DatagridInputProps) => { ] ); return ( -
+ {/* @ts-ignore FIXME cannot find another way to fix this error: "Types of property 'isPending' are incompatible: Type 'boolean' is not assignable to type 'false'." */} {filters ? ( @@ -145,7 +161,7 @@ export const DatagridInput = (props: DatagridInputProps) => { ) : null} {!fieldState.error && !fetchError && ( <> - + {pagination !== false && pagination} )} @@ -153,7 +169,7 @@ export const DatagridInput = (props: DatagridInputProps) => { error={fieldState.error?.message || fetchError?.message} /> -
+ ); }; @@ -169,3 +185,29 @@ export type DatagridInputProps = Omit< filters?: ReactElement | ReactElement[]; pagination?: ReactElement | false; }; + +const PREFIX = 'RaDatagridInput'; + +const Root = styled('div', { + 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]; + }; + } +} diff --git a/packages/ra-ui-materialui/src/input/DateInput.spec.tsx b/packages/ra-ui-materialui/src/input/DateInput.spec.tsx index bb71d119ba3..ea3ff106224 100644 --- a/packages/ra-ui-materialui/src/input/DateInput.spec.tsx +++ b/packages/ra-ui-materialui/src/input/DateInput.spec.tsx @@ -14,6 +14,7 @@ import { ExternalChanges, ExternalChangesWithParse, Parse, + Themed, } from './DateInput.stories'; describe('', () => { @@ -319,4 +320,9 @@ describe('', () => { await screen.findByText('Required'); }); }); + + it('should be customized by a theme', async () => { + render(); + await screen.findByTestId('themed'); + }); }); diff --git a/packages/ra-ui-materialui/src/input/DateInput.stories.tsx b/packages/ra-ui-materialui/src/input/DateInput.stories.tsx index 86640668a6c..1b94491ff44 100644 --- a/packages/ra-ui-materialui/src/input/DateInput.stories.tsx +++ b/packages/ra-ui-materialui/src/input/DateInput.stories.tsx @@ -3,7 +3,8 @@ import polyglotI18nProvider from 'ra-i18n-polyglot'; import englishMessages from 'ra-language-english'; import { minValue, useRecordContext } from 'ra-core'; import { useFormContext, useWatch } from 'react-hook-form'; -import { Box, Button, Typography } from '@mui/material'; +import { Box, Button, createTheme, Typography } from '@mui/material'; +import { ThemeOptions } from '@mui/material/styles'; import get from 'lodash/get'; import { AdminContext } from '../AdminContext'; @@ -141,16 +142,52 @@ export const ExternalChangesWithParse = ({ ); +export const Themed = ({ + dateInputProps, + simpleFormProps, +}: { + dateInputProps?: Partial; + simpleFormProps?: Partial; +}) => ( + + + +); + const i18nProvider = polyglotI18nProvider(() => englishMessages); const Wrapper = ({ children, simpleFormProps, + theme = undefined, }: { children: React.ReactNode; simpleFormProps?: Partial; + theme?: ThemeOptions; }) => ( - + {children} diff --git a/packages/ra-ui-materialui/src/input/DateInput.tsx b/packages/ra-ui-materialui/src/input/DateInput.tsx index cd3e824632b..faac05a5e84 100644 --- a/packages/ra-ui-materialui/src/input/DateInput.tsx +++ b/packages/ra-ui-materialui/src/input/DateInput.tsx @@ -2,6 +2,11 @@ import * as React from 'react'; import clsx from 'clsx'; import TextField, { TextFieldProps } from '@mui/material/TextField'; import { useInput, FieldTitle, useEvent } from 'ra-core'; +import { + ComponentsOverrides, + styled, + useThemeProps, +} from '@mui/material/styles'; import { CommonInputProps } from './CommonInputProps'; import { sanitizeInputRestProps } from './sanitizeInputRestProps'; @@ -46,23 +51,28 @@ import { useForkRef } from '@mui/material'; * to convert the form value (which is always a date string) back to a Date object. * new Date(val)} /> */ -export const DateInput = ({ - className, - defaultValue, - format = defaultFormat, - label, - source, - resource, - helperText, - margin, - onChange, - onFocus, - validate, - variant, - disabled, - readOnly, - ...rest -}: DateInputProps) => { +export const DateInput = (props: DateInputProps) => { + const { + className, + defaultValue, + format = defaultFormat, + label, + source, + resource, + helperText, + margin, + onChange, + onFocus, + validate, + variant, + disabled, + readOnly, + ...rest + } = useThemeProps({ + props: props, + name: PREFIX, + }); + const { field, fieldState, id, isRequired } = useInput({ defaultValue, resource, @@ -179,7 +189,7 @@ export const DateInput = ({ const inputRef = useForkRef(ref, localInputRef); return ( - { // other values (e.g., localized date strings, timestamps) need to be converted to Dates first return convertDateToString(new Date(value)); }; + +const PREFIX = 'RaDateInput'; + +const StyledTextField = styled(TextField, { + 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]; + }; + } +} diff --git a/packages/ra-ui-materialui/src/input/DateTimeInput.spec.tsx b/packages/ra-ui-materialui/src/input/DateTimeInput.spec.tsx index f2d3640aa6c..e52eed31257 100644 --- a/packages/ra-ui-materialui/src/input/DateTimeInput.spec.tsx +++ b/packages/ra-ui-materialui/src/input/DateTimeInput.spec.tsx @@ -14,6 +14,7 @@ import { SaveButton } from '../button'; import { ExternalChanges, ExternalChangesWithParse, + Themed, } from './DateTimeInput.stories'; describe('', () => { @@ -328,4 +329,9 @@ describe('', () => { }); }); }); + + it('should be customized by a theme', async () => { + render(); + await screen.findByTestId('themed'); + }); }); diff --git a/packages/ra-ui-materialui/src/input/DateTimeInput.stories.tsx b/packages/ra-ui-materialui/src/input/DateTimeInput.stories.tsx index 2e660fc328a..16441805db7 100644 --- a/packages/ra-ui-materialui/src/input/DateTimeInput.stories.tsx +++ b/packages/ra-ui-materialui/src/input/DateTimeInput.stories.tsx @@ -3,7 +3,8 @@ import polyglotI18nProvider from 'ra-i18n-polyglot'; import englishMessages from 'ra-language-english'; import { useRecordContext } from 'ra-core'; import { useFormContext, useWatch } from 'react-hook-form'; -import { Box, Button, Typography } from '@mui/material'; +import { Box, Button, createTheme, Typography } from '@mui/material'; +import { ThemeOptions } from '@mui/material/styles'; import get from 'lodash/get'; import { AdminContext } from '../AdminContext'; @@ -96,16 +97,45 @@ export const AsDateObject = () => ( ); +export const Themed = () => ( + + + +); + const i18nProvider = polyglotI18nProvider(() => englishMessages); const Wrapper = ({ children, simpleFormProps, + theme, }: { children: React.ReactNode; simpleFormProps?: Omit; + theme: ThemeOptions; }) => ( - + {children} diff --git a/packages/ra-ui-materialui/src/input/DateTimeInput.tsx b/packages/ra-ui-materialui/src/input/DateTimeInput.tsx index de9d072409e..051fad68d52 100644 --- a/packages/ra-ui-materialui/src/input/DateTimeInput.tsx +++ b/packages/ra-ui-materialui/src/input/DateTimeInput.tsx @@ -2,6 +2,11 @@ import * as React from 'react'; import clsx from 'clsx'; import TextField, { TextFieldProps } from '@mui/material/TextField'; import { useInput, FieldTitle } from 'ra-core'; +import { + ComponentsOverrides, + styled, + useThemeProps, +} from '@mui/material/styles'; import { CommonInputProps } from './CommonInputProps'; import { sanitizeInputRestProps } from './sanitizeInputRestProps'; @@ -11,24 +16,29 @@ import { useForkRef } from '@mui/material'; /** * Input component for entering a date and a time with timezone, using the browser locale */ -export const DateTimeInput = ({ - className, - defaultValue, - format = formatDateTime, - label, - helperText, - margin, - onBlur, - onChange, - onFocus, - source, - resource, - validate, - variant, - disabled, - readOnly, - ...rest -}: DateTimeInputProps) => { +export const DateTimeInput = (props: DateTimeInputProps) => { + const { + className, + defaultValue, + format = formatDateTime, + label, + helperText, + margin, + onBlur, + onChange, + onFocus, + source, + resource, + validate, + variant, + disabled, + readOnly, + ...rest + } = useThemeProps({ + props: props, + name: PREFIX, + }); + const { field, fieldState, id, isRequired } = useInput({ defaultValue, onBlur, @@ -138,7 +148,7 @@ export const DateTimeInput = ({ const inputRef = useForkRef(ref, localInputRef); return ( - { return convertDateToString(new Date(value)); }; + +const PREFIX = 'RaDateTimeInput'; + +const StyledTextField = styled(TextField, { + 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]; + }; + } +} diff --git a/packages/ra-ui-materialui/src/input/NumberInput.spec.tsx b/packages/ra-ui-materialui/src/input/NumberInput.spec.tsx index ea2c78a336c..eb85b3f3994 100644 --- a/packages/ra-ui-materialui/src/input/NumberInput.spec.tsx +++ b/packages/ra-ui-materialui/src/input/NumberInput.spec.tsx @@ -9,6 +9,7 @@ import { AdminContext } from '../AdminContext'; import { SaveButton } from '../button'; import { SimpleForm, Toolbar } from '../form'; import { required, ResourceContextProvider } from 'ra-core'; +import { Themed } from './NumberInput.stories'; describe('', () => { const defaultProps = { @@ -672,4 +673,9 @@ describe('', () => { }); }); }); + + it('should be customized by a theme', async () => { + render(); + await screen.findByTestId('themed'); + }); }); diff --git a/packages/ra-ui-materialui/src/input/NumberInput.stories.tsx b/packages/ra-ui-materialui/src/input/NumberInput.stories.tsx index e62f4a8736e..0ea04faa15d 100644 --- a/packages/ra-ui-materialui/src/input/NumberInput.stories.tsx +++ b/packages/ra-ui-materialui/src/input/NumberInput.stories.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { required } from 'ra-core'; import { useFormState, useFormContext } from 'react-hook-form'; +import { createTheme } from '@mui/material/styles'; import { NumberInput } from './NumberInput'; import { AdminContext } from '../AdminContext'; @@ -11,8 +12,8 @@ import { TextInput } from './TextInput'; export default { title: 'ra-ui-materialui/input/NumberInput' }; -const Wrapper = ({ children }) => ( - +const Wrapper = ({ children, theme = undefined }) => ( + ( ); + +export const Themed = () => ( + + + + +); diff --git a/packages/ra-ui-materialui/src/input/NumberInput.tsx b/packages/ra-ui-materialui/src/input/NumberInput.tsx index 949dc799b8b..78952c2a96f 100644 --- a/packages/ra-ui-materialui/src/input/NumberInput.tsx +++ b/packages/ra-ui-materialui/src/input/NumberInput.tsx @@ -2,6 +2,11 @@ import * as React from 'react'; import clsx from 'clsx'; import TextField, { TextFieldProps } from '@mui/material/TextField'; import { useInput, FieldTitle } from 'ra-core'; +import { + ComponentsOverrides, + styled, + useThemeProps, +} from '@mui/material/styles'; import { CommonInputProps } from './CommonInputProps'; import { InputHelperText } from './InputHelperText'; @@ -18,29 +23,34 @@ import { sanitizeInputRestProps } from './sanitizeInputRestProps'; * * */ -export const NumberInput = ({ - className, - defaultValue = null, - format = convertNumberToString, - helperText, - label, - margin, - onChange, - onBlur, - onFocus, - parse, - resource, - source, - step = 'any', - min, - max, - validate, - variant, - inputProps: overrideInputProps, - disabled, - readOnly, - ...rest -}: NumberInputProps) => { +export const NumberInput = (props: NumberInputProps) => { + const { + className, + defaultValue = null, + format = convertNumberToString, + helperText, + label, + margin, + onChange, + onBlur, + onFocus, + parse, + resource, + source, + step = 'any', + min, + max, + validate, + variant, + inputProps: overrideInputProps, + disabled, + readOnly, + ...rest + } = useThemeProps({ + props: props, + name: PREFIX, + }); + const { field, fieldState: { error, invalid }, @@ -124,7 +134,7 @@ export const NumberInput = ({ const { ref, ...fieldWithoutRef } = field; return ( - { const convertNumberToString = value => value == null || isNaN(value) ? '' : value.toString(); + +const PREFIX = 'RaNumberInput'; + +const StyledTextField = styled(TextField, { + 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]; + }; + } +} diff --git a/packages/ra-ui-materialui/src/input/PasswordInput.spec.tsx b/packages/ra-ui-materialui/src/input/PasswordInput.spec.tsx new file mode 100644 index 00000000000..658772da728 --- /dev/null +++ b/packages/ra-ui-materialui/src/input/PasswordInput.spec.tsx @@ -0,0 +1,11 @@ +import * as React from 'react'; +import { render, screen } from '@testing-library/react'; + +import { Themed } from './PasswordInput.stories'; + +describe('', () => { + it('should be customized by a theme', async () => { + render(); + await screen.findByTestId('themed'); + }); +}); diff --git a/packages/ra-ui-materialui/src/input/PasswordInput.stories.tsx b/packages/ra-ui-materialui/src/input/PasswordInput.stories.tsx index ef998eb1557..dc3c6e711c8 100644 --- a/packages/ra-ui-materialui/src/input/PasswordInput.stories.tsx +++ b/packages/ra-ui-materialui/src/input/PasswordInput.stories.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import polyglotI18nProvider from 'ra-i18n-polyglot'; import englishMessages from 'ra-language-english'; +import { createTheme } from '@mui/material/styles'; import { AdminContext } from '../AdminContext'; import { Create } from '../detail'; @@ -35,10 +36,33 @@ export const ReadOnly = () => ( ); +export const Themed = () => ( + + + +); + const i18nProvider = polyglotI18nProvider(() => englishMessages); -const Wrapper = ({ children }) => ( - +const Wrapper = ({ children, theme = undefined }) => ( + {children} diff --git a/packages/ra-ui-materialui/src/input/PasswordInput.tsx b/packages/ra-ui-materialui/src/input/PasswordInput.tsx index 4caa084bb84..7535cec8fd5 100644 --- a/packages/ra-ui-materialui/src/input/PasswordInput.tsx +++ b/packages/ra-ui-materialui/src/input/PasswordInput.tsx @@ -4,11 +4,19 @@ import { useTranslate } from 'ra-core'; import { InputAdornment, IconButton } from '@mui/material'; import Visibility from '@mui/icons-material/Visibility'; import VisibilityOff from '@mui/icons-material/VisibilityOff'; +import { + ComponentsOverrides, + styled, + useThemeProps, +} from '@mui/material/styles'; import { TextInput, TextInputProps } from './TextInput'; export const PasswordInput = (props: PasswordInputProps) => { - const { initiallyVisible = false, ...rest } = props; + const { initiallyVisible = false, ...rest } = useThemeProps({ + props: props, + name: PREFIX, + }); const [visible, setVisible] = useState(initiallyVisible); const translate = useTranslate(); @@ -17,7 +25,7 @@ export const PasswordInput = (props: PasswordInputProps) => { }; return ( - { export interface PasswordInputProps extends TextInputProps { initiallyVisible?: boolean; } + +const PREFIX = 'RaPasswordInput'; + +const StyledTextInput = styled(TextInput, { + 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]; + }; + } +} diff --git a/packages/ra-ui-materialui/src/input/RadioButtonGroupInput.spec.tsx b/packages/ra-ui-materialui/src/input/RadioButtonGroupInput.spec.tsx index 1e6b3d0d1b7..408651685a0 100644 --- a/packages/ra-ui-materialui/src/input/RadioButtonGroupInput.spec.tsx +++ b/packages/ra-ui-materialui/src/input/RadioButtonGroupInput.spec.tsx @@ -13,6 +13,7 @@ import { SimpleForm } from '../form'; import { RadioButtonGroupInput } from './RadioButtonGroupInput'; import { InsideReferenceArrayInput, + Themed, TranslateChoice, } from './RadioButtonGroupInput.stories'; @@ -310,6 +311,12 @@ describe('', () => { expect(screen.queryByText('Mastercard')).not.toBeNull(); }); + it('should be customized by a theme', async () => { + render(); + const inputs = await screen.findAllByTestId('themed'); + expect(inputs).toHaveLength(3); + }); + describe('translateChoice', () => { it('should translate the choices by default', async () => { render(); diff --git a/packages/ra-ui-materialui/src/input/RadioButtonGroupInput.stories.tsx b/packages/ra-ui-materialui/src/input/RadioButtonGroupInput.stories.tsx index cce3f60a48a..09c483ce6fe 100644 --- a/packages/ra-ui-materialui/src/input/RadioButtonGroupInput.stories.tsx +++ b/packages/ra-ui-materialui/src/input/RadioButtonGroupInput.stories.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import polyglotI18nProvider from 'ra-i18n-polyglot'; import englishMessages from 'ra-language-english'; +import { createTheme } from '@mui/material/styles'; import { AdminContext } from '../AdminContext'; import { Create, Edit } from '../detail'; @@ -178,8 +179,12 @@ export const Id = () => ( ); const i18nProvider = polyglotI18nProvider(() => englishMessages); -const Wrapper = ({ children }) => ( - +const Wrapper = ({ children, theme = undefined }) => ( + {children} @@ -272,3 +277,24 @@ export const TranslateChoice = () => { ); }; + +export const Themed = () => ( + + + +); diff --git a/packages/ra-ui-materialui/src/input/RadioButtonGroupInputItem.tsx b/packages/ra-ui-materialui/src/input/RadioButtonGroupInputItem.tsx index 72761931f77..50d304740d9 100644 --- a/packages/ra-ui-materialui/src/input/RadioButtonGroupInputItem.tsx +++ b/packages/ra-ui-materialui/src/input/RadioButtonGroupInputItem.tsx @@ -1,15 +1,30 @@ import * as React from 'react'; import FormControlLabel from '@mui/material/FormControlLabel'; import Radio from '@mui/material/Radio'; -import { useChoices } from 'ra-core'; - -export const RadioButtonGroupInputItem = ({ - choice, - optionText, - optionValue, - source, - translateChoice, -}) => { +import { type ChoicesProps, useChoices } from 'ra-core'; +import { FormControlLabelProps } from '@mui/material'; +import { + ComponentsOverrides, + styled, + useThemeProps, +} from '@mui/material/styles'; +import { sanitizeInputRestProps } from './sanitizeInputRestProps'; + +export const RadioButtonGroupInputItem = ( + props: RadioButtonGroupInputItemProps +) => { + const { + choice, + optionText, + optionValue, + source, + translateChoice, + ...rest + } = useThemeProps({ + props: props, + name: PREFIX, + }); + const { getChoiceText, getChoiceValue } = useChoices({ optionText, optionValue, @@ -21,13 +36,47 @@ export const RadioButtonGroupInputItem = ({ const nodeId = `${source}_${value}`; return ( - } + {...sanitizeInputRestProps(rest)} /> ); }; export default RadioButtonGroupInputItem; + +export interface RadioButtonGroupInputItemProps + extends Omit, + Pick { + choice: any; + source: any; +} + +const PREFIX = 'RaRadioButtonGroupInputItem'; + +const StyledFormControlLabel = styled(FormControlLabel, { + 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]; + }; + } +} diff --git a/packages/ra-ui-materialui/src/input/TextInput.spec.tsx b/packages/ra-ui-materialui/src/input/TextInput.spec.tsx index 08820056915..b63c605b716 100644 --- a/packages/ra-ui-materialui/src/input/TextInput.spec.tsx +++ b/packages/ra-ui-materialui/src/input/TextInput.spec.tsx @@ -5,7 +5,7 @@ import { required, ResourceContextProvider, testDataProvider } from 'ra-core'; import { AdminContext } from '../AdminContext'; import { SimpleForm } from '../form'; import { TextInput } from './TextInput'; -import { ValueNull, Parse } from './TextInput.stories'; +import { ValueNull, Parse, Themed } from './TextInput.stories'; describe('', () => { const defaultProps = { @@ -245,4 +245,9 @@ describe('', () => { expect(container.querySelector(`label`)).toBeNull(); }); }); + + it('should be customized by a theme', async () => { + render(); + await screen.findByTestId('themed'); + }); }); diff --git a/packages/ra-ui-materialui/src/input/TextInput.stories.tsx b/packages/ra-ui-materialui/src/input/TextInput.stories.tsx index 1753abc722d..e5e99448eb0 100644 --- a/packages/ra-ui-materialui/src/input/TextInput.stories.tsx +++ b/packages/ra-ui-materialui/src/input/TextInput.stories.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { required, Resource } from 'ra-core'; import { useFormState, useFormContext } from 'react-hook-form'; +import { createTheme } from '@mui/material/styles'; import { TextInput } from './TextInput'; import { AdminContext } from '../AdminContext'; @@ -13,8 +14,8 @@ import { MemoryRouter } from 'react-router'; export default { title: 'ra-ui-materialui/input/TextInput' }; -const Wrapper = ({ children }) => ( - +const Wrapper = ({ children, theme = undefined }) => ( + ( ); + +export const Themed = () => ( + + + + +); diff --git a/packages/ra-ui-materialui/src/input/TextInput.tsx b/packages/ra-ui-materialui/src/input/TextInput.tsx index ccbd0e9fb8b..e3a2b4ce2e5 100644 --- a/packages/ra-ui-materialui/src/input/TextInput.tsx +++ b/packages/ra-ui-materialui/src/input/TextInput.tsx @@ -1,6 +1,11 @@ import * as React from 'react'; import clsx from 'clsx'; import { useInput, FieldTitle } from 'ra-core'; +import { + ComponentsOverrides, + styled, + useThemeProps, +} from '@mui/material/styles'; import { CommonInputProps } from './CommonInputProps'; import { @@ -37,7 +42,11 @@ export const TextInput = (props: TextInputProps) => { source, validate, ...rest - } = props; + } = useThemeProps({ + props: props, + name: PREFIX, + }); + const { field, fieldState: { error, invalid }, @@ -59,7 +68,7 @@ export const TextInput = (props: TextInputProps) => { const renderHelperText = helperText !== false || invalid; return ( - { export type TextInputProps = CommonInputProps & Omit; + +const PREFIX = 'RaTextInput'; + +const StyledResettableTextField = styled(ResettableTextField, { + 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]; + }; + } +} diff --git a/packages/ra-ui-materialui/src/input/TimeInput.spec.tsx b/packages/ra-ui-materialui/src/input/TimeInput.spec.tsx index 2df9478ee92..9568f163380 100644 --- a/packages/ra-ui-materialui/src/input/TimeInput.spec.tsx +++ b/packages/ra-ui-materialui/src/input/TimeInput.spec.tsx @@ -10,6 +10,7 @@ import { SimpleForm, Toolbar } from '../form'; import { TimeInput } from './TimeInput'; import { ArrayInput, SimpleFormIterator } from './ArrayInput'; import { SaveButton } from '../button'; +import { Themed } from './TimeInput.stories'; describe('', () => { const defaultProps = { @@ -274,4 +275,9 @@ describe('', () => { }); }); }); + + it('should be customized by a theme', async () => { + render(); + await screen.findByTestId('themed'); + }); }); diff --git a/packages/ra-ui-materialui/src/input/TimeInput.stories.tsx b/packages/ra-ui-materialui/src/input/TimeInput.stories.tsx index 6a73afc9614..45c56b46f2e 100644 --- a/packages/ra-ui-materialui/src/input/TimeInput.stories.tsx +++ b/packages/ra-ui-materialui/src/input/TimeInput.stories.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import polyglotI18nProvider from 'ra-i18n-polyglot'; import englishMessages from 'ra-language-english'; +import { createTheme } from '@mui/material/styles'; import { AdminContext } from '../AdminContext'; import { Create } from '../detail'; @@ -41,10 +42,37 @@ export const OutlinedNoLabel = () => ( ); +export const Themed = () => ( + + + +); + const i18nProvider = polyglotI18nProvider(() => englishMessages); -const Wrapper = ({ children }) => ( - +const Wrapper = ({ children, theme = undefined }) => ( + {children} diff --git a/packages/ra-ui-materialui/src/input/TimeInput.tsx b/packages/ra-ui-materialui/src/input/TimeInput.tsx index cf69444e004..846ea5ff7da 100644 --- a/packages/ra-ui-materialui/src/input/TimeInput.tsx +++ b/packages/ra-ui-materialui/src/input/TimeInput.tsx @@ -2,6 +2,11 @@ import * as React from 'react'; import clsx from 'clsx'; import TextField, { TextFieldProps } from '@mui/material/TextField'; import { useInput, FieldTitle } from 'ra-core'; +import { + ComponentsOverrides, + styled, + useThemeProps, +} from '@mui/material/styles'; import { CommonInputProps } from './CommonInputProps'; import { sanitizeInputRestProps } from './sanitizeInputRestProps'; @@ -43,24 +48,29 @@ const parseTime = (value: string) => { * * ); */ -export const TimeInput = ({ - className, - defaultValue, - format = formatTime, - label, - helperText, - margin, - onBlur, - onChange, - source, - resource, - disabled, - readOnly, - parse = parseTime, - validate, - variant, - ...rest -}: TimeInputProps) => { +export const TimeInput = (props: TimeInputProps) => { + const { + className, + defaultValue, + format = formatTime, + label, + helperText, + margin, + onBlur, + onChange, + source, + resource, + disabled, + readOnly, + parse = parseTime, + validate, + variant, + ...rest + } = useThemeProps({ + props: props, + name: PREFIX, + }); + const { field, fieldState, id, isRequired } = useInput({ defaultValue, format, @@ -80,7 +90,7 @@ export const TimeInput = ({ const renderHelperText = helperText !== false || invalid; return ( - { return convertDateToString(new Date(value)); }; + +const PREFIX = 'RaTimeInput'; + +const StyledTextField = styled(TextField, { + 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]; + }; + } +} diff --git a/packages/ra-ui-materialui/src/input/sanitizeInputRestProps.ts b/packages/ra-ui-materialui/src/input/sanitizeInputRestProps.ts index 14561eb19b4..4d843ed54ea 100644 --- a/packages/ra-ui-materialui/src/input/sanitizeInputRestProps.ts +++ b/packages/ra-ui-materialui/src/input/sanitizeInputRestProps.ts @@ -41,5 +41,6 @@ export const sanitizeInputRestProps = ({ validate, validateFields, value, + fullWidth, ...rest }: any) => rest; diff --git a/packages/ra-ui-materialui/src/list/List.stories.tsx b/packages/ra-ui-materialui/src/list/List.stories.tsx index 4bf5ba41d29..90189f61f78 100644 --- a/packages/ra-ui-materialui/src/list/List.stories.tsx +++ b/packages/ra-ui-materialui/src/list/List.stories.tsx @@ -30,7 +30,7 @@ import { ShowGuesser } from '../detail'; import TopToolbar from '../layout/TopToolbar'; import { BulkActionsToolbar } from './BulkActionsToolbar'; import { deepmerge } from '@mui/utils'; -import { defaultLightTheme, RaThemeOptions } from '../theme'; +import { defaultLightTheme } from '../theme'; export default { title: 'ra-ui-materialui/list/List' };