diff --git a/docs/pages/experiments/md3/inputs.tsx b/docs/pages/experiments/md3/inputs.tsx
index 5a80eb6e60d349..eb9b76b7d7b349 100644
--- a/docs/pages/experiments/md3/inputs.tsx
+++ b/docs/pages/experiments/md3/inputs.tsx
@@ -1,5 +1,6 @@
import * as React from 'react';
import Stack from '@mui/material/Stack';
+import Divider from '@mui/material/Divider';
import Md2FilledInput from '@mui/material/FilledInput';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import FilledInput from '@mui/material-next/FilledInput';
@@ -14,16 +15,22 @@ export default function MaterialYouInputs() {
return (
+ MD2
-
-
+
+
+
-
+
+ MD3
+
+
-
-
+
+
+
diff --git a/packages/mui-material-next/migration.md b/packages/mui-material-next/migration.md
index 724be901347fe4..4b1620a9ebd768 100644
--- a/packages/mui-material-next/migration.md
+++ b/packages/mui-material-next/migration.md
@@ -140,6 +140,19 @@ If you need to prevent default on a `key-up` and/or `key-down` event, then besid
This is to ensure that default is prevented when the `ButtonBase` root is not a native button, for example, when the root element used is a `span`.
+## FilledInput
+
+### Removed `inputProps`
+
+`inputProps` are removed in favor of `slotProps.input`:
+
+```diff
+
+```
+
## FormControl
### Renamed `FormControlState`
diff --git a/packages/mui-material-next/src/FilledInput/FilledInput.d.ts b/packages/mui-material-next/src/FilledInput/FilledInput.d.ts
deleted file mode 100644
index 74e6dd93fccf9a..00000000000000
--- a/packages/mui-material-next/src/FilledInput/FilledInput.d.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import { SxProps } from '@mui/system';
-import { InternalStandardProps as StandardProps, Theme } from '@mui/material';
-import { InputBaseProps } from '@mui/material/InputBase';
-import { FilledInputClasses } from './filledInputClasses';
-
-export interface FilledInputProps extends StandardProps {
- /**
- * Override or extend the styles applied to the component.
- */
- classes?: Partial;
- /**
- * If `true`, the label is hidden.
- * This is used to increase density for a `FilledInput`.
- * Be sure to add `aria-label` to the `input` element.
- * @default false
- */
- hiddenLabel?: boolean;
- /**
- * If `true`, the input will not have an underline.
- */
- disableUnderline?: boolean;
- /**
- * The system prop that allows defining system overrides as well as additional CSS styles.
- */
- sx?: SxProps;
-}
-
-/**
- *
- * Demos:
- *
- * - [Text Field](https://mui.com/material-ui/react-text-field/)
- *
- * API:
- *
- * - [FilledInput API](https://mui.com/material-ui/api/filled-input/)
- * - inherits [InputBase API](https://mui.com/material-ui/api/input-base/)
- */
-declare const FilledInput: ((props: FilledInputProps) => JSX.Element) & { muiName: string };
-
-export default FilledInput;
diff --git a/packages/mui-material-next/src/FilledInput/FilledInput.test.js b/packages/mui-material-next/src/FilledInput/FilledInput.test.tsx
similarity index 65%
rename from packages/mui-material-next/src/FilledInput/FilledInput.test.js
rename to packages/mui-material-next/src/FilledInput/FilledInput.test.tsx
index e7ce5fde0068d7..b989695090cdb3 100644
--- a/packages/mui-material-next/src/FilledInput/FilledInput.test.js
+++ b/packages/mui-material-next/src/FilledInput/FilledInput.test.tsx
@@ -1,13 +1,16 @@
import * as React from 'react';
import { expect } from 'chai';
import { createRenderer, describeConformance } from '@mui-internal/test-utils';
-import FilledInput, { filledInputClasses as classes } from '@mui/material/FilledInput';
-import InputBase from '@mui/material/InputBase';
+import { CssVarsProvider, extendTheme } from '@mui/material-next/styles';
+import FilledInput, { filledInputClasses as classes } from '@mui/material-next/FilledInput';
+import InputBase from '@mui/material-next/InputBase';
describe('', () => {
const { render } = createRenderer();
describeConformance(, () => ({
+ ThemeProvider: CssVarsProvider,
+ createTheme: extendTheme,
classes,
inheritComponent: InputBase,
render,
@@ -16,11 +19,9 @@ describe('', () => {
testDeepOverrides: { slotName: 'input', slotClassName: classes.input },
testVariantProps: { variant: 'contained', fullWidth: true },
testStateOverrides: { prop: 'size', value: 'small', styleKey: 'sizeSmall' },
- testLegacyComponentsProp: true,
slots: {
- // can't test with DOM element as Input places an ownerState prop on it unconditionally.
- root: { expectedClassName: classes.root, testWithElement: null },
- input: { expectedClassName: classes.input, testWithElement: null },
+ root: { expectedClassName: classes.root },
+ input: { expectedClassName: classes.input, testWithElement: 'input' },
},
skip: [
'componentProp',
@@ -30,9 +31,10 @@ describe('', () => {
}));
it('should have the underline class', () => {
- const { container } = render();
- const root = container.firstChild;
+ const { getByTestId } = render();
+ const root = getByTestId('test-input');
expect(root).not.to.equal(null);
+ expect(root).to.have.class(classes.underline);
});
it('color={undefined} should not result in crash', () => {
@@ -42,8 +44,8 @@ describe('', () => {
});
it('can disable the underline', () => {
- const { container } = render();
- const root = container.firstChild;
+ const { getByTestId } = render();
+ const root = getByTestId('test-input');
expect(root).not.to.have.class(classes.underline);
});
@@ -52,19 +54,15 @@ describe('', () => {
expect(document.querySelector('.error')).not.to.equal(null);
});
- it('should respects the componentsProps if passed', () => {
- render();
- expect(document.querySelector('[data-test=test]')).not.to.equal(null);
- });
-
it('should respect the classes coming from InputBase', () => {
- render(
+ const { getByTestId } = render(
,
);
- expect(document.querySelector('[data-test=test]')).toHaveComputedStyle({ marginTop: '10px' });
+ const root = getByTestId('test-input');
+ expect(root).toHaveComputedStyle({ marginTop: '10px' });
});
});
diff --git a/packages/mui-material-next/src/FilledInput/FilledInput.js b/packages/mui-material-next/src/FilledInput/FilledInput.tsx
similarity index 54%
rename from packages/mui-material-next/src/FilledInput/FilledInput.js
rename to packages/mui-material-next/src/FilledInput/FilledInput.tsx
index 1c4c18c2fbaa59..8c89dc2bc7c9e4 100644
--- a/packages/mui-material-next/src/FilledInput/FilledInput.js
+++ b/packages/mui-material-next/src/FilledInput/FilledInput.tsx
@@ -1,20 +1,23 @@
'use client';
import * as React from 'react';
-import { refType, deepmerge } from '@mui/utils';
+import { refType } from '@mui/utils';
import PropTypes from 'prop-types';
-import { unstable_composeClasses as composeClasses } from '@mui/base/composeClasses';
-// TODO v6: use material-next/InputBase
+import { unstable_composeClasses as composeClasses, useSlotProps } from '@mui/base';
+import { DefaultComponentProps, OverrideProps } from '@mui/types';
+import { useThemeProps, styled } from '../styles';
+import { rootShouldForwardProp } from '../styles/styled';
import {
+ InputBaseRoot,
+ InputBaseInput,
rootOverridesResolver as inputBaseRootOverridesResolver,
inputOverridesResolver as inputBaseInputOverridesResolver,
-} from '@mui/material/InputBase/InputBase';
+} from '../InputBase/InputBase';
import InputBase from '../InputBase';
-import styled, { rootShouldForwardProp } from '../styles/styled';
-import useThemeProps from '../styles/useThemeProps';
+import { InputBaseOwnerState } from '../InputBase/InputBase.types';
import filledInputClasses, { getFilledInputUtilityClass } from './filledInputClasses';
-import { InputBaseRoot, InputBaseInput } from '../InputBase/InputBase';
+import { FilledInputOwnerState, FilledInputProps, FilledInputTypeMap } from './FilledInput.types';
-const useUtilityClasses = (ownerState) => {
+const useUtilityClasses = (ownerState: FilledInputOwnerState) => {
const { classes, disableUnderline } = ownerState;
const slots = {
@@ -36,44 +39,55 @@ const FilledInputRoot = styled(InputBaseRoot, {
slot: 'Root',
overridesResolver: (props, styles) => {
const { ownerState } = props;
+
return [
...inputBaseRootOverridesResolver(props, styles),
!ownerState.disableUnderline && styles.underline,
];
},
-})(({ theme, ownerState }) => {
- const light = theme.palette.mode === 'light';
- const bottomLineColor = light ? 'rgba(0, 0, 0, 0.42)' : 'rgba(255, 255, 255, 0.7)';
- const backgroundColor = light ? 'rgba(0, 0, 0, 0.06)' : 'rgba(255, 255, 255, 0.09)';
- const hoverBackground = light ? 'rgba(0, 0, 0, 0.09)' : 'rgba(255, 255, 255, 0.13)';
- const disabledBackground = light ? 'rgba(0, 0, 0, 0.12)' : 'rgba(255, 255, 255, 0.12)';
+})<{ ownerState: FilledInputOwnerState }>(({ theme, ownerState }) => {
+ const { vars: tokens } = theme;
+
return {
+ '--md-comp-filled-input-active-indicator-color': tokens.sys.color.onSurfaceVariant,
+ '--md-comp-filled-input-container-color': tokens.sys.color.surfaceContainerHighest,
+ '--md-comp-filled-input-disabled-container-color': tokens.sys.color.onSurface,
+ '--md-comp-filled-input-disabled-container-opacity': 0.04,
+ '--md-comp-filled-input-error-active-indicator-color': tokens.sys.color.error,
+ '--md-comp-filled-input-error-hover-active-indicator-color': tokens.sys.color.onErrorContainer,
+ '--md-comp-filled-input-focus-active-indicator-color':
+ tokens.sys.color[ownerState.color ?? 'primary'],
+ '--md-comp-filled-input-hover-active-indicator-color': tokens.sys.color.onSurface,
+ '--md-comp-filled-input-hover-state-layer-opacity': tokens.sys.state.hover.stateLayerOpacity,
position: 'relative',
- backgroundColor: theme.vars ? theme.vars.palette.FilledInput.bg : backgroundColor,
- borderTopLeftRadius: (theme.vars || theme).shape.borderRadius,
- borderTopRightRadius: (theme.vars || theme).shape.borderRadius,
+ backgroundColor: 'var(--md-comp-filled-input-container-color)',
+ borderTopLeftRadius: tokens.shape.borderRadius,
+ borderTopRightRadius: tokens.shape.borderRadius,
transition: theme.transitions.create('background-color', {
duration: theme.transitions.duration.shorter,
easing: theme.transitions.easing.easeOut,
}),
'&:hover': {
- backgroundColor: theme.vars ? theme.vars.palette.FilledInput.hoverBg : hoverBackground,
+ backgroundColor:
+ 'color-mix(in srgb, var(--md-comp-filled-input-hover-active-indicator-color) calc(var(--md-comp-filled-input-hover-state-layer-opacity) * 100%), var(--md-comp-filled-input-container-color))',
// Reset on touch devices, it doesn't add specificity
'@media (hover: none)': {
- backgroundColor: theme.vars ? theme.vars.palette.FilledInput.bg : backgroundColor,
+ backgroundColor: 'var(--md-comp-filled-input-container-color)',
},
},
[`&.${filledInputClasses.focused}`]: {
- backgroundColor: theme.vars ? theme.vars.palette.FilledInput.bg : backgroundColor,
+ backgroundColor: 'var(--md-comp-filled-input-container-color)',
+ '&:after': {
+ borderColor: 'var(--md-comp-filled-input-focus-active-indicator-color)',
+ },
},
[`&.${filledInputClasses.disabled}`]: {
- backgroundColor: theme.vars ? theme.vars.palette.FilledInput.disabledBg : disabledBackground,
+ backgroundColor:
+ 'color-mix(in srgb, var(--md-comp-filled-input-disabled-container-color) var(--md-comp-filled-input-disabled-container-opacity), var(--md-comp-filled-input-container-color))',
},
...(!ownerState.disableUnderline && {
'&:after': {
- borderBottom: `2px solid ${
- (theme.vars || theme).palette[ownerState.color || 'primary']?.main
- }`,
+ borderBottom: '2px solid var(--md-comp-filled-input-active-indicator-color)',
left: 0,
bottom: 0,
// Doing the other way around crash on IE11 "''" https://github.com/cssinjs/jss/issues/242
@@ -94,15 +108,11 @@ const FilledInputRoot = styled(InputBaseRoot, {
},
[`&.${filledInputClasses.error}`]: {
'&:before, &:after': {
- borderBottomColor: (theme.vars || theme).palette.error.main,
+ borderBottomColor: 'var(--md-comp-filled-input-error-active-indicator-color)',
},
},
'&:before': {
- borderBottom: `1px solid ${
- theme.vars
- ? `rgba(${theme.vars.palette.common.onBackgroundChannel} / ${theme.vars.opacity.inputUnderline})`
- : bottomLineColor
- }`,
+ borderBottom: '1px solid var(--md-comp-filled-input-active-indicator-color)',
left: 0,
bottom: 0,
// Doing the other way around crash on IE11 "''" https://github.com/cssinjs/jss/issues/242
@@ -115,7 +125,7 @@ const FilledInputRoot = styled(InputBaseRoot, {
pointerEvents: 'none', // Transparent to the hover style.
},
[`&:hover:not(.${filledInputClasses.disabled}, .${filledInputClasses.error}):before`]: {
- borderBottom: `1px solid ${(theme.vars || theme).palette.text.primary}`,
+ borderBottom: '1px solid var(--md-comp-filled-input-active-indicator-color)',
},
[`&.${filledInputClasses.disabled}:before`]: {
borderBottomStyle: 'dotted',
@@ -145,115 +155,163 @@ const FilledInputInput = styled(InputBaseInput, {
name: 'MuiFilledInput',
slot: 'Input',
overridesResolver: inputBaseInputOverridesResolver,
-})(({ theme, ownerState }) => ({
- paddingTop: 25,
- paddingRight: 12,
- paddingBottom: 8,
- paddingLeft: 12,
- ...(!theme.vars && {
- '&:-webkit-autofill': {
- WebkitBoxShadow: theme.palette.mode === 'light' ? null : '0 0 0 100px #266798 inset',
- WebkitTextFillColor: theme.palette.mode === 'light' ? null : '#fff',
- caretColor: theme.palette.mode === 'light' ? null : '#fff',
- borderTopLeftRadius: 'inherit',
- borderTopRightRadius: 'inherit',
- },
- }),
- ...(theme.vars && {
- '&:-webkit-autofill': {
- borderTopLeftRadius: 'inherit',
- borderTopRightRadius: 'inherit',
- },
- [theme.getColorSchemeSelector('dark')]: {
- '&:-webkit-autofill': {
- WebkitBoxShadow: '0 0 0 100px #266798 inset',
- WebkitTextFillColor: '#fff',
- caretColor: '#fff',
- },
- },
- }),
- ...(ownerState.size === 'small' && {
- paddingTop: 21,
- paddingBottom: 4,
- }),
- ...(ownerState.hiddenLabel && {
- paddingTop: 16,
- paddingBottom: 17,
- }),
- ...(ownerState.multiline && {
- paddingTop: 0,
- paddingBottom: 0,
- paddingLeft: 0,
- paddingRight: 0,
- }),
- ...(ownerState.startAdornment && {
- paddingLeft: 0,
- }),
- ...(ownerState.endAdornment && {
- paddingRight: 0,
- }),
- ...(ownerState.hiddenLabel &&
- ownerState.size === 'small' && {
- paddingTop: 8,
- paddingBottom: 9,
+})<{ ownerState: FilledInputOwnerState }>(({ theme, ownerState }) => {
+ const { vars: tokens } = theme;
+
+ return {
+ paddingTop: 25,
+ paddingRight: 12,
+ paddingBottom: 8,
+ paddingLeft: 12,
+ ...(!tokens
+ ? {
+ [theme.getColorSchemeSelector('light')]: {
+ '&:-webkit-autofill': {
+ WebkitBoxShadow: null,
+ WebkitTextFillColor: null,
+ caretColor: null,
+ borderTopLeftRadius: 'inherit',
+ borderTopRightRadius: 'inherit',
+ },
+ },
+ [theme.getColorSchemeSelector('dark')]: {
+ '&:-webkit-autofill': {
+ WebkitBoxShadow: '0 0 0 100px #266798 inset',
+ WebkitTextFillColor: '#fff',
+ caretColor: '#fff',
+ borderTopLeftRadius: 'inherit',
+ borderTopRightRadius: 'inherit',
+ },
+ },
+ }
+ : {
+ '&:-webkit-autofill': {
+ borderTopLeftRadius: 'inherit',
+ borderTopRightRadius: 'inherit',
+ },
+ // this could be undefined in unit tests
+ ...(theme.getColorSchemeSelector && {
+ [theme.getColorSchemeSelector('dark')]: {
+ '&:-webkit-autofill': {
+ WebkitBoxShadow: '0 0 0 100px #266798 inset',
+ WebkitTextFillColor: '#fff',
+ caretColor: '#fff',
+ },
+ },
+ }),
+ }),
+ ...(ownerState.size === 'small' && {
+ paddingTop: 21,
+ paddingBottom: 4,
+ }),
+ ...(ownerState.hiddenLabel && {
+ paddingTop: 16,
+ paddingBottom: 17,
+ }),
+ ...(ownerState.multiline && {
+ paddingTop: 0,
+ paddingBottom: 0,
+ paddingLeft: 0,
+ paddingRight: 0,
+ }),
+ ...(ownerState.startAdornment && {
+ paddingLeft: 0,
}),
-}));
+ ...(ownerState.endAdornment && {
+ paddingRight: 0,
+ }),
+ ...(ownerState.hiddenLabel &&
+ ownerState.size === 'small' && {
+ paddingTop: 8,
+ paddingBottom: 9,
+ }),
+ };
+});
-const FilledInput = React.forwardRef(function FilledInput(inProps, ref) {
+const FilledInput = React.forwardRef(function FilledInput<
+ RootComponentType extends React.ElementType,
+>(inProps: FilledInputProps, forwardedRef: React.ForwardedRef) {
const props = useThemeProps({ props: inProps, name: 'MuiFilledInput' });
const {
disableUnderline,
- components = {},
- componentsProps: componentsPropsProp,
fullWidth = false,
hiddenLabel, // declare here to prevent spreading to DOM
inputComponent = 'input',
multiline = false,
- slotProps,
- slots = {},
type = 'text',
+ slotProps = {},
+ slots = {},
...other
} = props;
- const ownerState = {
+ const ownerState: FilledInputOwnerState = {
...props,
+ disableUnderline,
fullWidth,
inputComponent,
multiline,
type,
};
- const classes = useUtilityClasses(props);
- const filledInputComponentsProps = { root: { ownerState }, input: { ownerState } };
+ const classes = useUtilityClasses(ownerState);
+
+ const Root = slots.root ?? FilledInputRoot;
+ const Input = slots.input ?? FilledInputInput;
- const componentsProps =
- slotProps ?? componentsPropsProp
- ? deepmerge(slotProps ?? componentsPropsProp, filledInputComponentsProps)
- : filledInputComponentsProps;
+ const rootProps = useSlotProps({
+ elementType: Root,
+ externalSlotProps: slotProps.root,
+ additionalProps: {
+ ref: forwardedRef,
+ fullWidth,
+ inputComponent,
+ multiline,
+ type,
+ },
+ externalForwardedProps: other,
+ ownerState: ownerState as FilledInputOwnerState & InputBaseOwnerState,
+ className: [classes.root],
+ });
- const RootSlot = slots.root ?? components.Root ?? FilledInputRoot;
- const InputSlot = slots.input ?? components.Input ?? FilledInputInput;
+ const inputProps = useSlotProps({
+ elementType: Input,
+ externalSlotProps: slotProps.input,
+ ownerState: ownerState as FilledInputOwnerState & InputBaseOwnerState,
+ className: [classes.input],
+ });
return (
);
-});
+}) as FilledInputComponent;
+
+interface FilledInputComponent {
+ (
+ props: {
+ /**
+ * The component used for the input node.
+ * Either a string to use a HTML element or a component.
+ * @default 'input'
+ */
+ inputComponent?: C;
+ } & OverrideProps,
+ ): JSX.Element | null;
+ (props: DefaultComponentProps): JSX.Element | null;
+ propTypes?: any;
+ muiName?: string;
+}
FilledInput.propTypes /* remove-proptypes */ = {
// ----------------------------- Warning --------------------------------
// | These PropTypes are generated from the TypeScript type definitions |
- // | To update them edit the d.ts file and run "yarn proptypes" |
+ // | To update them edit TypeScript types and run "yarn proptypes" |
// ----------------------------------------------------------------------
/**
* This prop helps users to fill forms faster, especially on mobile devices.
@@ -265,6 +323,10 @@ FilledInput.propTypes /* remove-proptypes */ = {
* If `true`, the `input` element is focused during the first mount.
*/
autoFocus: PropTypes.bool,
+ /**
+ * @ignore
+ */
+ children: PropTypes.node,
/**
* Override or extend the styles applied to the component.
*/
@@ -276,34 +338,9 @@ FilledInput.propTypes /* remove-proptypes */ = {
* The prop defaults to the value (`'primary'`) inherited from the parent FormControl component.
*/
color: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([
- PropTypes.oneOf(['primary', 'secondary']),
+ PropTypes.oneOf(['error', 'info', 'primary', 'secondary', 'success', 'tertiary', 'warning']),
PropTypes.string,
]),
- /**
- * The components used for each slot inside.
- *
- * This prop is an alias for the `slots` prop.
- * It's recommended to use the `slots` prop instead.
- *
- * @default {}
- */
- components: PropTypes.shape({
- Input: PropTypes.elementType,
- Root: PropTypes.elementType,
- }),
- /**
- * The extra props for the slot components.
- * You can override the existing props or add new ones.
- *
- * This prop is an alias for the `slotProps` prop.
- * It's recommended to use the `slotProps` prop instead, as `componentsProps` will be deprecated in the future.
- *
- * @default {}
- */
- componentsProps: PropTypes.shape({
- input: PropTypes.object,
- root: PropTypes.object,
- }),
/**
* The default value. Use when the component is not controlled.
*/
@@ -343,16 +380,11 @@ FilledInput.propTypes /* remove-proptypes */ = {
*/
id: PropTypes.string,
/**
- * The component used for the `input` element.
+ * The component used for the input node.
* Either a string to use a HTML element or a component.
* @default 'input'
*/
- inputComponent: PropTypes.elementType,
- /**
- * [Attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Attributes) applied to the `input` element.
- * @default {}
- */
- inputProps: PropTypes.object,
+ inputComponent: PropTypes /* @typescript-to-proptypes-ignore */.elementType,
/**
* Pass a ref to the `input` element.
*/
@@ -366,13 +398,13 @@ FilledInput.propTypes /* remove-proptypes */ = {
/**
* Maximum number of rows to display when multiline option is set to true.
*/
- maxRows: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
+ maxRows: PropTypes.number,
/**
* Minimum number of rows to display when multiline option is set to true.
*/
- minRows: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
+ minRows: PropTypes.number,
/**
- * If `true`, a [TextareaAutosize](/material-ui/react-textarea-autosize/) element is rendered.
+ * If `true`, a `textarea` element is rendered.
* @default false
*/
multiline: PropTypes.bool,
@@ -404,24 +436,18 @@ FilledInput.propTypes /* remove-proptypes */ = {
/**
* Number of rows to display when multiline option is set to true.
*/
- rows: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
+ rows: PropTypes.number,
/**
- * The extra props for the slot components.
- * You can override the existing props or add new ones.
- *
- * This prop is an alias for the `componentsProps` prop, which will be deprecated in the future.
- *
+ * The props used for each slot inside the Input.
* @default {}
*/
slotProps: PropTypes.shape({
- input: PropTypes.object,
- root: PropTypes.object,
+ input: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
+ root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
}),
/**
- * The components used for each slot inside.
- *
- * This prop is an alias for the `components` prop, which will be deprecated in the future.
- *
+ * The components used for each slot inside the InputBase.
+ * Either a string to use a HTML element or a component.
* @default {}
*/
slots: PropTypes.shape({
@@ -444,12 +470,35 @@ FilledInput.propTypes /* remove-proptypes */ = {
* Type of the `input` element. It should be [a valid HTML5 input type](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Form_%3Cinput%3E_types).
* @default 'text'
*/
- type: PropTypes.string,
+ type: PropTypes /* @typescript-to-proptypes-ignore */.oneOf([
+ 'button',
+ 'checkbox',
+ 'color',
+ 'date',
+ 'datetime-local',
+ 'email',
+ 'file',
+ 'hidden',
+ 'image',
+ 'month',
+ 'number',
+ 'password',
+ 'radio',
+ 'range',
+ 'reset',
+ 'search',
+ 'submit',
+ 'tel',
+ 'text',
+ 'time',
+ 'url',
+ 'week',
+ ]),
/**
* The value of the `input` element, required for a controlled component.
*/
value: PropTypes.any,
-};
+} as any;
FilledInput.muiName = 'Input';
diff --git a/packages/mui-material-next/src/FilledInput/FilledInput.types.ts b/packages/mui-material-next/src/FilledInput/FilledInput.types.ts
new file mode 100644
index 00000000000000..fe229efca59b7f
--- /dev/null
+++ b/packages/mui-material-next/src/FilledInput/FilledInput.types.ts
@@ -0,0 +1,75 @@
+import { SxProps } from '@mui/system';
+// TODO v6: port to material-next
+// eslint-disable-next-line no-restricted-imports
+import { InternalStandardProps as StandardProps } from '@mui/material';
+import { OverrideProps, Simplify } from '@mui/types';
+import { Theme } from '../styles/Theme.types';
+import { InputBaseProps } from '../InputBase/InputBase.types';
+import { FilledInputClasses } from './filledInputClasses';
+
+export interface FilledInputOwnProps
+ extends StandardProps> {
+ /**
+ * Override or extend the styles applied to the component.
+ */
+ classes?: Partial;
+ /**
+ * If `true`, the label is hidden.
+ * This is used to increase density for a `FilledInput`.
+ * Be sure to add `aria-label` to the `input` element.
+ * @default false
+ */
+ hiddenLabel?: boolean;
+ /**
+ * If `true`, the input will not have an underline.
+ */
+ disableUnderline?: boolean;
+ /**
+ * The components used for each slot inside the InputBase.
+ * Either a string to use a HTML element or a component.
+ * @default {}
+ */
+ slots?: FilledInputSlots;
+ /**
+ * The system prop that allows defining system overrides as well as additional CSS styles.
+ */
+ sx?: SxProps;
+}
+
+export interface FilledInputSlots {
+ /**
+ * The component that renders the root.
+ * @default 'div'
+ */
+ root?: React.ElementType;
+ /**
+ * The component that renders the input.
+ * @default 'input'
+ */
+ input?: React.ElementType;
+}
+
+export interface FilledInputTypeMap<
+ AdditionalProps = {},
+ RootComponentType extends React.ElementType = 'div',
+> {
+ props: FilledInputOwnProps & AdditionalProps;
+ defaultComponent: RootComponentType;
+}
+
+export type FilledInputProps<
+ RootComponentType extends React.ElementType = FilledInputTypeMap['defaultComponent'],
+ AdditionalProps = {},
+> = OverrideProps, RootComponentType> & {
+ inputComponent?: React.ElementType;
+};
+
+export type FilledInputOwnerState = Simplify<
+ FilledInputOwnProps & {
+ disableUnderline?: boolean;
+ fullWidth: boolean;
+ inputComponent: React.ElementType;
+ multiline: boolean;
+ type?: React.InputHTMLAttributes['type'];
+ }
+>;
diff --git a/packages/mui-material-next/src/FilledInput/index.d.ts b/packages/mui-material-next/src/FilledInput/index.d.ts
deleted file mode 100644
index f3576964636f51..00000000000000
--- a/packages/mui-material-next/src/FilledInput/index.d.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export { default } from './FilledInput';
-export * from './FilledInput';
-
-export { default as filledInputClasses } from './filledInputClasses';
-export * from './filledInputClasses';
diff --git a/packages/mui-material-next/src/FilledInput/index.js b/packages/mui-material-next/src/FilledInput/index.ts
similarity index 100%
rename from packages/mui-material-next/src/FilledInput/index.js
rename to packages/mui-material-next/src/FilledInput/index.ts
diff --git a/packages/mui-material-next/src/FormControl/FormControl.test.js b/packages/mui-material-next/src/FormControl/FormControl.test.tsx
similarity index 86%
rename from packages/mui-material-next/src/FormControl/FormControl.test.js
rename to packages/mui-material-next/src/FormControl/FormControl.test.tsx
index ed22b74a058d50..1068d199c74b54 100644
--- a/packages/mui-material-next/src/FormControl/FormControl.test.js
+++ b/packages/mui-material-next/src/FormControl/FormControl.test.tsx
@@ -3,17 +3,28 @@ import { expect } from 'chai';
import { spy } from 'sinon';
import { describeConformance, act, createRenderer, fireEvent } from '@mui-internal/test-utils';
import FormControl, { formControlClasses as classes } from '@mui/material-next/FormControl';
-// TODO v6: replace with material-next/FilledInput
+import FilledInput from '@mui/material-next/FilledInput';
import InputBase from '@mui/material-next/InputBase';
import { CssVarsProvider, extendTheme } from '@mui/material-next/styles';
// TODO v6: replace with material-next/Select
import Select from '@mui/material/Select';
import useFormControl from './useFormControl';
+type TestFormControlledComponent = {
+ onFilled: () => {};
+ onEmpty: () => {};
+ onFocus: () => {};
+ onBlur: () => {};
+};
+
describe('', () => {
const { render } = createRenderer();
- function TestComponent(props) {
+ interface TestComponentProps {
+ contextCallback: (context: ReturnType) => void;
+ }
+
+ function TestComponent(props: TestComponentProps) {
const context = useFormControl();
React.useEffect(() => {
props.contextCallback(context);
@@ -47,7 +58,6 @@ describe('', () => {
const root = container.firstChild;
expect(root).not.to.have.class(classes.marginNormal);
- expect(root).not.to.have.class(classes.sizeSmall);
});
it('can have the margin normal class', () => {
@@ -55,7 +65,6 @@ describe('', () => {
const root = container.firstChild;
expect(root).to.have.class(classes.marginNormal);
- expect(root).not.to.have.class(classes.sizeSmall);
});
it('can have the margin dense class', () => {
@@ -106,7 +115,7 @@ describe('', () => {
expect(readContext.args[0][0]).to.have.property('focused', false);
act(() => {
- container.querySelector('input').focus();
+ container.querySelector('input')?.focus();
});
expect(readContext.lastCall.args[0]).to.have.property('focused', true);
@@ -126,7 +135,7 @@ describe('', () => {
);
expect(readContext.args[0][0]).to.have.property('focused', true);
- container.querySelector('input').blur();
+ container.querySelector('input')?.blur();
expect(readContext.args[0][0]).to.have.property('focused', true);
});
@@ -201,27 +210,23 @@ describe('', () => {
});
});
- // TODO v6: needs FilledInput + FormControl integrated
- // eslint-disable-next-line mocha/no-skipped-tests
- describe.skip('input', () => {
+ describe('input', () => {
it('should be filled when a value is set', () => {
const readContext = spy();
render(
- {/* TODO v6: use material-next/FilledInput */}
-
+
,
);
expect(readContext.args[0][0]).to.have.property('filled', true);
});
- it('should be filled when a value is set through inputProps', () => {
+ it('should be filled when a value is set through slotProps.input', () => {
const readContext = spy();
render(
- {/* TODO v6: use material-next/FilledInput */}
-
+
,
);
@@ -232,8 +237,7 @@ describe('', () => {
const readContext = spy();
render(
- {/* TODO v6: use material-next/FilledInput */}
-
+
,
);
@@ -244,8 +248,7 @@ describe('', () => {
const readContext = spy();
render(
- {/* TODO v6: use material-next/FilledInput */}
- } />
+ } />
,
);
@@ -256,8 +259,7 @@ describe('', () => {
const readContext = spy();
render(
- {/* TODO v6: use material-next/FilledInput */}
- } />
+ } />
,
);
@@ -287,7 +289,7 @@ describe('', () => {
,
);
- expect(readContext.args[0][0].adornedStart, true);
+ expect(readContext.args[0][0].adornedStart, 'true');
});
});
@@ -308,7 +310,7 @@ describe('', () => {
describe('from props', () => {
it('should have the required prop from the instance', () => {
- const formControlRef = React.createRef();
+ const formControlRef = React.createRef();
const { setProps } = render();
expect(formControlRef.current).to.have.property('required', false);
@@ -318,7 +320,7 @@ describe('', () => {
});
it('should have the error prop from the instance', () => {
- const formControlRef = React.createRef();
+ const formControlRef = React.createRef();
const { setProps } = render();
expect(formControlRef.current).to.have.property('error', false);
@@ -328,7 +330,7 @@ describe('', () => {
});
it('should have the margin prop from the instance', () => {
- const formControlRef = React.createRef();
+ const formControlRef = React.createRef();
const { setProps } = render();
expect(formControlRef.current).to.have.property('size', 'medium');
@@ -338,7 +340,7 @@ describe('', () => {
});
it('should have the fullWidth prop from the instance', () => {
- const formControlRef = React.createRef();
+ const formControlRef = React.createRef();
const { setProps } = render();
expect(formControlRef.current).to.have.property('fullWidth', false);
@@ -351,19 +353,19 @@ describe('', () => {
describe('callbacks', () => {
describe('onFilled', () => {
it('should set the filled state', () => {
- const formControlRef = React.createRef();
+ const formControlRef = React.createRef();
render();
expect(formControlRef.current).to.have.property('filled', false);
act(() => {
- formControlRef.current.onFilled();
+ formControlRef.current?.onFilled();
});
expect(formControlRef.current).to.have.property('filled', true);
act(() => {
- formControlRef.current.onFilled();
+ formControlRef.current?.onFilled();
});
expect(formControlRef.current).to.have.property('filled', true);
@@ -372,23 +374,23 @@ describe('', () => {
describe('onEmpty', () => {
it('should clean the filled state', () => {
- const formControlRef = React.createRef();
+ const formControlRef = React.createRef();
render();
act(() => {
- formControlRef.current.onFilled();
+ formControlRef.current?.onFilled();
});
expect(formControlRef.current).to.have.property('filled', true);
act(() => {
- formControlRef.current.onEmpty();
+ formControlRef.current?.onEmpty();
});
expect(formControlRef.current).to.have.property('filled', false);
act(() => {
- formControlRef.current.onEmpty();
+ formControlRef.current?.onEmpty();
});
expect(formControlRef.current).to.have.property('filled', false);
@@ -397,18 +399,18 @@ describe('', () => {
describe('handleFocus', () => {
it('should set the focused state', () => {
- const formControlRef = React.createRef();
+ const formControlRef = React.createRef();
render();
expect(formControlRef.current).to.have.property('focused', false);
act(() => {
- formControlRef.current.onFocus();
+ formControlRef.current?.onFocus();
});
expect(formControlRef.current).to.have.property('focused', true);
act(() => {
- formControlRef.current.onFocus();
+ formControlRef.current?.onFocus();
});
expect(formControlRef.current).to.have.property('focused', true);
@@ -417,24 +419,24 @@ describe('', () => {
describe('handleBlur', () => {
it('should clear the focused state', () => {
- const formControlRef = React.createRef();
+ const formControlRef = React.createRef();
render();
expect(formControlRef.current).to.have.property('focused', false);
act(() => {
- formControlRef.current.onFocus();
+ formControlRef.current?.onFocus();
});
expect(formControlRef.current).to.have.property('focused', true);
act(() => {
- formControlRef.current.onBlur();
+ formControlRef.current?.onBlur();
});
expect(formControlRef.current).to.have.property('focused', false);
act(() => {
- formControlRef.current.onBlur();
+ formControlRef.current?.onBlur();
});
expect(formControlRef.current).to.have.property('focused', false);
diff --git a/packages/mui-material-next/src/FormControl/FormControl.tsx b/packages/mui-material-next/src/FormControl/FormControl.tsx
index 4d0f15b1c1306f..478a61dc14ad1b 100644
--- a/packages/mui-material-next/src/FormControl/FormControl.tsx
+++ b/packages/mui-material-next/src/FormControl/FormControl.tsx
@@ -141,7 +141,7 @@ const FormControl = React.forwardRef(function FormControl<
if (
React.isValidElement(child) &&
- (isFilled(child.props, true) || isFilled(child.props.inputProps, true))
+ (isFilled(child.props, true) || isFilled(child.props.slotProps?.input, true))
) {
initialFilled = true;
}
diff --git a/packages/mui-material-next/src/InputBase/InputBase.test.js b/packages/mui-material-next/src/InputBase/InputBase.test.tsx
similarity index 82%
rename from packages/mui-material-next/src/InputBase/InputBase.test.js
rename to packages/mui-material-next/src/InputBase/InputBase.test.tsx
index 96eb55612ed2ca..b91ed92548e827 100644
--- a/packages/mui-material-next/src/InputBase/InputBase.test.js
+++ b/packages/mui-material-next/src/InputBase/InputBase.test.tsx
@@ -18,6 +18,11 @@ import TextField from '@mui/material/TextField';
import Select from '@mui/material/Select';
import InputBase, { inputBaseClasses as classes } from '@mui/material-next/InputBase';
import { CssVarsProvider, extendTheme } from '@mui/material-next/styles';
+import {
+ InputBaseInputSlotPropsOverrides,
+ InputBaseOwnerState,
+ InputBaseProps,
+} from './InputBase.types';
describe('', () => {
const { render } = createRenderer();
@@ -53,6 +58,21 @@ describe('', () => {
});
describe('multiline', () => {
+ describe('warning if multiline related props are passed without specifying the multiline prop', () => {
+ ['rows', 'minRows', 'maxRows'].forEach((multilineProp) => {
+ it(`warns if ${multilineProp} is passed without specifying multiline`, () => {
+ const multilineErrorMessage = `MUI: You have set multiline props on an single-line input.\nSet the \`multiline\` prop if you want to render a multi-line input.\nOtherwise they will be ignored.\nIgnored props: ${multilineProp}`;
+ expect(() => {
+ render();
+ }).toErrorDev([
+ multilineErrorMessage,
+ // React 18 Strict Effects run mount effects twice
+ React.version.startsWith('18') && multilineErrorMessage,
+ ]);
+ });
+ });
+ });
+
it('should render a `textbox` with `aria-multiline`', () => {
render();
@@ -185,7 +205,12 @@ describe('', () => {
*
* A ref is exposed to trigger a change event instead of using fireEvent.change
*/
- const BadInputComponent = React.forwardRef(function BadInputComponent(props, ref) {
+ const BadInputComponent = React.forwardRef(function BadInputComponent(
+ props: {
+ onChange: (arg: Record) => void;
+ },
+ ref,
+ ) {
const { onChange } = props;
// simulates const handleChange = () => onChange({}) and passing that
@@ -199,7 +224,7 @@ describe('', () => {
onChange: PropTypes.func.isRequired,
};
- const triggerChangeRef = React.createRef();
+ const triggerChangeRef = React.createRef();
expect(() => {
render(
@@ -228,15 +253,21 @@ describe('', () => {
const { getByTestId } = render(
,
);
expect(getByTestId('input-component')).to.have.property('nodeName', 'SPAN');
});
it('should inject onBlur and onFocus', () => {
- let injectedProps;
- const MyInputBase = React.forwardRef(function MyInputBase(props, ref) {
+ let injectedProps: Record = {};
+
+ const MyInputBase = React.forwardRef(function MyInputBase(
+ props: { ownerState: InputBaseOwnerState } & Record,
+ ref: React.ForwardedRef,
+ ) {
injectedProps = props;
const { ownerState, ...other } = props;
return ;
@@ -250,20 +281,27 @@ describe('', () => {
describe('target mock implementations', () => {
it('can just mock the value', () => {
- const MockedValue = React.forwardRef(function MockedValue(props, ref) {
+ const MockedValue = React.forwardRef(function MockedValue(
+ props: {
+ onChange: React.ChangeEventHandler;
+ },
+ ref: React.ForwardedRef,
+ ) {
const { onChange } = props;
- const handleChange = (event) => {
- onChange({ target: { value: event.target.value } });
+ const handleChange = (event: React.ChangeEvent) => {
+ onChange({
+ target: { value: event.target.value },
+ } as React.ChangeEvent);
};
return ;
});
MockedValue.propTypes = { onChange: PropTypes.func.isRequired };
- function FilledState(props) {
- const { filled } = useFormControl();
- return filled: {String(filled)};
+ function FilledState(props: { 'data-testid': string }) {
+ const formControlContext = useFormControl();
+ return filled: {String(formControlContext?.filled)};
}
const { getByRole, getByTestId } = render(
@@ -279,14 +317,17 @@ describe('', () => {
});
it("can expose the input component's ref through the inputComponent prop", () => {
- const FullTarget = React.forwardRef(function FullTarget(props, ref) {
+ const FullTarget = React.forwardRef(function FullTarget(
+ props: { ownerState: InputBaseOwnerState } & Record,
+ ref: React.ForwardedRef,
+ ) {
const { ownerState, ...otherProps } = props;
return ;
});
- function FilledState(props) {
- const { filled } = useFormControl();
- return filled: {String(filled)};
+ function FilledState(props: { 'data-testid': string }) {
+ const formControlContext = useFormControl();
+ return filled: {String(formControlContext?.filled)};
}
const { getByRole, getByTestId } = render(
@@ -331,7 +372,7 @@ describe('', () => {
describe('error', () => {
it('should be overridden by props', () => {
- function InputBaseInErrorForm(props) {
+ function InputBaseInErrorForm(props: InputBaseProps) {
return (
@@ -361,7 +402,7 @@ describe('', () => {
});
it('should be overridden by props', () => {
- function InputBaseInFormWithMargin(props) {
+ function InputBaseInFormWithMargin(props: InputBaseProps) {
return (
@@ -398,16 +439,21 @@ describe('', () => {
});
});
+ type TestFormController = {
+ onFocus: () => {};
+ onBlur: () => {};
+ };
+
describe('focused', () => {
it('prioritizes context focus', () => {
const FormController = React.forwardRef((props, ref) => {
- const { onBlur, onFocus } = useFormControl();
+ const { onBlur, onFocus } = useFormControl() ?? {};
React.useImperativeHandle(ref, () => ({ onBlur, onFocus }), [onBlur, onFocus]);
return null;
});
- const controlRef = React.createRef();
+ const controlRef = React.createRef();
const { getByRole, getByTestId } = render(
@@ -421,22 +467,22 @@ describe('', () => {
expect(getByTestId('root')).to.have.class(classes.focused);
act(() => {
- controlRef.current.onBlur();
+ controlRef.current?.onBlur();
});
expect(getByTestId('root')).not.to.have.class(classes.focused);
act(() => {
- controlRef.current.onFocus();
+ controlRef.current?.onFocus();
});
expect(getByTestId('root')).to.have.class(classes.focused);
});
it('propagates focused state', () => {
- function FocusedStateLabel(props) {
- const { focused } = useFormControl();
- return ;
+ function FocusedStateLabel(props: { 'data-testid': string; htmlFor: string }) {
+ const formControlContext = useFormControl();
+ return ;
}
const { getByRole, getByTestId } = render(
@@ -459,9 +505,9 @@ describe('', () => {
});
it('propagates filled state when uncontrolled', () => {
- function FilledStateLabel(props) {
- const { filled } = useFormControl();
- return ;
+ function FilledStateLabel(props: { 'data-testid': string }) {
+ const formControlContext = useFormControl();
+ return ;
}
const { getByRole, getByTestId } = render(
@@ -483,11 +529,11 @@ describe('', () => {
});
it('propagates filled state when controlled', () => {
- function FilledStateLabel(props) {
- const { filled } = useFormControl();
- return ;
+ function FilledStateLabel(props: { 'data-testid': string }) {
+ const formControlContext = useFormControl();
+ return ;
}
- function ControlledInputBase(props) {
+ function ControlledInputBase(props: InputBaseProps) {
return (
@@ -552,7 +598,7 @@ describe('', () => {
});
it('should be able to get a ref', () => {
- const inputRef = React.createRef();
+ const inputRef = React.createRef();
const { container } = render();
expect(inputRef.current).to.equal(container.querySelector('input'));
});
@@ -560,21 +606,29 @@ describe('', () => {
it('should not repeat the same classname', () => {
const { container } = render();
const input = container.querySelector('input');
- const matches = input.className.match(/foo/g);
+ const matches = input?.className.match(/foo/g);
expect(input).to.have.class('foo');
expect(matches).to.have.length(1);
});
});
describe('prop: slots and slotProps', () => {
- it('should call onChange inputProp callback with all params sent from custom inputComponent', () => {
+ // e.g. integration of react-select with InputBase
+ // https://github.com/mui/material-ui/issues/18130
+ // react-select has a custom onChange that is essentially "(string, string) => void"
+ it('should call slotProps.input.onChange callback with all params sent from custom inputComponent', () => {
const INPUT_VALUE = 'material';
const OUTPUT_VALUE = 'test';
- const MyInputBase = React.forwardRef(function MyInputBase(props, ref) {
- const { onChange, ownerState, ...other } = props;
+ const MyInputBase = React.forwardRef(function MyInputBase(
+ props: {
+ onChange: (...args: string[]) => void;
+ },
+ ref: React.ForwardedRef,
+ ) {
+ const { onChange, ...other } = props;
- const handleChange = (e) => {
+ const handleChange = (e: React.ChangeEvent) => {
onChange(e.target.value, OUTPUT_VALUE);
};
@@ -585,17 +639,17 @@ describe('', () => {
onChange: PropTypes.func.isRequired,
};
- let outputArguments;
- function parentHandleChange(...args) {
+ let outputArguments: string[] = [];
+ function parentHandleChange(...args: string[]) {
outputArguments = args;
}
const { getByRole } = render(
,
},
}}
/>,
@@ -670,7 +724,7 @@ describe('', () => {
});
describe('prop: focused', () => {
- // TODO v6: requires material-next/OutlinedInput
+ // TODO v6: requires material-next/TextField
// eslint-disable-next-line mocha/no-skipped-tests
it.skip('should render correct border color with a customized primary color supplied to CssVarsProvider', function test() {
if (/jsdom/.test(window.navigator.userAgent)) {
@@ -689,7 +743,7 @@ describe('', () => {
});
const { getByRole } = render(
- {/* TODO v6: use material-next/TextField or OutlinedInput */}
+ {/* TODO v6: use material-next/TextField */}
,
);
diff --git a/packages/mui-material-next/src/InputBase/InputBase.tsx b/packages/mui-material-next/src/InputBase/InputBase.tsx
index fa5000b80ada3a..fcbb3b667027a3 100644
--- a/packages/mui-material-next/src/InputBase/InputBase.tsx
+++ b/packages/mui-material-next/src/InputBase/InputBase.tsx
@@ -11,12 +11,13 @@ import {
WithOptionalOwnerState,
} from '@mui/base';
import { useInput } from '@mui/base/useInput';
+import { CSSInterpolation } from '@mui/system';
+import { DefaultComponentProps, OverrideProps } from '@mui/types';
import {
refType,
unstable_capitalize as capitalize,
unstable_useEnhancedEffect as useEnhancedEffect,
} from '@mui/utils';
-import { OverrideProps } from '@mui/types';
import FormControlContext from '@mui/material-next/FormControl/FormControlContext';
import useFormControl from '@mui/material-next/FormControl/useFormControl';
import styled from '../styles/styled';
@@ -80,25 +81,30 @@ const useUtilityClasses = (ownerState: InputBaseOwnerState) => {
return composeClasses(slots, getInputBaseUtilityClass, classes);
};
+export function rootOverridesResolver(
+ props: InputBaseRootSlotProps,
+ styles: Record,
+) {
+ const { ownerState } = props;
+
+ return [
+ styles.root,
+ ownerState.formControl && styles.formControl,
+ ownerState.startAdornment && styles.adornedStart,
+ ownerState.endAdornment && styles.adornedEnd,
+ ownerState.error && styles.error,
+ ownerState.size === 'small' && styles.sizeSmall,
+ ownerState.multiline && styles.multiline,
+ ownerState.color && styles[`color${capitalize(ownerState.color)}`],
+ ownerState.fullWidth && styles.fullWidth,
+ ownerState.hiddenLabel && styles.hiddenLabel,
+ ];
+}
+
export const InputBaseRoot = styled('div', {
name: 'MuiInputBase',
slot: 'Root',
- overridesResolver: (props, styles) => {
- const { ownerState } = props;
-
- return [
- styles.root,
- ownerState.formControl && styles.formControl,
- ownerState.startAdornment && styles.adornedStart,
- ownerState.endAdornment && styles.adornedEnd,
- ownerState.error && styles.error,
- ownerState.size === 'small' && styles.sizeSmall,
- ownerState.multiline && styles.multiline,
- ownerState.color && styles[`color${capitalize(ownerState.color)}`],
- ownerState.fullWidth && styles.fullWidth,
- ownerState.hiddenLabel && styles.hiddenLabel,
- ];
- },
+ overridesResolver: rootOverridesResolver,
})<{ ownerState: InputBaseOwnerState }>(({ theme, ownerState }) => {
const { vars: tokens } = theme;
@@ -129,22 +135,27 @@ export const InputBaseRoot = styled('div', {
};
});
+export function inputOverridesResolver(
+ props: InputBaseInputSlotProps,
+ styles: Record,
+) {
+ const { ownerState } = props;
+
+ return [
+ styles.input,
+ ownerState.size === 'small' && styles.inputSizeSmall,
+ ownerState.multiline && styles.inputMultiline,
+ ownerState.type === 'search' && styles.inputTypeSearch,
+ ownerState.startAdornment && styles.inputAdornedStart,
+ ownerState.endAdornment && styles.inputAdornedEnd,
+ ownerState.hiddenLabel && styles.inputHiddenLabel,
+ ];
+}
+
export const InputBaseInput = styled('input', {
name: 'MuiInputBase',
slot: 'Input',
- overridesResolver: (props, styles) => {
- const { ownerState } = props;
-
- return [
- styles.input,
- ownerState.size === 'small' && styles.inputSizeSmall,
- ownerState.multiline && styles.inputMultiline,
- ownerState.type === 'search' && styles.inputTypeSearch,
- ownerState.startAdornment && styles.inputAdornedStart,
- ownerState.endAdornment && styles.inputAdornedEnd,
- ownerState.hiddenLabel && styles.inputHiddenLabel,
- ];
- },
+ overridesResolver: inputOverridesResolver,
})<{ ownerState: InputBaseOwnerState }>(({ theme, ownerState }) => {
const { vars: tokens } = theme;
@@ -291,6 +302,23 @@ const InputBase = React.forwardRef(function InputBase<
...other
} = props;
+ if (process.env.NODE_ENV !== 'production') {
+ const definedMultilineProps = (['rows', 'minRows', 'maxRows'] as const).filter(
+ (multilineProp) => props[multilineProp] !== undefined,
+ );
+
+ if (!multiline && definedMultilineProps.length > 0) {
+ console.error(
+ [
+ 'MUI: You have set multiline props on an single-line input.',
+ 'Set the `multiline` prop if you want to render a multi-line input.',
+ 'Otherwise they will be ignored.',
+ `Ignored props: ${definedMultilineProps.join(', ')}`,
+ ].join('\n'),
+ );
+ }
+ }
+
const { current: isControlled } = React.useRef(value != null);
const muiFormControl = useFormControl();
@@ -308,7 +336,7 @@ const InputBase = React.forwardRef(function InputBase<
const onFilled = muiFormControl && muiFormControl.onFilled;
const onEmpty = muiFormControl && muiFormControl.onEmpty;
- // TODO: needs material-next/Outlined|FilledInput
+ // TODO: needs material-next/OutlinedInput
const checkDirty = React.useCallback(
(obj: any) => {
if (isFilled(obj)) {
@@ -535,10 +563,12 @@ interface InputBaseComponent {
/**
* The component used for the input node.
* Either a string to use a HTML element or a component.
+ * @default 'input'
*/
inputComponent?: C;
} & OverrideProps,
): JSX.Element | null;
+ (props: DefaultComponentProps): JSX.Element | null;
propTypes?: any;
}
@@ -551,6 +581,16 @@ InputBase.propTypes /* remove-proptypes */ = {
* @ignore
*/
'aria-describedby': PropTypes.string,
+ /**
+ * Defines a string value that labels the current element.
+ * @see aria-labelledby.
+ */
+ 'aria-label': PropTypes.string,
+ /**
+ * Identifies the element (or elements) that labels the current element.
+ * @see aria-describedby.
+ */
+ 'aria-labelledby': PropTypes.string,
/**
* This prop helps users to fill forms faster, especially on mobile devices.
* The name can be confusing, as it's more like an autofill.
@@ -561,17 +601,33 @@ InputBase.propTypes /* remove-proptypes */ = {
* If `true`, the `input` element is focused during the first mount.
*/
autoFocus: PropTypes.bool,
+ /**
+ * @ignore
+ */
+ children: PropTypes.node,
/**
* Override or extend the styles applied to the component.
*/
classes: PropTypes.object,
+ /**
+ * @ignore
+ */
+ className: PropTypes.string,
/**
* The color of the component.
* It supports both default and custom theme colors, which can be added as shown in the
* [palette customization guide](https://mui.com/material-ui/customization/palette/#custom-colors).
* The prop defaults to the value (`'primary'`) inherited from the parent FormControl component.
*/
- color: PropTypes.oneOf(['error', 'info', 'primary', 'secondary', 'success', 'warning']),
+ color: PropTypes.oneOf([
+ 'error',
+ 'info',
+ 'primary',
+ 'secondary',
+ 'success',
+ 'tertiary',
+ 'warning',
+ ]),
/**
* The default value. Use when the component is not controlled.
*/
@@ -608,6 +664,7 @@ InputBase.propTypes /* remove-proptypes */ = {
/**
* The component used for the input node.
* Either a string to use a HTML element or a component.
+ * @default 'input'
*/
inputComponent: PropTypes.elementType,
/**
@@ -650,6 +707,10 @@ InputBase.propTypes /* remove-proptypes */ = {
* You can pull out the new value by accessing `event.target.value` (string).
*/
onChange: PropTypes.func,
+ /**
+ * @ignore
+ */
+ onClick: PropTypes.func,
/**
* @ignore
*/
@@ -726,7 +787,30 @@ InputBase.propTypes /* remove-proptypes */ = {
* Type of the `input` element. It should be [a valid HTML5 input type](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Form_%3Cinput%3E_types).
* @default 'text'
*/
- type: PropTypes.string,
+ type: PropTypes /* @typescript-to-proptypes-ignore */.oneOf([
+ 'button',
+ 'checkbox',
+ 'color',
+ 'date',
+ 'datetime-local',
+ 'email',
+ 'file',
+ 'hidden',
+ 'image',
+ 'month',
+ 'number',
+ 'password',
+ 'radio',
+ 'range',
+ 'reset',
+ 'search',
+ 'submit',
+ 'tel',
+ 'text',
+ 'time',
+ 'url',
+ 'week',
+ ]),
/**
* The value of the `input` element, required for a controlled component.
*/
diff --git a/packages/mui-material-next/src/InputBase/InputBase.types.ts b/packages/mui-material-next/src/InputBase/InputBase.types.ts
index fd4f8df7bcc570..41767dc2dc2238 100644
--- a/packages/mui-material-next/src/InputBase/InputBase.types.ts
+++ b/packages/mui-material-next/src/InputBase/InputBase.types.ts
@@ -12,57 +12,7 @@ export interface InputBasePropsColorOverrides {}
export interface InputBaseRootSlotPropsOverrides {}
export interface InputBaseInputSlotPropsOverrides {}
-export interface SingleLineInputProps {
- /**
- * Maximum number of rows to display when multiline option is set to true.
- */
- maxRows?: undefined;
- /**
- * Minimum number of rows to display when multiline option is set to true.
- */
- minRows?: undefined;
- /**
- * If `true`, a `textarea` element is rendered.
- * @default false
- */
- multiline?: false;
- /**
- * Number of rows to display when multiline option is set to true.
- */
- rows?: undefined;
- /**
- * Type of the `input` element. It should be [a valid HTML5 input type](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Form_%3Cinput%3E_types).
- * @default 'text'
- */
- type?: React.HTMLInputTypeAttribute;
-}
-
-export interface MultiLineInputProps {
- /**
- * Maximum number of rows to display when multiline option is set to true.
- */
- maxRows?: number;
- /**
- * Minimum number of rows to display when multiline option is set to true.
- */
- minRows?: number;
- /**
- * If `true`, a `textarea` element is rendered.
- * @default false
- */
- multiline: true;
- /**
- * Number of rows to display when multiline option is set to true.
- */
- rows?: number;
- /**
- * Type of the `input` element. It should be [a valid HTML5 input type](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Form_%3Cinput%3E_types).
- * @default 'text'
- */
- type?: undefined;
-}
-
-export type InputBaseOwnProps = (SingleLineInputProps | MultiLineInputProps) & {
+export type InputBaseOwnProps = {
'aria-describedby'?: string;
/**
* This prop helps users to fill forms faster, especially on mobile devices.
@@ -85,7 +35,7 @@ export type InputBaseOwnProps = (SingleLineInputProps | MultiLineInputProps) & {
* The prop defaults to the value (`'primary'`) inherited from the parent FormControl component.
*/
color?: OverridableStringUnion<
- 'primary' | 'secondary' | 'error' | 'info' | 'success' | 'warning',
+ 'primary' | 'secondary' | 'tertiary' | 'error' | 'info' | 'success' | 'warning',
InputBasePropsColorOverrides
>;
/**
@@ -131,11 +81,6 @@ export type InputBaseOwnProps = (SingleLineInputProps | MultiLineInputProps) & {
* The prop defaults to the value (`'none'`) inherited from the parent FormControl component.
*/
margin?: 'dense' | 'none';
- /**
- * If `true`, a [TextareaAutosize](/material-ui/react-textarea-autosize/) element is rendered.
- * @default false
- */
- multiline?: boolean;
/**
* Name attribute of the `input` element.
*/
@@ -183,18 +128,6 @@ export type InputBaseOwnProps = (SingleLineInputProps | MultiLineInputProps) & {
required?: boolean;
startAdornment?: React.ReactNode;
}) => React.ReactNode;
- /**
- * Number of rows to display when multiline option is set to true.
- */
- rows?: string | number;
- /**
- * Maximum number of rows to display when multiline option is set to true.
- */
- maxRows?: string | number;
- /**
- * Minimum number of rows to display when multiline option is set to true.
- */
- minRows?: string | number;
/**
* The size of the component.
*/
@@ -221,15 +154,32 @@ export type InputBaseOwnProps = (SingleLineInputProps | MultiLineInputProps) & {
* The system prop that allows defining system overrides as well as additional CSS styles.
*/
sx?: SxProps;
- /**
- * Type of the `input` element. It should be [a valid HTML5 input type](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Form_%3Cinput%3E_types).
- * @default 'text'
- */
- type?: string;
/**
* The value of the `input` element, required for a controlled component.
*/
value?: unknown;
+ /**
+ * Maximum number of rows to display when multiline option is set to true.
+ */
+ maxRows?: number;
+ /**
+ * Minimum number of rows to display when multiline option is set to true.
+ */
+ minRows?: number;
+ /**
+ * If `true`, a `textarea` element is rendered.
+ * @default false
+ */
+ multiline?: boolean;
+ /**
+ * Number of rows to display when multiline option is set to true.
+ */
+ rows?: number;
+ /**
+ * Type of the `input` element. It should be [a valid HTML5 input type](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Form_%3Cinput%3E_types).
+ * @default 'text'
+ */
+ type?: React.HTMLInputTypeAttribute;
};
export interface InputBaseSlots {
diff --git a/packages/mui-material/src/InputLabel/InputLabel.test.js b/packages/mui-material/src/InputLabel/InputLabel.test.js
index 76a4d338e2e352..f2b2678faa73d9 100644
--- a/packages/mui-material/src/InputLabel/InputLabel.test.js
+++ b/packages/mui-material/src/InputLabel/InputLabel.test.js
@@ -128,7 +128,7 @@ describe('', () => {
root: (props) => {
return {
...(props.ownerState.focused === true && {
- fontWeight: '700',
+ mixBlendMode: 'darken',
}),
};
},
@@ -140,14 +140,16 @@ describe('', () => {
const { getByText } = render(
- Bold Test Label
+ Test Label
,
);
- const label = getByText('Bold Test Label');
+ const label = getByText('Test Label');
- expect(getComputedStyle(label).fontWeight).to.equal('700');
+ expect(label).to.toHaveComputedStyle({
+ mixBlendMode: 'darken',
+ });
});
});
});