Skip to content

Commit f80fa27

Browse files
authored
Merge pull request #10772 from marmelab/customizable-inputs
Allow to theme all inputs
2 parents c693cf8 + 8af02ea commit f80fa27

29 files changed

+770
-121
lines changed

packages/ra-ui-materialui/src/input/BooleanInput.spec.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { ResourceContextProvider, testDataProvider } from 'ra-core';
55
import { AdminContext } from '../AdminContext';
66
import { SimpleForm } from '../form';
77
import { BooleanInput } from './BooleanInput';
8+
import { Themed } from './BooleanInput.stories';
89

910
describe('<BooleanInput />', () => {
1011
const defaultProps = {
@@ -185,4 +186,9 @@ describe('<BooleanInput />', () => {
185186
expect(screen.queryByText('ra.validation.error')).not.toBeNull();
186187
});
187188
});
189+
190+
it('should be customized by a theme', async () => {
191+
render(<Themed />);
192+
await screen.findByTestId('themed');
193+
});
188194
});

packages/ra-ui-materialui/src/input/BooleanInput.stories.tsx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import polyglotI18nProvider from 'ra-i18n-polyglot';
33
import englishMessages from 'ra-language-english';
44
import { useFormContext } from 'react-hook-form';
55
import FavoriteIcon from '@mui/icons-material/Favorite';
6+
import { createTheme } from '@mui/material/styles';
67

78
import { AdminContext } from '../AdminContext';
89
import { Create } from '../detail';
@@ -67,10 +68,11 @@ export const Dark = () => (
6768

6869
const i18nProvider = polyglotI18nProvider(() => englishMessages);
6970

70-
const Wrapper = ({ children, defaultTheme = 'light' }) => (
71+
const Wrapper = ({ children, defaultTheme = 'light', theme = undefined }) => (
7172
<AdminContext
7273
i18nProvider={i18nProvider}
7374
defaultTheme={defaultTheme as any}
75+
theme={theme}
7476
>
7577
<Create resource="posts">
7678
<SimpleForm>{children}</SimpleForm>
@@ -96,3 +98,24 @@ export const SetFocus = () => (
9698
</Create>
9799
</AdminContext>
98100
);
101+
102+
export const Themed = () => (
103+
<Wrapper
104+
theme={createTheme({
105+
components: {
106+
RaBooleanInput: {
107+
defaultProps: {
108+
'data-testid': 'themed',
109+
} as any,
110+
styleOverrides: {
111+
root: {
112+
color: 'red',
113+
},
114+
},
115+
},
116+
},
117+
})}
118+
>
119+
<BooleanInput source="published" />
120+
</Wrapper>
121+
);

packages/ra-ui-materialui/src/input/BooleanInput.tsx

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ import FormHelperText from '@mui/material/FormHelperText';
66
import FormGroup, { FormGroupProps } from '@mui/material/FormGroup';
77
import Switch, { SwitchProps } from '@mui/material/Switch';
88
import { FieldTitle, useInput } from 'ra-core';
9+
import {
10+
ComponentsOverrides,
11+
styled,
12+
useThemeProps,
13+
} from '@mui/material/styles';
914

1015
import { CommonInputProps } from './CommonInputProps';
1116
import { sanitizeInputRestProps } from './sanitizeInputRestProps';
@@ -32,7 +37,10 @@ export const BooleanInput = (props: BooleanInputProps) => {
3237
options = defaultOptions,
3338
sx,
3439
...rest
35-
} = props;
40+
} = useThemeProps({
41+
props: props,
42+
name: PREFIX,
43+
});
3644
const {
3745
id,
3846
field,
@@ -65,7 +73,7 @@ export const BooleanInput = (props: BooleanInputProps) => {
6573
const renderHelperText = helperText !== false || invalid;
6674

6775
return (
68-
<FormGroup
76+
<StyledFormGroup
6977
className={clsx('ra-input', `ra-input-${source}`, className)}
7078
row={row}
7179
sx={sx}
@@ -102,7 +110,7 @@ export const BooleanInput = (props: BooleanInputProps) => {
102110
/>
103111
</FormHelperText>
104112
) : null}
105-
</FormGroup>
113+
</StyledFormGroup>
106114
);
107115
};
108116

@@ -113,3 +121,29 @@ export type BooleanInputProps = CommonInputProps &
113121
};
114122

115123
const defaultOptions = {};
124+
125+
const PREFIX = 'RaBooleanInput';
126+
127+
const StyledFormGroup = styled(FormGroup, {
128+
name: PREFIX,
129+
overridesResolver: (props, styles) => styles.root,
130+
})({});
131+
132+
declare module '@mui/material/styles' {
133+
interface ComponentNameToClassKey {
134+
[PREFIX]: 'root';
135+
}
136+
137+
interface ComponentsPropsList {
138+
[PREFIX]: Partial<BooleanInputProps>;
139+
}
140+
141+
interface Components {
142+
[PREFIX]?: {
143+
defaultProps?: ComponentsPropsList[typeof PREFIX];
144+
styleOverrides?: ComponentsOverrides<
145+
Omit<Theme, 'components'>
146+
>[typeof PREFIX];
147+
};
148+
}
149+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import * as React from 'react';
2+
import { render, screen } from '@testing-library/react';
3+
4+
import { Themed } from './DatagridInput.stories';
5+
6+
describe('<DatagridInput />', () => {
7+
it('should be customized by a theme', async () => {
8+
render(<Themed />);
9+
await screen.findByTestId('themed');
10+
});
11+
});

packages/ra-ui-materialui/src/input/DatagridInput.stories.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as React from 'react';
22
import { Admin } from 'react-admin';
33
import { Resource, TestMemoryRouter } from 'ra-core';
4+
import { createTheme } from '@mui/material/styles';
45

56
import { Edit } from '../detail';
67
import { SimpleForm } from '../form';
@@ -157,3 +158,30 @@ export const InsideReferenceInput = () => (
157158
</Admin>
158159
</TestMemoryRouter>
159160
);
161+
162+
export const Themed = () => (
163+
<TestMemoryRouter initialEntries={['/books/1']}>
164+
<Admin
165+
dataProvider={dataProvider}
166+
theme={createTheme({
167+
components: {
168+
RaDatagridInput: {
169+
defaultProps: {
170+
'data-testid': 'themed',
171+
} as any,
172+
styleOverrides: {
173+
root: {
174+
['& .MuiTypography-root']: {
175+
color: 'red',
176+
fontWeight: 'bold',
177+
},
178+
},
179+
},
180+
},
181+
},
182+
})}
183+
>
184+
<Resource name="books" edit={BookEdit} />
185+
</Admin>
186+
</TestMemoryRouter>
187+
);

packages/ra-ui-materialui/src/input/DatagridInput.tsx

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,24 @@ import {
88
useChoicesContext,
99
useInput,
1010
} from 'ra-core';
11+
import {
12+
ComponentsOverrides,
13+
styled,
14+
useThemeProps,
15+
} from '@mui/material/styles';
16+
1117
import { CommonInputProps } from './CommonInputProps';
1218
import { InputHelperText } from './InputHelperText';
1319
import { SupportCreateSuggestionOptions } from './useSupportCreateSuggestion';
14-
import { Datagrid, DatagridProps } from '../list/datagrid';
15-
import { FilterButton, FilterForm } from '../list/filter';
16-
import { FilterContext } from '../list/FilterContext';
20+
import {
21+
Datagrid,
22+
DatagridProps,
23+
FilterButton,
24+
FilterForm,
25+
FilterContext,
26+
} from '../list';
1727
import { Pagination as DefaultPagination } from '../list/pagination';
28+
import { sanitizeInputRestProps } from './sanitizeInputRestProps';
1829

1930
const defaultPagination = <DefaultPagination />;
2031

@@ -49,7 +60,12 @@ const defaultPagination = <DefaultPagination />;
4960
* </Edit>
5061
* );
5162
*/
52-
export const DatagridInput = (props: DatagridInputProps) => {
63+
export const DatagridInput = (inProps: DatagridInputProps) => {
64+
const props = useThemeProps({
65+
props: inProps,
66+
name: PREFIX,
67+
});
68+
5369
const {
5470
choices,
5571
className,
@@ -121,7 +137,7 @@ export const DatagridInput = (props: DatagridInputProps) => {
121137
]
122138
);
123139
return (
124-
<div className={clsx('ra-input', `ra-input-${source}`, className)}>
140+
<Root className={clsx('ra-input', `ra-input-${source}`, className)}>
125141
{/* @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'." */}
126142
<ListContextProvider value={listContext}>
127143
{filters ? (
@@ -145,15 +161,15 @@ export const DatagridInput = (props: DatagridInputProps) => {
145161
) : null}
146162
{!fieldState.error && !fetchError && (
147163
<>
148-
<Datagrid {...rest} />
164+
<Datagrid {...sanitizeInputRestProps(rest)} />
149165
{pagination !== false && pagination}
150166
</>
151167
)}
152168
<InputHelperText
153169
error={fieldState.error?.message || fetchError?.message}
154170
/>
155171
</ListContextProvider>
156-
</div>
172+
</Root>
157173
);
158174
};
159175

@@ -169,3 +185,29 @@ export type DatagridInputProps = Omit<
169185
filters?: ReactElement | ReactElement[];
170186
pagination?: ReactElement | false;
171187
};
188+
189+
const PREFIX = 'RaDatagridInput';
190+
191+
const Root = styled('div', {
192+
name: PREFIX,
193+
overridesResolver: (props, styles) => styles.root,
194+
})({});
195+
196+
declare module '@mui/material/styles' {
197+
interface ComponentNameToClassKey {
198+
[PREFIX]: 'root';
199+
}
200+
201+
interface ComponentsPropsList {
202+
[PREFIX]: Partial<DatagridInputProps>;
203+
}
204+
205+
interface Components {
206+
[PREFIX]?: {
207+
defaultProps?: ComponentsPropsList[typeof PREFIX];
208+
styleOverrides?: ComponentsOverrides<
209+
Omit<Theme, 'components'>
210+
>[typeof PREFIX];
211+
};
212+
}
213+
}

packages/ra-ui-materialui/src/input/DateInput.spec.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
ExternalChanges,
1515
ExternalChangesWithParse,
1616
Parse,
17+
Themed,
1718
} from './DateInput.stories';
1819

1920
describe('<DateInput />', () => {
@@ -319,4 +320,9 @@ describe('<DateInput />', () => {
319320
await screen.findByText('Required');
320321
});
321322
});
323+
324+
it('should be customized by a theme', async () => {
325+
render(<Themed />);
326+
await screen.findByTestId('themed');
327+
});
322328
});

packages/ra-ui-materialui/src/input/DateInput.stories.tsx

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import polyglotI18nProvider from 'ra-i18n-polyglot';
33
import englishMessages from 'ra-language-english';
44
import { minValue, useRecordContext } from 'ra-core';
55
import { useFormContext, useWatch } from 'react-hook-form';
6-
import { Box, Button, Typography } from '@mui/material';
6+
import { Box, Button, createTheme, Typography } from '@mui/material';
7+
import { ThemeOptions } from '@mui/material/styles';
78
import get from 'lodash/get';
89

910
import { AdminContext } from '../AdminContext';
@@ -141,16 +142,52 @@ export const ExternalChangesWithParse = ({
141142
</Wrapper>
142143
);
143144

145+
export const Themed = ({
146+
dateInputProps,
147+
simpleFormProps,
148+
}: {
149+
dateInputProps?: Partial<DateInputProps>;
150+
simpleFormProps?: Partial<SimpleFormProps>;
151+
}) => (
152+
<Wrapper
153+
simpleFormProps={simpleFormProps}
154+
theme={createTheme({
155+
components: {
156+
RaDateInput: {
157+
defaultProps: {
158+
'data-testid': 'themed',
159+
} as any,
160+
styleOverrides: {
161+
root: {
162+
['& input']: {
163+
color: 'red',
164+
},
165+
},
166+
},
167+
},
168+
},
169+
})}
170+
>
171+
<DateInput source="publishedAt" {...dateInputProps} />
172+
</Wrapper>
173+
);
174+
144175
const i18nProvider = polyglotI18nProvider(() => englishMessages);
145176

146177
const Wrapper = ({
147178
children,
148179
simpleFormProps,
180+
theme = undefined,
149181
}: {
150182
children: React.ReactNode;
151183
simpleFormProps?: Partial<SimpleFormProps>;
184+
theme?: ThemeOptions;
152185
}) => (
153-
<AdminContext i18nProvider={i18nProvider} defaultTheme="light">
186+
<AdminContext
187+
i18nProvider={i18nProvider}
188+
defaultTheme="light"
189+
theme={theme}
190+
>
154191
<Create resource="posts">
155192
<SimpleForm {...simpleFormProps}>
156193
{children}

0 commit comments

Comments
 (0)