Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Select] Make error part of the ownerState to enable overriding styles with it in theme #36422

Merged
merged 17 commits into from Apr 3, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 3 additions & 2 deletions docs/pages/material-ui/api/native-select.json
Expand Up @@ -35,9 +35,10 @@
"iconFilled",
"iconOutlined",
"iconStandard",
"nativeInput"
"nativeInput",
"error"
],
"globalClasses": { "disabled": "Mui-disabled" },
"globalClasses": { "disabled": "Mui-disabled", "error": "Mui-error" },
"name": "MuiNativeSelect"
},
"spread": true,
Expand Down
6 changes: 4 additions & 2 deletions docs/pages/material-ui/api/select.json
Expand Up @@ -6,6 +6,7 @@
"defaultOpen": { "type": { "name": "bool" }, "default": "false" },
"defaultValue": { "type": { "name": "any" } },
"displayEmpty": { "type": { "name": "bool" }, "default": "false" },
"error": { "type": { "name": "bool" } },
"IconComponent": { "type": { "name": "elementType" }, "default": "ArrowDropDownIcon" },
"id": { "type": { "name": "string" } },
"input": { "type": { "name": "element" } },
Expand Down Expand Up @@ -50,9 +51,10 @@
"iconFilled",
"iconOutlined",
"iconStandard",
"nativeInput"
"nativeInput",
"error"
],
"globalClasses": { "disabled": "Mui-disabled" },
"globalClasses": { "disabled": "Mui-disabled", "error": "Mui-error" },
"name": "MuiSelect"
},
"spread": true,
Expand Down
4 changes: 4 additions & 0 deletions docs/translations/api-docs/native-select/native-select.json
Expand Up @@ -65,6 +65,10 @@
"nativeInput": {
"description": "Styles applied to {{nodeName}}.",
"nodeName": "the underlying native input component"
},
"error": {
"description": "State class applied to {{nodeName}}.",
"nodeName": "the select component `error` class"
}
}
}
6 changes: 6 additions & 0 deletions docs/translations/api-docs/select/select.json
Expand Up @@ -7,6 +7,7 @@
"defaultOpen": "If <code>true</code>, the component is initially open. Use when the component open state is not controlled (i.e. the <code>open</code> prop is not defined). You can only use it when the <code>native</code> prop is <code>false</code> (default).",
"defaultValue": "The default value. Use when the component is not controlled.",
"displayEmpty": "If <code>true</code>, a value is displayed even if no items are selected.<br>In order to display a meaningful value, a function can be passed to the <code>renderValue</code> prop which returns the value to be displayed when no items are selected.<br>⚠️ When using this prop, make sure the label doesn&#39;t overlap with the empty displayed value. The label should either be hidden or forced to a shrunk state.",
"error": "If <code>true</code>, the <code>input</code> will indicate an error. The prop defaults to the value (<code>false</code>) inherited from the parent FormControl component.",
"IconComponent": "The icon that displays the arrow.",
"id": "The <code>id</code> of the wrapper element or the <code>select</code> element when <code>native</code>.",
"input": "An <code>Input</code> element; does not have to be a material-ui specific <code>Input</code>.",
Expand Down Expand Up @@ -79,6 +80,11 @@
"nativeInput": {
"description": "Styles applied to {{nodeName}}.",
"nodeName": "the underlying native input component"
},
"error": {
"description": "State class applied to {{nodeName}} if {{conditions}}.",
"nodeName": "the root element",
"conditions": "<code>error={true}</code>"
}
}
}
Expand Up @@ -7,6 +7,7 @@ export interface NativeSelectInputProps extends React.SelectHTMLAttributes<HTMLS
IconComponent: React.ElementType;
inputRef?: React.Ref<HTMLSelectElement>;
variant?: 'standard' | 'outlined' | 'filled';
error?: boolean;
sx?: SxProps<Theme>;
}

Expand Down
21 changes: 18 additions & 3 deletions packages/mui-material/src/NativeSelect/NativeSelectInput.js
Expand Up @@ -8,10 +8,10 @@ import nativeSelectClasses, { getNativeSelectUtilityClasses } from './nativeSele
import styled, { rootShouldForwardProp } from '../styles/styled';

const useUtilityClasses = (ownerState) => {
const { classes, variant, disabled, multiple, open } = ownerState;
const { classes, variant, disabled, multiple, open, error } = ownerState;

const slots = {
select: ['select', variant, disabled && 'disabled', multiple && 'multiple'],
select: ['select', variant, disabled && 'disabled', multiple && 'multiple', error && 'error'],
icon: ['icon', `icon${capitalize(variant)}`, open && 'iconOpen', disabled && 'disabled'],
};

Expand Down Expand Up @@ -80,6 +80,7 @@ const NativeSelectSelect = styled('select', {
return [
styles.select,
styles[ownerState.variant],
styles[ownerState.error],
ZeeshanTamboli marked this conversation as resolved.
Show resolved Hide resolved
{ [`&.${nativeSelectClasses.multiple}`]: styles.multiple },
];
},
Expand Down Expand Up @@ -124,12 +125,21 @@ const NativeSelectIcon = styled('svg', {
* @ignore - internal component.
*/
const NativeSelectInput = React.forwardRef(function NativeSelectInput(props, ref) {
const { className, disabled, IconComponent, inputRef, variant = 'standard', ...other } = props;
const {
className,
disabled,
error,
IconComponent,
inputRef,
variant = 'standard',
...other
} = props;

const ownerState = {
...props,
disabled,
variant,
error,
};

const classes = useUtilityClasses(ownerState);
Expand Down Expand Up @@ -168,6 +178,11 @@ NativeSelectInput.propTypes = {
* If `true`, the select is disabled.
*/
disabled: PropTypes.bool,
/**
* If `true`, the `select input` will indicate an error.
* The prop defaults to the value (`false`).
ZeeshanTamboli marked this conversation as resolved.
Show resolved Hide resolved
*/
error: PropTypes.bool,
/**
* The icon that displays the arrow.
*/
Expand Down
39 changes: 39 additions & 0 deletions packages/mui-material/src/NativeSelect/NativeSelectInput.test.js
Expand Up @@ -111,4 +111,43 @@ describe('<NativeSelectInput />', () => {
).to.toHaveComputedStyle(combinedStyle);
});
});

it('it should override with error style when `select` has `error` state', function test() {
ZeeshanTamboli marked this conversation as resolved.
Show resolved Hide resolved
if (/jsdom/.test(window.navigator.userAgent)) {
this.skip();
}

const iconStyle = { color: 'rgb(255, 0, 0)' };
const selectStyle = { color: 'rgb(255, 192, 203)' };

const theme = createTheme({
components: {
MuiNativeSelect: {
styleOverrides: {
icon: (props) => ({
...(props.ownerState.error && iconStyle),
}),
select: (props) => ({
...(props.ownerState.error && selectStyle),
}),
},
},
},
});

const { container } = render(
<ThemeProvider theme={theme}>
<NativeSelectInput error IconComponent="div">
<option value={'first'}>First</option>
<option value={'second'}>Second</option>
</NativeSelectInput>
</ThemeProvider>,
);
expect(container.getElementsByClassName(nativeSelectClasses.select)[0]).to.toHaveComputedStyle(
selectStyle,
);
expect(container.getElementsByClassName(nativeSelectClasses.icon)[0]).to.toHaveComputedStyle(
iconStyle,
);
});
});
3 changes: 3 additions & 0 deletions packages/mui-material/src/NativeSelect/nativeSelectClasses.ts
Expand Up @@ -28,6 +28,8 @@ export interface NativeSelectClasses {
iconStandard: string;
/** Styles applied to the underlying native input component. */
nativeInput: string;
/** State class applied to the select component `error` class. */
error: string;
}

export type NativeSelectClassKey = keyof NativeSelectClasses;
Expand All @@ -50,6 +52,7 @@ const nativeSelectClasses: NativeSelectClasses = generateUtilityClasses('MuiNati
'iconOutlined',
'iconStandard',
'nativeInput',
'error',
]);

export default nativeSelectClasses;
9 changes: 8 additions & 1 deletion packages/mui-material/src/Select/Select.js
Expand Up @@ -41,6 +41,7 @@ const Select = React.forwardRef(function Select(inProps, ref) {
className,
defaultOpen = false,
displayEmpty = false,
error,
ZeeshanTamboli marked this conversation as resolved.
Show resolved Hide resolved
IconComponent = ArrowDropDownIcon,
id,
input,
Expand All @@ -65,7 +66,7 @@ const Select = React.forwardRef(function Select(inProps, ref) {
const fcs = formControlState({
props,
muiFormControl,
states: ['variant'],
states: ['variant', 'error'],
});

const variant = fcs.variant || variantProp;
Expand All @@ -91,6 +92,7 @@ const Select = React.forwardRef(function Select(inProps, ref) {
inputComponent,
inputProps: {
children,
error: fcs.error,
IconComponent,
variant,
type: undefined, // We render a select. We can ignore the type provided by the `Input`.
Expand Down Expand Up @@ -172,6 +174,11 @@ Select.propTypes /* remove-proptypes */ = {
* @default false
*/
displayEmpty: PropTypes.bool,
/**
* If `true`, the `input` will indicate an error.
* The prop defaults to the value (`false`) inherited from the parent FormControl component.
*/
error: PropTypes.bool,
/**
* The icon that displays the arrow.
* @default ArrowDropDownIcon
Expand Down
1 change: 1 addition & 0 deletions packages/mui-material/src/Select/SelectInput.d.ts
Expand Up @@ -17,6 +17,7 @@ export interface SelectInputProps<T = unknown> {
autoWidth: boolean;
defaultOpen?: boolean;
disabled?: boolean;
error?: boolean;
IconComponent?: React.ElementType;
inputRef?: (
ref: HTMLSelectElement | { node: HTMLInputElement; value: SelectInputProps<T>['value'] },
Expand Down
13 changes: 11 additions & 2 deletions packages/mui-material/src/Select/SelectInput.js
Expand Up @@ -27,6 +27,7 @@ const SelectSelect = styled('div', {
// Win specificity over the input base
{ [`&.${selectClasses.select}`]: styles.select },
{ [`&.${selectClasses.select}`]: styles[ownerState.variant] },
{ [`&.${selectClasses.error}`]: styles[ownerState.error] },
ZeeshanTamboli marked this conversation as resolved.
Show resolved Hide resolved
{ [`&.${selectClasses.multiple}`]: styles.multiple },
];
},
Expand Down Expand Up @@ -83,10 +84,10 @@ function isEmpty(display) {
}

const useUtilityClasses = (ownerState) => {
const { classes, variant, disabled, multiple, open } = ownerState;
const { classes, variant, disabled, multiple, open, error } = ownerState;

const slots = {
select: ['select', variant, disabled && 'disabled', multiple && 'multiple'],
select: ['select', variant, disabled && 'disabled', multiple && 'multiple', error && 'error'],
icon: ['icon', `icon${capitalize(variant)}`, open && 'iconOpen', disabled && 'disabled'],
nativeInput: ['nativeInput'],
};
Expand All @@ -109,6 +110,7 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) {
defaultValue,
disabled,
displayEmpty,
error = false,
IconComponent,
inputRef: inputRefProp,
labelId,
Expand Down Expand Up @@ -475,6 +477,7 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) {
variant,
value,
open,
error,
};

const classes = useUtilityClasses(ownerState);
Expand Down Expand Up @@ -510,6 +513,7 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) {
)}
</SelectSelect>
<SelectNativeInput
aria-invalid={error}
value={Array.isArray(value) ? value.join(',') : value}
name={name}
ref={inputRef}
Expand Down Expand Up @@ -606,6 +610,11 @@ SelectInput.propTypes = {
* If `true`, the selected item is displayed even if its value is empty.
*/
displayEmpty: PropTypes.bool,
/**
* If `true`, the `select input` will indicate an error.
* The prop defaults to the value (`false`).
*/
error: PropTypes.bool,
/**
* The icon that displays the arrow.
*/
Expand Down
3 changes: 3 additions & 0 deletions packages/mui-material/src/Select/selectClasses.ts
Expand Up @@ -26,6 +26,8 @@ export interface SelectClasses {
iconStandard: string;
/** Styles applied to the underlying native input component. */
nativeInput: string;
/** State class applied to the root element if `error={true}`. */
error: string;
}

export type SelectClassKey = keyof SelectClasses;
Expand All @@ -48,6 +50,7 @@ const selectClasses: SelectClasses = generateUtilityClasses('MuiSelect', [
'iconOutlined',
'iconStandard',
'nativeInput',
'error',
]);

export default selectClasses;