diff --git a/docs/pages/material-ui/api/native-select.json b/docs/pages/material-ui/api/native-select.json
index 98b7363fbb70b1..d90e75525a6913 100644
--- a/docs/pages/material-ui/api/native-select.json
+++ b/docs/pages/material-ui/api/native-select.json
@@ -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,
diff --git a/docs/pages/material-ui/api/select.json b/docs/pages/material-ui/api/select.json
index dfc27fce6b09ee..75511f6656161f 100644
--- a/docs/pages/material-ui/api/select.json
+++ b/docs/pages/material-ui/api/select.json
@@ -50,9 +50,10 @@
"iconFilled",
"iconOutlined",
"iconStandard",
- "nativeInput"
+ "nativeInput",
+ "error"
],
- "globalClasses": { "disabled": "Mui-disabled" },
+ "globalClasses": { "disabled": "Mui-disabled", "error": "Mui-error" },
"name": "MuiSelect"
},
"spread": true,
diff --git a/docs/translations/api-docs/native-select/native-select.json b/docs/translations/api-docs/native-select/native-select.json
index 52a6b0e4a5e45c..7e7314bfa92aa0 100644
--- a/docs/translations/api-docs/native-select/native-select.json
+++ b/docs/translations/api-docs/native-select/native-select.json
@@ -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"
}
}
}
diff --git a/docs/translations/api-docs/select/select.json b/docs/translations/api-docs/select/select.json
index 9375b66b2b851a..d821115dcd5e19 100644
--- a/docs/translations/api-docs/select/select.json
+++ b/docs/translations/api-docs/select/select.json
@@ -79,6 +79,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": "error={true}
"
}
}
}
diff --git a/packages/mui-material/src/NativeSelect/NativeSelectInput.d.ts b/packages/mui-material/src/NativeSelect/NativeSelectInput.d.ts
index 4dd9d0fc05d670..bca7299cdce3cd 100644
--- a/packages/mui-material/src/NativeSelect/NativeSelectInput.d.ts
+++ b/packages/mui-material/src/NativeSelect/NativeSelectInput.d.ts
@@ -7,6 +7,7 @@ export interface NativeSelectInputProps extends React.SelectHTMLAttributes;
variant?: 'standard' | 'outlined' | 'filled';
+ error?: boolean;
sx?: SxProps;
}
diff --git a/packages/mui-material/src/NativeSelect/NativeSelectInput.js b/packages/mui-material/src/NativeSelect/NativeSelectInput.js
index 95bdcdbae34b77..b9ff1c48cbc182 100644
--- a/packages/mui-material/src/NativeSelect/NativeSelectInput.js
+++ b/packages/mui-material/src/NativeSelect/NativeSelectInput.js
@@ -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'],
};
@@ -80,6 +80,7 @@ const NativeSelectSelect = styled('select', {
return [
styles.select,
styles[ownerState.variant],
+ ownerState.error && styles.error,
{ [`&.${nativeSelectClasses.multiple}`]: styles.multiple },
];
},
@@ -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);
@@ -168,6 +178,10 @@ NativeSelectInput.propTypes = {
* If `true`, the select is disabled.
*/
disabled: PropTypes.bool,
+ /**
+ * If `true`, the `select input` will indicate an error.
+ */
+ error: PropTypes.bool,
/**
* The icon that displays the arrow.
*/
diff --git a/packages/mui-material/src/NativeSelect/NativeSelectInput.test.js b/packages/mui-material/src/NativeSelect/NativeSelectInput.test.js
index fe3d3b2e3497c6..28e4a4867f5a0c 100644
--- a/packages/mui-material/src/NativeSelect/NativeSelectInput.test.js
+++ b/packages/mui-material/src/NativeSelect/NativeSelectInput.test.js
@@ -111,4 +111,45 @@ describe('', () => {
).to.toHaveComputedStyle(combinedStyle);
});
});
+
+ describe('theme styleOverrides:', () => {
+ it('should override with error style when `select` has `error` state', function test() {
+ 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(
+
+
+
+
+
+ ,
+ );
+ expect(container.querySelector(`.${nativeSelectClasses.select}`)).toHaveComputedStyle(
+ selectStyle,
+ );
+ expect(container.querySelector(`.${nativeSelectClasses.icon}`)).toHaveComputedStyle(
+ iconStyle,
+ );
+ });
+ });
});
diff --git a/packages/mui-material/src/NativeSelect/nativeSelectClasses.ts b/packages/mui-material/src/NativeSelect/nativeSelectClasses.ts
index 7dae35c59ad08b..4d387b0cb9826e 100644
--- a/packages/mui-material/src/NativeSelect/nativeSelectClasses.ts
+++ b/packages/mui-material/src/NativeSelect/nativeSelectClasses.ts
@@ -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;
@@ -50,6 +52,7 @@ const nativeSelectClasses: NativeSelectClasses = generateUtilityClasses('MuiNati
'iconOutlined',
'iconStandard',
'nativeInput',
+ 'error',
]);
export default nativeSelectClasses;
diff --git a/packages/mui-material/src/Select/Select.js b/packages/mui-material/src/Select/Select.js
index 75cd04eac98857..deff5dda5dd855 100644
--- a/packages/mui-material/src/Select/Select.js
+++ b/packages/mui-material/src/Select/Select.js
@@ -65,22 +65,22 @@ const Select = React.forwardRef(function Select(inProps, ref) {
const fcs = formControlState({
props,
muiFormControl,
- states: ['variant'],
+ states: ['variant', 'error'],
});
const variant = fcs.variant || variantProp;
+ const ownerState = { ...props, variant, classes: classesProp };
+ const classes = useUtilityClasses(ownerState);
+
const InputComponent =
input ||
{
- standard: ,
- outlined: ,
- filled: ,
+ standard: ,
+ outlined: ,
+ filled: ,
}[variant];
- const ownerState = { ...props, variant, classes: classesProp };
- const classes = useUtilityClasses(ownerState);
-
const inputComponentRef = useForkRef(ref, InputComponent.ref);
return (
@@ -91,6 +91,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`.
diff --git a/packages/mui-material/src/Select/Select.test.js b/packages/mui-material/src/Select/Select.test.js
index 1f9dd561466348..5f27e00467ffde 100644
--- a/packages/mui-material/src/Select/Select.test.js
+++ b/packages/mui-material/src/Select/Select.test.js
@@ -18,6 +18,7 @@ import InputLabel from '@mui/material/InputLabel';
import Select from '@mui/material/Select';
import Divider from '@mui/material/Divider';
import classes from './selectClasses';
+import { nativeSelectClasses } from '../NativeSelect';
describe('', () => {
const { clock, render } = createRenderer({ clock: 'fake' });
@@ -1439,6 +1440,72 @@ describe('', () => {
expect(container.getElementsByClassName(classes.select)[0]).to.toHaveComputedStyle(selectStyle);
});
+ describe('theme styleOverrides:', () => {
+ it('should override with error style when `native select` has `error` state', function test() {
+ if (/jsdom/.test(window.navigator.userAgent)) {
+ this.skip();
+ }
+
+ const iconStyle = { color: 'rgb(255, 0, 0)' };
+
+ const theme = createTheme({
+ components: {
+ MuiNativeSelect: {
+ styleOverrides: {
+ icon: (props) => ({
+ ...(props.ownerState.error && iconStyle),
+ }),
+ },
+ },
+ },
+ });
+
+ const { container } = render(
+
+
+ ,
+ );
+
+ expect(container.querySelector(`.${nativeSelectClasses.icon}`)).toHaveComputedStyle(
+ iconStyle,
+ );
+ });
+
+ it('should override with error style when `select` has `error` state', function test() {
+ 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: {
+ MuiSelect: {
+ styleOverrides: {
+ icon: (props) => ({
+ ...(props.ownerState.error && iconStyle),
+ }),
+ select: (props) => ({
+ ...(props.ownerState.error && selectStyle),
+ }),
+ },
+ },
+ },
+ });
+
+ const { container } = render(
+
+
+ ,
+ );
+ expect(container.querySelector(`.${classes.select}`)).toHaveComputedStyle(selectStyle);
+ expect(container.querySelector(`.${classes.icon}`)).toHaveComputedStyle(iconStyle);
+ });
+ });
+
['standard', 'outlined', 'filled'].forEach((variant) => {
it(`variant overrides should work for "${variant}" variant`, function test() {
const theme = createTheme({
diff --git a/packages/mui-material/src/Select/SelectInput.d.ts b/packages/mui-material/src/Select/SelectInput.d.ts
index 43e29c9212e238..ab2bfb4e70fe02 100644
--- a/packages/mui-material/src/Select/SelectInput.d.ts
+++ b/packages/mui-material/src/Select/SelectInput.d.ts
@@ -17,6 +17,7 @@ export interface SelectInputProps {
autoWidth: boolean;
defaultOpen?: boolean;
disabled?: boolean;
+ error?: boolean;
IconComponent?: React.ElementType;
inputRef?: (
ref: HTMLSelectElement | { node: HTMLInputElement; value: SelectInputProps['value'] },
diff --git a/packages/mui-material/src/Select/SelectInput.js b/packages/mui-material/src/Select/SelectInput.js
index 2bb926fd16f3e0..f7d90667cc2ac0 100644
--- a/packages/mui-material/src/Select/SelectInput.js
+++ b/packages/mui-material/src/Select/SelectInput.js
@@ -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.error },
{ [`&.${selectClasses.multiple}`]: styles.multiple },
];
},
@@ -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'],
};
@@ -109,6 +110,7 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) {
defaultValue,
disabled,
displayEmpty,
+ error = false,
IconComponent,
inputRef: inputRefProp,
labelId,
@@ -475,6 +477,7 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) {
variant,
value,
open,
+ error,
};
const classes = useUtilityClasses(ownerState);
@@ -510,6 +513,7 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) {
)}