diff --git a/docs/pages/api-docs/form-control.json b/docs/pages/api-docs/form-control.json index 7b1ab4edb661be..4c59fe8ea36f4b 100644 --- a/docs/pages/api-docs/form-control.json +++ b/docs/pages/api-docs/form-control.json @@ -24,6 +24,7 @@ "type": { "name": "enum", "description": "'medium'
| 'small'" }, "default": "'medium'" }, + "sx": { "type": { "name": "object" } }, "variant": { "type": { "name": "enum", @@ -43,6 +44,6 @@ "filename": "/packages/material-ui/src/FormControl/FormControl.js", "inheritance": null, "demos": "", - "styledComponent": false, + "styledComponent": true, "cssComponent": false } diff --git a/docs/translations/api-docs/form-control/form-control.json b/docs/translations/api-docs/form-control/form-control.json index 02575bd9d30a5e..885343fdeabe7b 100644 --- a/docs/translations/api-docs/form-control/form-control.json +++ b/docs/translations/api-docs/form-control/form-control.json @@ -13,6 +13,7 @@ "margin": "If dense or normal, will adjust vertical spacing of this and contained components.", "required": "If true, the label will indicate that the input is required.", "size": "The size of the component.", + "sx": "The system prop that allows defining system overrides as well as additional CSS styles. See the `sx` page for more details.", "variant": "The variant to use." }, "classDescriptions": { diff --git a/packages/material-ui/src/FormControl/FormControl.d.ts b/packages/material-ui/src/FormControl/FormControl.d.ts index 7956c4371a44ca..b51b2ceb62282d 100644 --- a/packages/material-ui/src/FormControl/FormControl.d.ts +++ b/packages/material-ui/src/FormControl/FormControl.d.ts @@ -1,5 +1,7 @@ import * as React from 'react'; +import { SxProps } from '@material-ui/system'; import { OverridableComponent, OverrideProps } from '../OverridableComponent'; +import { Theme } from '../styles'; export interface FormControlTypeMap

{ props: P & { @@ -66,6 +68,10 @@ export interface FormControlTypeMap

* @default 'medium' */ size?: 'small' | 'medium'; + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx?: SxProps; /** * The variant to use. * @default 'standard' diff --git a/packages/material-ui/src/FormControl/FormControl.js b/packages/material-ui/src/FormControl/FormControl.js index 5a7c8d60f25c7a..e0da18ccfcd497 100644 --- a/packages/material-ui/src/FormControl/FormControl.js +++ b/packages/material-ui/src/FormControl/FormControl.js @@ -1,40 +1,62 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import clsx from 'clsx'; +import { deepmerge } from '@material-ui/utils'; +import { unstable_composeClasses as composeClasses } from '@material-ui/unstyled'; +import useThemeProps from '../styles/useThemeProps'; +import experimentalStyled from '../styles/experimentalStyled'; import { isFilled, isAdornedStart } from '../InputBase/utils'; -import withStyles from '../styles/withStyles'; import capitalize from '../utils/capitalize'; import isMuiElement from '../utils/isMuiElement'; import FormControlContext from './FormControlContext'; +import { getFormControlUtilityClasses } from './formControlClasses'; -export const styles = { - /* Styles applied to the root element. */ - root: { - display: 'inline-flex', - flexDirection: 'column', - position: 'relative', - // Reset fieldset default style. - minWidth: 0, - padding: 0, - margin: 0, - border: 0, - verticalAlign: 'top', // Fix alignment issue on Safari. +const overridesResolver = ({ styleProps }, styles) => { + return deepmerge(styles.root || {}, { + ...styles[`margin${capitalize(styleProps.margin)}`], + ...(styleProps.fullWidth && styles.fullWidth), + }); +}; + +const useUtilityClasses = (styleProps) => { + const { classes, margin, fullWidth } = styleProps; + const slots = { + root: ['root', `margin${capitalize(margin)}`, fullWidth && 'fullWidth'], + }; + + return composeClasses(slots, getFormControlUtilityClasses, classes); +}; + +const FormControlRoot = experimentalStyled( + 'div', + {}, + { + name: 'MuiFormControl', + slot: 'Root', + overridesResolver, }, - /* Styles applied to the root element if `margin="normal"`. */ - marginNormal: { +)(({ styleProps }) => ({ + display: 'inline-flex', + flexDirection: 'column', + position: 'relative', + // Reset fieldset default style. + minWidth: 0, + padding: 0, + margin: 0, + border: 0, + verticalAlign: 'top', // Fix alignment issue on Safari. + ...(styleProps.margin === 'normal' && { marginTop: 16, marginBottom: 8, - }, - /* Styles applied to the root element if `margin="dense"`. */ - marginDense: { + }), + ...(styleProps.margin === 'dense' && { marginTop: 8, marginBottom: 4, - }, - /* Styles applied to the root element if `fullWidth={true}`. */ - fullWidth: { + }), + ...(styleProps.fullWidth && { width: '100%', - }, -}; + }), +})); /** * Provides context such as filled/focused/error/required for form inputs. @@ -60,13 +82,13 @@ export const styles = { * ⚠️ Only one `InputBase` can be used within a FormControl because it create visual inconsistencies. * For instance, only one input can be focused at the same time, the state shouldn't be shared. */ -const FormControl = React.forwardRef(function FormControl(props, ref) { +const FormControl = React.forwardRef(function FormControl(inProps, ref) { + const props = useThemeProps({ props: inProps, name: 'MuiFormControl' }); const { children, - classes, className, color = 'primary', - component: Component = 'div', + component = 'div', disabled = false, error = false, focused: visuallyFocused, @@ -79,6 +101,22 @@ const FormControl = React.forwardRef(function FormControl(props, ref) { ...other } = props; + const styleProps = { + ...props, + color, + component, + disabled, + error, + fullWidth, + hiddenLabel, + margin, + required, + size, + variant, + }; + + const classes = useUtilityClasses(styleProps); + const [adornedStart, setAdornedStart] = React.useState(() => { // We need to iterate through the children and find the Input in order // to fully support server-side rendering. @@ -182,20 +220,15 @@ const FormControl = React.forwardRef(function FormControl(props, ref) { return ( - {children} - + ); }); @@ -268,6 +301,10 @@ FormControl.propTypes = { * @default 'medium' */ size: PropTypes.oneOf(['medium', 'small']), + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx: PropTypes.object, /** * The variant to use. * @default 'standard' @@ -275,4 +312,4 @@ FormControl.propTypes = { variant: PropTypes.oneOf(['filled', 'outlined', 'standard']), }; -export default withStyles(styles, { name: 'MuiFormControl' })(FormControl); +export default FormControl; diff --git a/packages/material-ui/src/FormControl/FormControl.test.js b/packages/material-ui/src/FormControl/FormControl.test.js index 952186260e5bd3..04f7eec0fafdd0 100644 --- a/packages/material-ui/src/FormControl/FormControl.test.js +++ b/packages/material-ui/src/FormControl/FormControl.test.js @@ -1,16 +1,16 @@ import * as React from 'react'; import { expect } from 'chai'; import { spy } from 'sinon'; -import { getClasses, createMount, describeConformance, act, createClientRender } from 'test/utils'; +import { createMount, describeConformanceV5, act, createClientRender } from 'test/utils'; import Input from '../Input'; import Select from '../Select'; import FormControl from './FormControl'; import useFormControl from './useFormControl'; +import classes from './formControlClasses'; describe('', () => { const mount = createMount(); const render = createClientRender(); - let classes; function TestComponent(props) { const context = useFormControl(); @@ -20,16 +20,15 @@ describe('', () => { return null; } - before(() => { - classes = getClasses(); - }); - - describeConformance(, () => ({ + describeConformanceV5(, () => ({ classes, inheritComponent: 'div', mount, refInstanceof: window.HTMLDivElement, testComponentPropWith: 'fieldset', + muiName: 'MuiFormControl', + testVariantProps: { margin: 'dense' }, + skip: ['componentsProp'], })); describe('initial state', () => { diff --git a/packages/material-ui/src/FormControl/formControlClasses.d.ts b/packages/material-ui/src/FormControl/formControlClasses.d.ts new file mode 100644 index 00000000000000..b081c8840a02df --- /dev/null +++ b/packages/material-ui/src/FormControl/formControlClasses.d.ts @@ -0,0 +1,13 @@ +export interface FormControlClasses { + root: string; + marginNone: string; + marginNormal: string; + marginDense: string; + fullWidth: string; +} + +declare const formControlClasses: FormControlClasses; + +export function getFormControlUtilityClasses(slot: string): string; + +export default formControlClasses; diff --git a/packages/material-ui/src/FormControl/formControlClasses.js b/packages/material-ui/src/FormControl/formControlClasses.js new file mode 100644 index 00000000000000..afd2e623acd5ba --- /dev/null +++ b/packages/material-ui/src/FormControl/formControlClasses.js @@ -0,0 +1,15 @@ +import { generateUtilityClasses, generateUtilityClass } from '@material-ui/unstyled'; + +export function getFormControlUtilityClasses(slot) { + return generateUtilityClass('MuiFormControl', slot); +} + +const formControlClasses = generateUtilityClasses('MuiFormControl', [ + 'root', + 'marginNone', + 'marginNormal', + 'marginDense', + 'fullWidth', +]); + +export default formControlClasses;