diff --git a/docs/pages/api-docs/outlined-input.json b/docs/pages/api-docs/outlined-input.json index 78e1975ce8ea9a..d6ae6faadee1b2 100644 --- a/docs/pages/api-docs/outlined-input.json +++ b/docs/pages/api-docs/outlined-input.json @@ -27,6 +27,7 @@ "required": { "type": { "name": "bool" } }, "rows": { "type": { "name": "union", "description": "number
| string" } }, "startAdornment": { "type": { "name": "node" } }, + "sx": { "type": { "name": "object" } }, "type": { "type": { "name": "string" }, "default": "'text'" }, "value": { "type": { "name": "any" } } }, @@ -57,6 +58,6 @@ "filename": "/packages/material-ui/src/OutlinedInput/OutlinedInput.js", "inheritance": { "component": "InputBase", "pathname": "/api/input-base/" }, "demos": "", - "styledComponent": false, + "styledComponent": true, "cssComponent": false } diff --git a/docs/translations/api-docs/outlined-input/outlined-input.json b/docs/translations/api-docs/outlined-input/outlined-input.json index c0beed450ac777..ebedb599a939d0 100644 --- a/docs/translations/api-docs/outlined-input/outlined-input.json +++ b/docs/translations/api-docs/outlined-input/outlined-input.json @@ -28,6 +28,7 @@ "required": "If true, the input element is required. The prop defaults to the value (false) inherited from the parent FormControl component.", "rows": "Number of rows to display when multiline option is set to true.", "startAdornment": "Start InputAdornment for this component.", + "sx": "The system prop that allows defining system overrides as well as additional CSS styles. See the `sx` page for more details.", "type": "Type of the input element. It should be a valid HTML5 input type.", "value": "The value of the input element, required for a controlled component." }, diff --git a/packages/material-ui/src/Autocomplete/Autocomplete.js b/packages/material-ui/src/Autocomplete/Autocomplete.js index d6527e4fe18416..6142e40a4862b9 100644 --- a/packages/material-ui/src/Autocomplete/Autocomplete.js +++ b/packages/material-ui/src/Autocomplete/Autocomplete.js @@ -159,7 +159,7 @@ const AutocompleteRoot = experimentalStyled( padding: '2px 4px 3px 0', }, }, - '&[class*="MuiOutlinedInput-root"]': { + '&.MuiOutlinedInput-root': { padding: 9, [`.${autocompleteClasses.hasPopupIcon}&, .${autocompleteClasses.hasClearIcon}&`]: { paddingRight: 26 + 4 + 9, @@ -167,16 +167,16 @@ const AutocompleteRoot = experimentalStyled( [`.${autocompleteClasses.hasPopupIcon}.${autocompleteClasses.hasClearIcon}&`]: { paddingRight: 52 + 4 + 9, }, - [`& .${autocompleteClasses.input}`]: { + '& .MuiOutlinedInput-input': { padding: '7.5px 4px 7.5px 6px', }, [`& .${autocompleteClasses.endAdornment}`]: { right: 9, }, }, - '&[class*="MuiOutlinedInput-root"][class*="MuiOutlinedInput-sizeSmall"]': { + '&.MuiOutlinedInput-root.MuiInputBase-sizeSmall': { padding: 6, - [`& .${autocompleteClasses.input}`]: { + '& .MuiOutlinedInput-input': { padding: '2.5px 4px 2.5px 6px', }, }, diff --git a/packages/material-ui/src/OutlinedInput/NotchedOutline.js b/packages/material-ui/src/OutlinedInput/NotchedOutline.js index 36bb1ad4386bf9..a4178113b9e345 100644 --- a/packages/material-ui/src/OutlinedInput/NotchedOutline.js +++ b/packages/material-ui/src/OutlinedInput/NotchedOutline.js @@ -1,39 +1,43 @@ import * as React from 'react'; import PropTypes from 'prop-types'; -import clsx from 'clsx'; -import withStyles from '../styles/withStyles'; import useTheme from '../styles/useTheme'; import capitalize from '../utils/capitalize'; +import experimentalStyled from '../styles/experimentalStyled'; -export const styles = (theme) => { +const NotchedOutlineRoot = experimentalStyled( + 'fieldset', + {}, + { name: 'PrivateNotchedOutline', slot: 'Root' }, +)(() => ({ + textAlign: 'left', + position: 'absolute', + bottom: 0, + right: 0, + top: -5, + left: 0, + margin: 0, + padding: '0 8px', + pointerEvents: 'none', + borderRadius: 'inherit', + borderStyle: 'solid', + borderWidth: 1, + overflow: 'hidden', +})); +const NotchedOutlineLegend = experimentalStyled( + 'legend', + {}, + { name: 'PrivateNotchedOutline', slot: 'Legend' }, +)(({ styleProps, theme }) => { return { - /* Styles applied to the root element. */ - root: { - textAlign: 'left', - position: 'absolute', - bottom: 0, - right: 0, - top: -5, - left: 0, - margin: 0, - padding: '0 8px', - pointerEvents: 'none', - borderRadius: 'inherit', - borderStyle: 'solid', - borderWidth: 1, - overflow: 'hidden', - }, - /* Styles applied to the legend element when `labelWidth` is provided. */ - legend: { + ...(styleProps.label === undefined && { padding: 0, lineHeight: '11px', // sync with `height` in `legend` styles transition: theme.transitions.create('width', { duration: 150, easing: theme.transitions.easing.easeOut, }), - }, - /* Styles applied to the legend element. */ - legendLabelled: { + }), + ...(styleProps.label !== undefined && { display: 'block', width: 'auto', padding: 0, @@ -50,18 +54,17 @@ export const styles = (theme) => { paddingRight: 5, display: 'inline-block', }, - }, - /* Styles applied to the legend element is notched. */ - legendNotched: { - maxWidth: 1000, - transition: theme.transitions.create('max-width', { - duration: 100, - easing: theme.transitions.easing.easeOut, - delay: 50, + ...(styleProps.notched && { + maxWidth: 1000, + transition: theme.transitions.create('max-width', { + duration: 100, + easing: theme.transitions.easing.easeOut, + delay: 50, + }), }), - }, + }), }; -}; +}); /** * @ignore - internal component. @@ -79,21 +82,22 @@ const NotchedOutline = React.forwardRef(function NotchedOutline(props, ref) { } = props; const theme = useTheme(); const align = theme.direction === 'rtl' ? 'right' : 'left'; - + const styleProps = { + ...props, + notched, + label, + }; if (label !== undefined) { return ( -
- + {/* Use the nominal use case of the legend, avoid rendering artefacts. */} {label ? ( {label} @@ -102,8 +106,8 @@ const NotchedOutline = React.forwardRef(function NotchedOutline(props, ref) { // eslint-disable-next-line react/no-danger )} - -
+ + ); } @@ -111,18 +115,19 @@ const NotchedOutline = React.forwardRef(function NotchedOutline(props, ref) { // TODO remove this branch return ( -
- - -
+ + ); }); @@ -171,4 +176,4 @@ NotchedOutline.propTypes = { style: PropTypes.object, }; -export default withStyles(styles, { name: 'PrivateNotchedOutline' })(NotchedOutline); +export default NotchedOutline; diff --git a/packages/material-ui/src/OutlinedInput/NotchedOutline.test.js b/packages/material-ui/src/OutlinedInput/NotchedOutline.test.js index 40b4eadffd2de8..0308c07f3a5967 100644 --- a/packages/material-ui/src/OutlinedInput/NotchedOutline.test.js +++ b/packages/material-ui/src/OutlinedInput/NotchedOutline.test.js @@ -1,23 +1,18 @@ import * as React from 'react'; import { expect } from 'chai'; -import { getClasses, createClientRender } from 'test/utils'; +import { createClientRender } from 'test/utils'; import { ThemeProvider, createMuiTheme } from '@material-ui/core/styles'; import NotchedOutline from './NotchedOutline'; describe('', () => { const render = createClientRender(); - let classes; const defaultProps = { labelWidth: 36, notched: true, label: 'My label', }; - before(() => { - classes = getClasses(); - }); - it('should pass props', () => { const { container } = render( ', () => { expect(container.querySelector('fieldset')).to.have.class('notched-outline'); expect(container.querySelector('fieldset').style.width).to.equal('17px'); - expect(container.querySelector('legend')).to.have.class(classes.legendNotched); }); it('should set alignment rtl', () => { diff --git a/packages/material-ui/src/OutlinedInput/OutlinedInput.d.ts b/packages/material-ui/src/OutlinedInput/OutlinedInput.d.ts index 88f94f8984d30a..0f26b936be4ed7 100644 --- a/packages/material-ui/src/OutlinedInput/OutlinedInput.d.ts +++ b/packages/material-ui/src/OutlinedInput/OutlinedInput.d.ts @@ -1,5 +1,6 @@ import * as React from 'react'; -import { InternalStandardProps as StandardProps } from '..'; +import { SxProps } from '@material-ui/system'; +import { InternalStandardProps as StandardProps, Theme } from '..'; import { InputBaseProps } from '../InputBase'; export interface OutlinedInputProps extends StandardProps { @@ -53,6 +54,10 @@ export interface OutlinedInputProps extends StandardProps { * If `true`, the outline is notched to accommodate the label. */ notched?: boolean; + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx?: SxProps; } export type OutlinedInputClassKey = keyof NonNullable; diff --git a/packages/material-ui/src/OutlinedInput/OutlinedInput.js b/packages/material-ui/src/OutlinedInput/OutlinedInput.js index 43c328f5c87ee8..4176d7b5846a36 100644 --- a/packages/material-ui/src/OutlinedInput/OutlinedInput.js +++ b/packages/material-ui/src/OutlinedInput/OutlinedInput.js @@ -1,111 +1,143 @@ import * as React from 'react'; import PropTypes from 'prop-types'; -import clsx from 'clsx'; -import { refType } from '@material-ui/utils'; -import InputBase from '../InputBase'; + +import { deepmerge, refType } from '@material-ui/utils'; +import { unstable_composeClasses as composeClasses } from '@material-ui/unstyled'; import NotchedOutline from './NotchedOutline'; -import withStyles from '../styles/withStyles'; +import experimentalStyled, { shouldForwardProp } from '../styles/experimentalStyled'; +import outlinedInputClasses, { getOutlinedInputUtilityClass } from './outlinedInputClasses'; +import InputBase, { + overridesResolver as inputBaseOverridesResolver, + InputBaseRoot, + InputBaseComponent as InputBaseInput, +} from '../InputBase/InputBase'; +import useThemeProps from '../styles/useThemeProps'; -export const styles = (theme) => { - const borderColor = - theme.palette.mode === 'light' ? 'rgba(0, 0, 0, 0.23)' : 'rgba(255, 255, 255, 0.23)'; +const overridesResolver = (props, styles) => { + return deepmerge(inputBaseOverridesResolver(props, styles), { + ...styles.notchedOutline, + }); +}; - return { - /* Styles applied to the root element. */ - root: { +const useUtilityClasses = (styleProps) => { + const { classes } = styleProps; + + const slots = { + root: ['root'], + notchedOutline: ['notchedOutline'], + input: ['input'], + }; + + return composeClasses(slots, getOutlinedInputUtilityClass, classes); +}; + +const OutlinedInputRoot = experimentalStyled( + InputBaseRoot, + { shouldForwardProp: (prop) => shouldForwardProp(prop) || prop === 'classes' }, + { name: 'MuiOutlinedInput', slot: 'Root', overridesResolver }, +)( + ({ theme, styleProps }) => { + const borderColor = + theme.palette.mode === 'light' ? 'rgba(0, 0, 0, 0.23)' : 'rgba(255, 255, 255, 0.23)'; + return { position: 'relative', borderRadius: theme.shape.borderRadius, - '&:hover $notchedOutline': { - borderColor: theme.palette.text.primary, + '&:hover': { + [`& .${outlinedInputClasses.notchedOutline}`]: { + borderColor: theme.palette.text.primary, + }, }, // Reset on touch devices, it doesn't add specificity '@media (hover: none)': { - '&:hover $notchedOutline': { - borderColor, + '&:hover': { + [`& .${outlinedInputClasses.notchedOutline}`]: { + borderColor, + }, }, }, - '&$focused $notchedOutline': { - borderColor: theme.palette.primary.main, - borderWidth: 2, - }, - '&$error $notchedOutline': { - borderColor: theme.palette.error.main, - }, - '&$disabled $notchedOutline': { - borderColor: theme.palette.action.disabled, - }, - }, - /* Styles applied to the root element if the color is secondary. */ - colorSecondary: { - '&$focused $notchedOutline': { - borderColor: theme.palette.secondary.main, + [`&.Mui-focused`]: { + [`& .${outlinedInputClasses.notchedOutline}`]: { + borderColor: theme.palette.primary.main, + borderWidth: 2, + }, }, - '&$error $notchedOutline': { - // To remove once we migrate to emotion - borderColor: theme.palette.error.main, + '&.Mui-error': { + [`& .${outlinedInputClasses.notchedOutline}`]: { + borderColor: theme.palette.error.main, + }, }, - }, - /* Styles applied to the root element if the component is focused. */ - focused: {}, - /* Styles applied to the root element if `disabled={true}`. */ - disabled: {}, - /* Styles applied to the root element if `startAdornment` is provided. */ - adornedStart: { - paddingLeft: 14, - }, - /* Styles applied to the root element if `endAdornment` is provided. */ - adornedEnd: { - paddingRight: 14, - }, - /* Pseudo-class applied to the root element if `error={true}`. */ - error: {}, - /* Styles applied to the input element if `size="small"`. */ - sizeSmall: {}, - /* Styles applied to the root element if `multiline={true}`. */ - multiline: { - padding: '16.5px 14px', - '&$sizeSmall': { - paddingTop: 10.5, - paddingBottom: 10.5, + '&.Mui-disabled': { + [`& .${outlinedInputClasses.notchedOutline}`]: { + borderColor: theme.palette.action.disabled, + }, }, - }, - /* Styles applied to the NotchedOutline element. */ - notchedOutline: { - borderColor, - }, - /* Styles applied to the input element. */ - input: { - padding: '16.5px 14px', - '&:-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', - borderRadius: 'inherit', + ...(styleProps.startAdornment && { + paddingLeft: 14, + }), + ...(styleProps.endAdornment && { + paddingRight: 14, + }), + ...(styleProps.multiline && { + padding: '16.5px 14px', + ...(styleProps.size === 'small' && { + paddingTop: 10.5, + paddingBottom: 10.5, + }), + }), + }; + }, + ({ styleProps, theme }) => ({ + ...(styleProps.color === 'secondary' && { + '&.Mui-focused': { + [`& .${outlinedInputClasses.notchedOutline}`]: { + borderColor: theme.palette.secondary.main, + }, }, - }, - /* Styles applied to the input element if `size="small"`. */ - inputSizeSmall: { - paddingTop: 8.5, - paddingBottom: 8.5, - }, - /* Styles applied to the input element if `multiline={true}`. */ - inputMultiline: { - padding: 0, - }, - /* Styles applied to the input element if `startAdornment` is provided. */ - inputAdornedStart: { - paddingLeft: 0, - }, - /* Styles applied to the input element if `endAdornment` is provided. */ - inputAdornedEnd: { - paddingRight: 0, - }, - }; -}; + }), + }), +); + +const NotchedOutlineRoot = experimentalStyled( + NotchedOutline, + {}, + { name: 'MuiOutlinedInput', slot: 'NotchedOutline' }, +)(({ theme }) => { + const borderColor = + theme.palette.mode === 'light' ? 'rgba(0, 0, 0, 0.23)' : 'rgba(255, 255, 255, 0.23)'; + return { borderColor }; +}); + +const OutlinedInputInput = experimentalStyled( + InputBaseInput, + { shouldForwardProp: (prop) => shouldForwardProp(prop) || prop === 'classes' }, + { name: 'MuiOutlinedInput', slot: 'Input' }, +)(({ theme, styleProps }) => ({ + padding: '16.5px 14px', + '&:-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', + borderRadius: 'inherit', + }, + ...(styleProps.size === 'small' && { + paddingTop: 8.5, + paddingBottom: 8.5, + }), + ...(styleProps.multiline && { + padding: 0, + }), + ...(styleProps.startAdornment && { + paddingLeft: 0, + }), + ...(styleProps.endAdornment && { + paddingRight: 0, + }), +})); + +const OutlinedInput = React.forwardRef(function OutlinedInput(inProps, ref) { + const props = useThemeProps({ props: inProps, name: 'MuiOutlinedInput' }); -const OutlinedInput = React.forwardRef(function OutlinedInput(props, ref) { const { - classes, fullWidth = false, inputComponent = 'input', label, @@ -116,10 +148,23 @@ const OutlinedInput = React.forwardRef(function OutlinedInput(props, ref) { ...other } = props; + const styleProps = { + ...props, + fullWidth, + inputComponent, + labelWidth, + multiline, + type, + }; + + const classes = useUtilityClasses(props); + return ( ( - )} - classes={{ - ...classes, - root: clsx(classes.root, classes.underline), - notchedOutline: null, - }} + classes={classes} fullWidth={fullWidth} inputComponent={inputComponent} multiline={multiline} @@ -278,6 +319,10 @@ OutlinedInput.propTypes = { * Start `InputAdornment` for this component. */ startAdornment: PropTypes.node, + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx: PropTypes.object, /** * 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' @@ -291,4 +336,4 @@ OutlinedInput.propTypes = { OutlinedInput.muiName = 'Input'; -export default withStyles(styles, { name: 'MuiOutlinedInput' })(OutlinedInput); +export default OutlinedInput; diff --git a/packages/material-ui/src/OutlinedInput/OutlinedInput.test.js b/packages/material-ui/src/OutlinedInput/OutlinedInput.test.js index c10285622e618c..70d543955747d6 100644 --- a/packages/material-ui/src/OutlinedInput/OutlinedInput.test.js +++ b/packages/material-ui/src/OutlinedInput/OutlinedInput.test.js @@ -1,24 +1,24 @@ import * as React from 'react'; import { expect } from 'chai'; -import { getClasses, createMount, createClientRender, describeConformance } from 'test/utils'; +import { createMount, createClientRender, describeConformanceV5 } from 'test/utils'; import OutlinedInput from './OutlinedInput'; import InputBase from '../InputBase'; +import classes from './outlinedInputClasses'; describe('', () => { - let classes; const mount = createMount(); const render = createClientRender(); - before(() => { - classes = getClasses(); - }); - - describeConformance(, () => ({ + describeConformanceV5(, () => ({ classes, inheritComponent: InputBase, mount, refInstanceof: window.HTMLDivElement, - skip: ['componentProp'], + muiName: 'MuiOutlinedInput', + testDeepOverrides: { slotName: 'input', slotClassName: classes.input }, + testVariantProps: { variant: 'contained', fullWidth: true }, + testStateOverrides: { prop: 'size', value: 'small', styleKey: 'sizeSmall' }, + skip: ['componentProp', 'componentsProp'], })); it('should render a NotchedOutline', () => { diff --git a/packages/material-ui/src/OutlinedInput/index.d.ts b/packages/material-ui/src/OutlinedInput/index.d.ts index 330ce05397b8f7..56e72dcf01e7e7 100644 --- a/packages/material-ui/src/OutlinedInput/index.d.ts +++ b/packages/material-ui/src/OutlinedInput/index.d.ts @@ -1,2 +1,4 @@ export { default } from './OutlinedInput'; export * from './OutlinedInput'; +export { default as outlinedInputClasses } from './outlinedInputClasses'; +export * from './outlinedInputClasses'; diff --git a/packages/material-ui/src/OutlinedInput/index.js b/packages/material-ui/src/OutlinedInput/index.js index 56d43aaaaf69e4..09bd8d68e617d9 100644 --- a/packages/material-ui/src/OutlinedInput/index.js +++ b/packages/material-ui/src/OutlinedInput/index.js @@ -1 +1,3 @@ export { default } from './OutlinedInput'; +export { default as outlinedInputClasses } from './outlinedInputClasses'; +export * from './outlinedInputClasses'; diff --git a/packages/material-ui/src/OutlinedInput/outlinedInputClasses.d.ts b/packages/material-ui/src/OutlinedInput/outlinedInputClasses.d.ts new file mode 100644 index 00000000000000..4f412f2e938137 --- /dev/null +++ b/packages/material-ui/src/OutlinedInput/outlinedInputClasses.d.ts @@ -0,0 +1,7 @@ +import { OutlinedInputClassKey } from './OutlinedInput'; + +declare const outlinedInputClasses: Record; + +export function getOutlinedInputUtilityClasses(slot: string): string; + +export default outlinedInputClasses; diff --git a/packages/material-ui/src/OutlinedInput/outlinedInputClasses.js b/packages/material-ui/src/OutlinedInput/outlinedInputClasses.js new file mode 100644 index 00000000000000..a926d5b86b69e6 --- /dev/null +++ b/packages/material-ui/src/OutlinedInput/outlinedInputClasses.js @@ -0,0 +1,13 @@ +import { generateUtilityClasses, generateUtilityClass } from '@material-ui/unstyled'; + +export function getOutlinedInputUtilityClass(slot) { + return generateUtilityClass('MuiOutlinedInput', slot); +} + +const outlinedInputClasses = generateUtilityClasses('MuiOutlinedInput', [ + 'root', + 'notchedOutline', + 'input', +]); + +export default outlinedInputClasses; diff --git a/packages/material-ui/src/TextField/TextField.test.js b/packages/material-ui/src/TextField/TextField.test.js index 0a189e3b9e354f..979ad67f66c002 100644 --- a/packages/material-ui/src/TextField/TextField.test.js +++ b/packages/material-ui/src/TextField/TextField.test.js @@ -2,10 +2,10 @@ import * as React from 'react'; import { expect } from 'chai'; import { getClasses, createMount, createClientRender, describeConformance } from 'test/utils'; import FormControl from '../FormControl'; -import OutlinedInput from '../OutlinedInput'; import TextField from './TextField'; import MenuItem from '../MenuItem'; import { inputBaseClasses } from '../InputBase'; +import { outlinedInputClasses } from '../OutlinedInput'; describe('', () => { let classes; @@ -126,7 +126,6 @@ describe('', () => { }); it('should set shrink prop on outline from label', () => { - const outlinedInputClasses = getClasses(); const { container } = render(); expect(container.querySelector('fieldset')).to.have.class(