From 564302849824ed2104d4889ac734a040cae5d7f2 Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Tue, 3 Oct 2023 22:57:51 +0800 Subject: [PATCH 01/34] Convert to TS --- .../src/FilledInput/FilledInput.d.ts | 41 --------- .../{FilledInput.js => FilledInput.tsx} | 88 ++++++++++++------- .../src/FilledInput/FilledInput.types.ts | 52 +++++++++++ .../src/FilledInput/index.d.ts | 5 -- .../src/InputBase/InputBase.tsx | 71 ++++++++------- 5 files changed, 149 insertions(+), 108 deletions(-) delete mode 100644 packages/mui-material-next/src/FilledInput/FilledInput.d.ts rename packages/mui-material-next/src/FilledInput/{FilledInput.js => FilledInput.tsx} (87%) create mode 100644 packages/mui-material-next/src/FilledInput/FilledInput.types.ts delete mode 100644 packages/mui-material-next/src/FilledInput/index.d.ts 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.js b/packages/mui-material-next/src/FilledInput/FilledInput.tsx similarity index 87% rename from packages/mui-material-next/src/FilledInput/FilledInput.js rename to packages/mui-material-next/src/FilledInput/FilledInput.tsx index 1c4c18c2fbaa59..f2306578cc0708 100644 --- a/packages/mui-material-next/src/FilledInput/FilledInput.js +++ b/packages/mui-material-next/src/FilledInput/FilledInput.tsx @@ -1,20 +1,20 @@ '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 { +import { OverrideProps } from '@mui/types'; +import InputBase, { rootOverridesResolver as inputBaseRootOverridesResolver, inputOverridesResolver as inputBaseInputOverridesResolver, -} from '@mui/material/InputBase/InputBase'; -import InputBase from '../InputBase'; +} from '../InputBase'; import styled, { rootShouldForwardProp } from '../styles/styled'; import useThemeProps from '../styles/useThemeProps'; 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 = { @@ -41,7 +41,7 @@ const FilledInputRoot = styled(InputBaseRoot, { !ownerState.disableUnderline && styles.underline, ]; }, -})(({ theme, ownerState }) => { +})<{ ownerState: FilledInputOwnerState }>(({ 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)'; @@ -145,20 +145,22 @@ const FilledInputInput = styled(InputBaseInput, { name: 'MuiFilledInput', slot: 'Input', overridesResolver: inputBaseInputOverridesResolver, -})(({ theme, ownerState }) => ({ +})<{ ownerState: FilledInputOwnerState }>(({ 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': { + 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', @@ -199,13 +201,13 @@ const FilledInputInput = styled(InputBaseInput, { }), })); -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', @@ -218,37 +220,58 @@ const FilledInput = React.forwardRef(function FilledInput(inProps, ref) { const ownerState = { ...props, + disableUnderline, fullWidth, inputComponent, multiline, type, }; - const classes = useUtilityClasses(props); - const filledInputComponentsProps = { root: { ownerState }, input: { ownerState } }; + const classes = useUtilityClasses(ownerState); - const componentsProps = - slotProps ?? componentsPropsProp - ? deepmerge(slotProps ?? componentsPropsProp, filledInputComponentsProps) - : filledInputComponentsProps; + const RootSlot = slots.root ?? FilledInputRoot; + const InputSlot = slots.input ?? FilledInputInput; - const RootSlot = slots.root ?? components.Root ?? FilledInputRoot; - const InputSlot = slots.input ?? components.Input ?? FilledInputInput; + if (multiline) { + return ( + + ); + } return ( ); -}); +}) as FilledInputComponent; + +interface FilledInputComponent { + ( + props: { + /** + * The component used for the input node. + * Either a string to use a HTML element or a component. + */ + inputComponent?: C; + } & OverrideProps, + ): JSX.Element | null; + propTypes?: any; +} FilledInput.propTypes /* remove-proptypes */ = { // ----------------------------- Warning -------------------------------- @@ -451,6 +474,7 @@ FilledInput.propTypes /* remove-proptypes */ = { value: PropTypes.any, }; +// @ts-ignore - internal logic to integrate with FormControl FilledInput.muiName = 'Input'; export default FilledInput; 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..aa76a32c9cc3d2 --- /dev/null +++ b/packages/mui-material-next/src/FilledInput/FilledInput.types.ts @@ -0,0 +1,52 @@ +import { SxProps } from '@mui/system'; +// eslint-disable-next-line no-restricted-imports +import { InternalStandardProps as StandardProps } from '@mui/material'; +import { PolymorphicProps } from '@mui/base/utils'; +import { Simplify } from '@mui/types'; +import { Theme } from '../styles/Theme.types'; +import { InputBaseProps } from '../InputBase/InputBase.types'; +import { FilledInputClasses } from './filledInputClasses'; + +export type FilledInputOwnProps = 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; +}; + +export interface FilledInputTypeMap< + AdditionalProps = {}, + RootComponentType extends React.ElementType = 'div', +> { + props: FilledInputOwnProps & AdditionalProps; + defaultComponent: RootComponentType; +} + +export type FilledInputProps< + RootComponentType extends React.ElementType = FilledInputTypeMap['defaultComponent'], +> = PolymorphicProps, RootComponentType>; + +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/InputBase/InputBase.tsx b/packages/mui-material-next/src/InputBase/InputBase.tsx index fa5000b80ada3a..b0d919b3d388e0 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 { 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; From 9a733e8f47163130beb36e29135faf85542d7e32 Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Mon, 9 Oct 2023 23:02:09 +0800 Subject: [PATCH 02/34] Fix types --- .../src/InputBase/InputBase.types.ts | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/packages/mui-material-next/src/InputBase/InputBase.types.ts b/packages/mui-material-next/src/InputBase/InputBase.types.ts index fd4f8df7bcc570..a1a3d7c85d3dd1 100644 --- a/packages/mui-material-next/src/InputBase/InputBase.types.ts +++ b/packages/mui-material-next/src/InputBase/InputBase.types.ts @@ -131,11 +131,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 +178,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,11 +204,6 @@ 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. */ From a3c761a4bb61b8c6c41d3e02160a2d705cbac7ed Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Wed, 4 Oct 2023 20:01:24 +0800 Subject: [PATCH 03/34] Fix styles --- .../src/FilledInput/FilledInput.tsx | 177 ++++++++++-------- 1 file changed, 102 insertions(+), 75 deletions(-) diff --git a/packages/mui-material-next/src/FilledInput/FilledInput.tsx b/packages/mui-material-next/src/FilledInput/FilledInput.tsx index f2306578cc0708..69d9f3ff655e40 100644 --- a/packages/mui-material-next/src/FilledInput/FilledInput.tsx +++ b/packages/mui-material-next/src/FilledInput/FilledInput.tsx @@ -8,8 +8,8 @@ import InputBase, { rootOverridesResolver as inputBaseRootOverridesResolver, inputOverridesResolver as inputBaseInputOverridesResolver, } from '../InputBase'; -import styled, { rootShouldForwardProp } from '../styles/styled'; -import useThemeProps from '../styles/useThemeProps'; +import { useThemeProps, styled } from '../styles'; +import { rootShouldForwardProp } from '../styles/styled'; import filledInputClasses, { getFilledInputUtilityClass } from './filledInputClasses'; import { InputBaseRoot, InputBaseInput } from '../InputBase/InputBase'; import { FilledInputOwnerState, FilledInputProps, FilledInputTypeMap } from './FilledInput.types'; @@ -42,38 +42,51 @@ const FilledInputRoot = styled(InputBaseRoot, { ]; }, })<{ ownerState: FilledInputOwnerState }>(({ 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)'; + const { vars: tokens } = theme; + return { + '--md-comp-filled-input-container-color': tokens.sys.color.surfaceContainerHighest, + + '--md-comp-filled-input-active-indicator-color': tokens.sys.color.onSurfaceVariant, + '--md-comp-filled-input-hover-active-indicator-color': tokens.sys.color.onSurface, + '--md-comp-filled-input-focus-active-indicator-color': + tokens.sys.color[ownerState.color ?? 'primary'], + + '--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-error-focus-active-indicator-color': tokens.sys.color.error, + + '--md-comp-filled-input-disabled-container-color': tokens.sys.color.onSurface, + '--md-comp-filled-input-disabled-container-opacity': 0.04, + 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, ${tokens.sys.color.onSurface} calc(${tokens.sys.state.hover.stateLayerOpacity} * 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 +107,11 @@ const FilledInputRoot = styled(InputBaseRoot, { }, [`&.${filledInputClasses.error}`]: { '&:before, &:after': { - borderBottomColor: (theme.vars || theme).palette.error.main, + borderBottomColor: tokens.sys.color.errorContainer, }, }, '&: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 +124,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,61 +154,79 @@ const FilledInputInput = styled(InputBaseInput, { name: 'MuiFilledInput', slot: 'Input', overridesResolver: inputBaseInputOverridesResolver, -})<{ ownerState: FilledInputOwnerState }>(({ theme, ownerState }) => ({ - paddingTop: 25, - paddingRight: 12, - paddingBottom: 8, - paddingLeft: 12, - ...(!theme.vars - ? { +})<{ 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', + }, + }, + } + : {}), + ...(tokens && { + '&:-webkit-autofill': { + borderTopLeftRadius: 'inherit', + borderTopRightRadius: 'inherit', + }, + }), + // TODO: investigate why describeConformance dies without this check + ...(typeof theme.getColorSchemeSelector === 'function' && { + [theme.getColorSchemeSelector('dark')]: { '&:-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', + WebkitBoxShadow: '0 0 0 100px #266798 inset', + WebkitTextFillColor: '#fff', + caretColor: '#fff', }, - } - : {}), - ...(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.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< RootComponentType extends React.ElementType, From c0a4b967cc14cd1d17e97af89e88ba984d618405 Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Wed, 4 Oct 2023 21:50:12 +0800 Subject: [PATCH 04/34] Fix describeConformance --- ...lledInput.test.js => FilledInput.test.tsx} | 27 +++++---- .../src/FilledInput/FilledInput.tsx | 58 +++++++++++++++---- 2 files changed, 63 insertions(+), 22 deletions(-) rename packages/mui-material-next/src/FilledInput/{FilledInput.test.js => FilledInput.test.tsx} (71%) diff --git a/packages/mui-material-next/src/FilledInput/FilledInput.test.js b/packages/mui-material-next/src/FilledInput/FilledInput.test.tsx similarity index 71% rename from packages/mui-material-next/src/FilledInput/FilledInput.test.js rename to packages/mui-material-next/src/FilledInput/FilledInput.test.tsx index e7ce5fde0068d7..a6a1501e714e77 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,10 @@ describe('', () => { testDeepOverrides: { slotName: 'input', slotClassName: classes.input }, testVariantProps: { variant: 'contained', fullWidth: true }, testStateOverrides: { prop: 'size', value: 'small', styleKey: 'sizeSmall' }, - testLegacyComponentsProp: true, + testLegacyComponentsProp: false, 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', @@ -29,35 +31,36 @@ describe('', () => { ], })); - it('should have the underline class', () => { + it.skip('should have the underline class', () => { const { container } = render(); const root = container.firstChild; expect(root).not.to.equal(null); }); - it('color={undefined} should not result in crash', () => { + it.skip('color={undefined} should not result in crash', () => { expect(() => { render(); }).not.toErrorDev(); }); - it('can disable the underline', () => { + it.skip('can disable the underline', () => { const { container } = render(); const root = container.firstChild; expect(root).not.to.have.class(classes.underline); }); - it('should forward classes to InputBase', () => { + it.skip('should forward classes to InputBase', () => { render(); expect(document.querySelector('.error')).not.to.equal(null); }); - it('should respects the componentsProps if passed', () => { + // eslint-disable-next-line mocha/no-skipped-tests + it.skip('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', () => { + it.skip('should respect the classes coming from InputBase', () => { render( ); } return ( ); }) as FilledInputComponent; From 30b90989a441fb6c271b4067e73fc331188ca98c Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Wed, 4 Oct 2023 22:02:02 +0800 Subject: [PATCH 05/34] Fix tests --- .../src/FilledInput/FilledInput.test.tsx | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/mui-material-next/src/FilledInput/FilledInput.test.tsx b/packages/mui-material-next/src/FilledInput/FilledInput.test.tsx index a6a1501e714e77..cf37ce0f3bd83b 100644 --- a/packages/mui-material-next/src/FilledInput/FilledInput.test.tsx +++ b/packages/mui-material-next/src/FilledInput/FilledInput.test.tsx @@ -31,25 +31,26 @@ describe('', () => { ], })); - it.skip('should have the underline class', () => { - const { container } = render(); - const root = container.firstChild; + it('should have the underline class', () => { + const { getByTestId } = render(); + const root = getByTestId('test-input'); expect(root).not.to.equal(null); + expect(root).to.have.class(classes.underline); }); - it.skip('color={undefined} should not result in crash', () => { + it('color={undefined} should not result in crash', () => { expect(() => { render(); }).not.toErrorDev(); }); - it.skip('can disable the underline', () => { - const { container } = render(); - const root = container.firstChild; + it('can disable the underline', () => { + const { getByTestId } = render(); + const root = getByTestId('test-input'); expect(root).not.to.have.class(classes.underline); }); - it.skip('should forward classes to InputBase', () => { + it('should forward classes to InputBase', () => { render(); expect(document.querySelector('.error')).not.to.equal(null); }); @@ -60,14 +61,15 @@ describe('', () => { expect(document.querySelector('[data-test=test]')).not.to.equal(null); }); - it.skip('should respect the classes coming from InputBase', () => { - render( + it('should respect the classes coming from InputBase', () => { + const { getByTestId } = render( , ); - expect(document.querySelector('[data-test=test]')).toHaveComputedStyle({ marginTop: '10px' }); + const root = getByTestId('test-input'); + expect(root).toHaveComputedStyle({ marginTop: '10px' }); }); }); From 8794164df0da9afd7870778c723a439453cb35a0 Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Wed, 4 Oct 2023 22:16:06 +0800 Subject: [PATCH 06/34] Convert FormControl test to typescript --- ...rmControl.test.js => FormControl.test.tsx} | 61 +++++++++++-------- 1 file changed, 36 insertions(+), 25 deletions(-) rename packages/mui-material-next/src/FormControl/{FormControl.test.js => FormControl.test.tsx} (87%) diff --git a/packages/mui-material-next/src/FormControl/FormControl.test.js b/packages/mui-material-next/src/FormControl/FormControl.test.tsx similarity index 87% rename from packages/mui-material-next/src/FormControl/FormControl.test.js rename to packages/mui-material-next/src/FormControl/FormControl.test.tsx index ed22b74a058d50..d57f240520dbe8 100644 --- a/packages/mui-material-next/src/FormControl/FormControl.test.js +++ b/packages/mui-material-next/src/FormControl/FormControl.test.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; 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'; +import FormControl, { FormControlClasses, formControlClasses as classes } from '@mui/material-next/FormControl'; // TODO v6: replace with material-next/FilledInput import InputBase from '@mui/material-next/InputBase'; import { CssVarsProvider, extendTheme } from '@mui/material-next/styles'; @@ -13,7 +13,11 @@ import useFormControl from './useFormControl'; 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 +51,7 @@ describe('', () => { const root = container.firstChild; expect(root).not.to.have.class(classes.marginNormal); - expect(root).not.to.have.class(classes.sizeSmall); + expect(root).not.to.have.class((classes as FormControlClasses & { sizeSmall: string }).sizeSmall); }); it('can have the margin normal class', () => { @@ -55,7 +59,7 @@ describe('', () => { const root = container.firstChild; expect(root).to.have.class(classes.marginNormal); - expect(root).not.to.have.class(classes.sizeSmall); + expect(root).not.to.have.class((classes as FormControlClasses & { sizeSmall: string }).sizeSmall); }); it('can have the margin dense class', () => { @@ -106,7 +110,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 +130,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); }); @@ -287,7 +291,7 @@ describe('', () => { , ); - expect(readContext.args[0][0].adornedStart, true); + expect(readContext.args[0][0].adornedStart, 'true'); }); }); @@ -308,7 +312,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 +322,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 +332,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 +342,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); @@ -348,22 +352,29 @@ describe('', () => { }); }); + type TestFormControlledComponent = { + onFilled: () => {}, + onEmpty: () => {}, + onFocus: () => {}, + onBlur: () => {}, + } + 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 +383,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 +408,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 +428,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); From de742ce3df1ff87b515fa37cc90fb383da3ea079 Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Wed, 4 Oct 2023 22:19:27 +0800 Subject: [PATCH 07/34] Update FormControl tests that rely on FilledInput --- .../src/FormControl/FormControl.test.tsx | 44 ++++++++----------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/packages/mui-material-next/src/FormControl/FormControl.test.tsx b/packages/mui-material-next/src/FormControl/FormControl.test.tsx index d57f240520dbe8..56d8eca1169c78 100644 --- a/packages/mui-material-next/src/FormControl/FormControl.test.tsx +++ b/packages/mui-material-next/src/FormControl/FormControl.test.tsx @@ -2,14 +2,21 @@ import * as React from 'react'; import { expect } from 'chai'; import { spy } from 'sinon'; import { describeConformance, act, createRenderer, fireEvent } from '@mui-internal/test-utils'; -import FormControl, { FormControlClasses, formControlClasses as classes } from '@mui/material-next/FormControl'; -// TODO v6: replace with material-next/FilledInput +import FormControl, { formControlClasses as classes } from '@mui/material-next/FormControl'; +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(); @@ -51,7 +58,7 @@ describe('', () => { const root = container.firstChild; expect(root).not.to.have.class(classes.marginNormal); - expect(root).not.to.have.class((classes as FormControlClasses & { sizeSmall: string }).sizeSmall); + expect(root).not.to.have.class('MuiFormControl-sizeSmall'); }); it('can have the margin normal class', () => { @@ -59,7 +66,7 @@ describe('', () => { const root = container.firstChild; expect(root).to.have.class(classes.marginNormal); - expect(root).not.to.have.class((classes as FormControlClasses & { sizeSmall: string }).sizeSmall); + expect(root).not.to.have.class('MuiFormControl-sizeSmall'); }); it('can have the margin dense class', () => { @@ -205,27 +212,24 @@ 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', () => { + // eslint-disable-next-line mocha/no-skipped-tests + it.skip('should be filled when a value is set through inputProps', () => { const readContext = spy(); render( - {/* TODO v6: use material-next/FilledInput */} - + , ); @@ -236,8 +240,7 @@ describe('', () => { const readContext = spy(); render( - {/* TODO v6: use material-next/FilledInput */} - + , ); @@ -248,8 +251,7 @@ describe('', () => { const readContext = spy(); render( - {/* TODO v6: use material-next/FilledInput */} - } /> + } /> , ); @@ -260,8 +262,7 @@ describe('', () => { const readContext = spy(); render( - {/* TODO v6: use material-next/FilledInput */} - } /> + } /> , ); @@ -352,13 +353,6 @@ describe('', () => { }); }); - type TestFormControlledComponent = { - onFilled: () => {}, - onEmpty: () => {}, - onFocus: () => {}, - onBlur: () => {}, - } - describe('callbacks', () => { describe('onFilled', () => { it('should set the filled state', () => { From 9c8626d6a98d693a0432619078df33850fea143a Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Wed, 4 Oct 2023 22:45:29 +0800 Subject: [PATCH 08/34] Convert InputBase tests to typescript --- .../{InputBase.test.js => InputBase.test.tsx} | 95 +++++++++++-------- 1 file changed, 55 insertions(+), 40 deletions(-) rename packages/mui-material-next/src/InputBase/{InputBase.test.js => InputBase.test.tsx} (88%) diff --git a/packages/mui-material-next/src/InputBase/InputBase.test.js b/packages/mui-material-next/src/InputBase/InputBase.test.tsx similarity index 88% rename from packages/mui-material-next/src/InputBase/InputBase.test.js rename to packages/mui-material-next/src/InputBase/InputBase.test.tsx index 96eb55612ed2ca..61218490d22088 100644 --- a/packages/mui-material-next/src/InputBase/InputBase.test.js +++ b/packages/mui-material-next/src/InputBase/InputBase.test.tsx @@ -18,6 +18,7 @@ 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(); @@ -185,7 +186,9 @@ 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 +202,7 @@ describe('', () => { onChange: PropTypes.func.isRequired, }; - const triggerChangeRef = React.createRef(); + const triggerChangeRef = React.createRef(); expect(() => { render( @@ -228,15 +231,15 @@ 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: any = {}; + const MyInputBase = React.forwardRef(function MyInputBase(props: any, ref: React.ForwardedRef) { injectedProps = props; const { ownerState, ...other } = props; return ; @@ -244,26 +247,30 @@ describe('', () => { render(); - expect(typeof injectedProps.onBlur).to.equal('function'); - expect(typeof injectedProps.onFocus).to.equal('function'); + if (injectedProps) { + expect(typeof injectedProps.onBlur).to.equal('function'); + expect(typeof injectedProps.onFocus).to.equal('function'); + } }); 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: any) { + const formControlContext = useFormControl(); + return filled: {String(formControlContext?.filled)}; } const { getByRole, getByTestId } = render( @@ -279,14 +286,14 @@ 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: any, ref: React.ForwardedRef) { const { ownerState, ...otherProps } = props; return ; }); - function FilledState(props) { - const { filled } = useFormControl(); - return filled: {String(filled)}; + function FilledState(props: any) { + const formControlContext = useFormControl(); + return filled: {String(formControlContext?.filled)}; } const { getByRole, getByTestId } = render( @@ -331,7 +338,7 @@ describe('', () => { describe('error', () => { it('should be overridden by props', () => { - function InputBaseInErrorForm(props) { + function InputBaseInErrorForm(props: InputBaseProps) { return ( @@ -361,7 +368,7 @@ describe('', () => { }); it('should be overridden by props', () => { - function InputBaseInFormWithMargin(props) { + function InputBaseInFormWithMargin(props: InputBaseProps) { return ( @@ -398,16 +405,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 +433,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: any) { + const formControlContext = useFormControl(); + return ; } const { getByRole, getByTestId } = render( @@ -459,9 +471,9 @@ describe('', () => { }); it('propagates filled state when uncontrolled', () => { - function FilledStateLabel(props) { - const { filled } = useFormControl(); - return ; + function FilledStateLabel(props: any) { + const formControlContext = useFormControl(); + return ; } const { getByRole, getByTestId } = render( @@ -483,11 +495,11 @@ describe('', () => { }); it('propagates filled state when controlled', () => { - function FilledStateLabel(props) { - const { filled } = useFormControl(); - return ; + function FilledStateLabel(props: any) { + const formControlContext = useFormControl(); + return ; } - function ControlledInputBase(props) { + function ControlledInputBase(props: InputBaseProps) { return ( @@ -552,7 +564,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,7 +572,7 @@ 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); }); @@ -571,10 +583,13 @@ describe('', () => { const INPUT_VALUE = 'material'; const OUTPUT_VALUE = 'test'; - const MyInputBase = React.forwardRef(function MyInputBase(props, ref) { + const MyInputBase = React.forwardRef(function MyInputBase(props: { + onChange: (...args: string[]) => void, + ownerState: InputBaseOwnerState + }, ref: React.ForwardedRef) { const { onChange, ownerState, ...other } = props; - const handleChange = (e) => { + const handleChange = (e: React.ChangeEvent) => { onChange(e.target.value, OUTPUT_VALUE); }; @@ -585,8 +600,8 @@ describe('', () => { onChange: PropTypes.func.isRequired, }; - let outputArguments; - function parentHandleChange(...args) { + let outputArguments: string[] = []; + function parentHandleChange(...args: string[]) { outputArguments = args; } @@ -595,7 +610,7 @@ describe('', () => { slots={{ input: MyInputBase }} slotProps={{ input: { - onChange: parentHandleChange, + onChange: parentHandleChange as unknown as React.ChangeEventHandler, }, }} />, From 692ee5caecf7cba9a6e6244bec3b7c6445821f35 Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Wed, 4 Oct 2023 23:23:40 +0800 Subject: [PATCH 09/34] Update proptypes --- .../src/FilledInput/FilledInput.tsx | 99 ++++++++----------- .../src/InputBase/InputBase.tsx | 25 ++++- 2 files changed, 67 insertions(+), 57 deletions(-) diff --git a/packages/mui-material-next/src/FilledInput/FilledInput.tsx b/packages/mui-material-next/src/FilledInput/FilledInput.tsx index e237379d13d25f..6bf1066fa84b9c 100644 --- a/packages/mui-material-next/src/FilledInput/FilledInput.tsx +++ b/packages/mui-material-next/src/FilledInput/FilledInput.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import { refType } from '@mui/utils'; import PropTypes from 'prop-types'; import { unstable_composeClasses as composeClasses, useSlotProps } from '@mui/base'; -import { OverrideProps } from '@mui/types'; +import { DefaultComponentProps, OverrideProps } from '@mui/types'; import InputBase, { rootOverridesResolver as inputBaseRootOverridesResolver, inputOverridesResolver as inputBaseInputOverridesResolver, @@ -46,19 +46,15 @@ const FilledInputRoot = styled(InputBaseRoot, { return { '--md-comp-filled-input-container-color': tokens.sys.color.surfaceContainerHighest, - '--md-comp-filled-input-active-indicator-color': tokens.sys.color.onSurfaceVariant, '--md-comp-filled-input-hover-active-indicator-color': tokens.sys.color.onSurface, '--md-comp-filled-input-focus-active-indicator-color': tokens.sys.color[ownerState.color ?? 'primary'], - '--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-error-focus-active-indicator-color': tokens.sys.color.error, - '--md-comp-filled-input-disabled-container-color': tokens.sys.color.onSurface, '--md-comp-filled-input-disabled-container-opacity': 0.04, - position: 'relative', backgroundColor: 'var(--md-comp-filled-input-container-color)', borderTopLeftRadius: tokens.shape.borderRadius, @@ -341,7 +337,7 @@ interface FilledInputComponent { 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. @@ -353,6 +349,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. */ @@ -367,31 +367,6 @@ FilledInput.propTypes /* remove-proptypes */ = { PropTypes.oneOf(['primary', 'secondary']), 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. */ @@ -431,16 +406,10 @@ 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. */ @@ -454,13 +423,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, @@ -492,29 +461,24 @@ 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({ input: PropTypes.elementType, root: PropTypes.elementType, + textarea: PropTypes.elementType, }), /** * Start `InputAdornment` for this component. @@ -532,12 +496,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; // @ts-ignore - internal logic to integrate with FormControl FilledInput.muiName = 'Input'; diff --git a/packages/mui-material-next/src/InputBase/InputBase.tsx b/packages/mui-material-next/src/InputBase/InputBase.tsx index b0d919b3d388e0..5571ff3f31a13e 100644 --- a/packages/mui-material-next/src/InputBase/InputBase.tsx +++ b/packages/mui-material-next/src/InputBase/InputBase.tsx @@ -737,7 +737,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. */ From 5a7559f70da368639965df3352946a84d3e64c29 Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Wed, 4 Oct 2023 23:23:48 +0800 Subject: [PATCH 10/34] Prettier --- .../src/InputBase/InputBase.test.tsx | 59 +++++++++++++------ 1 file changed, 41 insertions(+), 18 deletions(-) diff --git a/packages/mui-material-next/src/InputBase/InputBase.test.tsx b/packages/mui-material-next/src/InputBase/InputBase.test.tsx index 61218490d22088..9c7ccf34397358 100644 --- a/packages/mui-material-next/src/InputBase/InputBase.test.tsx +++ b/packages/mui-material-next/src/InputBase/InputBase.test.tsx @@ -18,7 +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'; +import { + InputBaseInputSlotPropsOverrides, + InputBaseOwnerState, + InputBaseProps, +} from './InputBase.types'; describe('', () => { const { render } = createRenderer(); @@ -186,9 +190,12 @@ describe('', () => { * * A ref is exposed to trigger a change event instead of using fireEvent.change */ - const BadInputComponent = React.forwardRef(function BadInputComponent(props: { - onChange: (arg: Record) => void, - }, ref) { + const BadInputComponent = React.forwardRef(function BadInputComponent( + props: { + onChange: (arg: Record) => void; + }, + ref, + ) { const { onChange } = props; // simulates const handleChange = () => onChange({}) and passing that @@ -231,7 +238,9 @@ describe('', () => { const { getByTestId } = render( , ); expect(getByTestId('input-component')).to.have.property('nodeName', 'SPAN'); @@ -239,7 +248,10 @@ describe('', () => { it('should inject onBlur and onFocus', () => { let injectedProps: any = {}; - const MyInputBase = React.forwardRef(function MyInputBase(props: any, ref: React.ForwardedRef) { + const MyInputBase = React.forwardRef(function MyInputBase( + props: any, + ref: React.ForwardedRef, + ) { injectedProps = props; const { ownerState, ...other } = props; return ; @@ -255,13 +267,18 @@ describe('', () => { describe('target mock implementations', () => { it('can just mock the value', () => { - const MockedValue = React.forwardRef(function MockedValue(props: { - onChange: React.ChangeEventHandler, - }, ref: React.ForwardedRef) { + const MockedValue = React.forwardRef(function MockedValue( + props: { + onChange: React.ChangeEventHandler; + }, + ref: React.ForwardedRef, + ) { const { onChange } = props; const handleChange = (event: React.ChangeEvent) => { - onChange({ target: { value: event.target.value } } as React.ChangeEvent); + onChange({ + target: { value: event.target.value }, + } as React.ChangeEvent); }; return ; @@ -286,7 +303,10 @@ describe('', () => { }); it("can expose the input component's ref through the inputComponent prop", () => { - const FullTarget = React.forwardRef(function FullTarget(props: any, ref: React.ForwardedRef) { + const FullTarget = React.forwardRef(function FullTarget( + props: any, + ref: React.ForwardedRef, + ) { const { ownerState, ...otherProps } = props; return ; }); @@ -406,9 +426,9 @@ describe('', () => { }); type TestFormController = { - onFocus: () => {}, - onBlur: () => {}, - } + onFocus: () => {}; + onBlur: () => {}; + }; describe('focused', () => { it('prioritizes context focus', () => { @@ -583,10 +603,13 @@ describe('', () => { const INPUT_VALUE = 'material'; const OUTPUT_VALUE = 'test'; - const MyInputBase = React.forwardRef(function MyInputBase(props: { - onChange: (...args: string[]) => void, - ownerState: InputBaseOwnerState - }, ref: React.ForwardedRef) { + const MyInputBase = React.forwardRef(function MyInputBase( + props: { + onChange: (...args: string[]) => void; + ownerState: InputBaseOwnerState; + }, + ref: React.ForwardedRef, + ) { const { onChange, ownerState, ...other } = props; const handleChange = (e: React.ChangeEvent) => { From b8ac7f5d4d2614203cd2842be7cff84c1e3d380a Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Wed, 4 Oct 2023 23:39:21 +0800 Subject: [PATCH 11/34] Minor CI fixes --- packages/mui-material-next/src/FilledInput/FilledInput.tsx | 2 +- .../mui-material-next/src/FilledInput/{index.js => index.ts} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename packages/mui-material-next/src/FilledInput/{index.js => index.ts} (100%) diff --git a/packages/mui-material-next/src/FilledInput/FilledInput.tsx b/packages/mui-material-next/src/FilledInput/FilledInput.tsx index 6bf1066fa84b9c..776c87733fbcc7 100644 --- a/packages/mui-material-next/src/FilledInput/FilledInput.tsx +++ b/packages/mui-material-next/src/FilledInput/FilledInput.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import { refType } from '@mui/utils'; import PropTypes from 'prop-types'; import { unstable_composeClasses as composeClasses, useSlotProps } from '@mui/base'; -import { DefaultComponentProps, OverrideProps } from '@mui/types'; +import { OverrideProps } from '@mui/types'; import InputBase, { rootOverridesResolver as inputBaseRootOverridesResolver, inputOverridesResolver as inputBaseInputOverridesResolver, 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 From 86907826124b0e2a8a3c4356658b75ee5c5f157c Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Thu, 5 Oct 2023 10:20:47 +0800 Subject: [PATCH 12/34] Fix internal imports --- .../src/FilledInput/FilledInput.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/mui-material-next/src/FilledInput/FilledInput.tsx b/packages/mui-material-next/src/FilledInput/FilledInput.tsx index 776c87733fbcc7..3b51148b639450 100644 --- a/packages/mui-material-next/src/FilledInput/FilledInput.tsx +++ b/packages/mui-material-next/src/FilledInput/FilledInput.tsx @@ -4,14 +4,16 @@ import { refType } from '@mui/utils'; import PropTypes from 'prop-types'; import { unstable_composeClasses as composeClasses, useSlotProps } from '@mui/base'; import { OverrideProps } from '@mui/types'; -import InputBase, { - rootOverridesResolver as inputBaseRootOverridesResolver, - inputOverridesResolver as inputBaseInputOverridesResolver, -} from '../InputBase'; import { useThemeProps, styled } from '../styles'; import { rootShouldForwardProp } from '../styles/styled'; +import { + InputBaseRoot, + InputBaseInput, + rootOverridesResolver as inputBaseRootOverridesResolver, + inputOverridesResolver as inputBaseInputOverridesResolver, +} from '../InputBase/InputBase'; +import InputBase from '../InputBase'; import filledInputClasses, { getFilledInputUtilityClass } from './filledInputClasses'; -import { InputBaseRoot, InputBaseInput } from '../InputBase/InputBase'; import { FilledInputOwnerState, FilledInputProps, FilledInputTypeMap } from './FilledInput.types'; const useUtilityClasses = (ownerState: FilledInputOwnerState) => { @@ -36,6 +38,7 @@ const FilledInputRoot = styled(InputBaseRoot, { slot: 'Root', overridesResolver: (props, styles) => { const { ownerState } = props; + return [ ...inputBaseRootOverridesResolver(props, styles), !ownerState.disableUnderline && styles.underline, From b9027a5f70ef04089deb295761a448f0e25f5f71 Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Thu, 5 Oct 2023 14:09:56 +0800 Subject: [PATCH 13/34] Fully use useSlotProps --- .../src/FilledInput/FilledInput.tsx | 40 +++++-------------- 1 file changed, 10 insertions(+), 30 deletions(-) diff --git a/packages/mui-material-next/src/FilledInput/FilledInput.tsx b/packages/mui-material-next/src/FilledInput/FilledInput.tsx index 3b51148b639450..9f1ef45b316530 100644 --- a/packages/mui-material-next/src/FilledInput/FilledInput.tsx +++ b/packages/mui-material-next/src/FilledInput/FilledInput.tsx @@ -13,6 +13,7 @@ import { inputOverridesResolver as inputBaseInputOverridesResolver, } from '../InputBase/InputBase'; import InputBase from '../InputBase'; +import { InputBaseOwnerState } from '../InputBase/InputBase.types'; import filledInputClasses, { getFilledInputUtilityClass } from './filledInputClasses'; import { FilledInputOwnerState, FilledInputProps, FilledInputTypeMap } from './FilledInput.types'; @@ -261,32 +262,20 @@ const FilledInput = React.forwardRef(function FilledInput< const rootProps = useSlotProps({ elementType: Root, externalSlotProps: slotProps.root, - ownerState: { - ...ownerState, - maxRows: undefined, - minRows: undefined, - rows: undefined, - 'aria-describedby': undefined, - autoComplete: undefined, - formControl: undefined, - focused: false, + additionalProps: { + ref: forwardedRef, + fullWidth, + inputComponent, }, + externalForwardedProps: other, + ownerState: ownerState as FilledInputOwnerState & InputBaseOwnerState, className: [classes.root], }); const inputProps = useSlotProps({ elementType: Input, externalSlotProps: slotProps.input, - ownerState: { - ...ownerState, - maxRows: undefined, - minRows: undefined, - rows: undefined, - 'aria-describedby': undefined, - autoComplete: undefined, - formControl: undefined, - focused: false, - }, + ownerState: ownerState as FilledInputOwnerState & InputBaseOwnerState, className: [classes.input], }); @@ -295,14 +284,10 @@ const FilledInput = React.forwardRef(function FilledInput< ); } @@ -311,15 +296,10 @@ const FilledInput = React.forwardRef(function FilledInput< ); }) as FilledInputComponent; From 9e912512fc469c930686128a5a88dde5962ec5fc Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Fri, 6 Oct 2023 14:02:08 +0800 Subject: [PATCH 14/34] Fix slots in types and proptypes --- .../src/FilledInput/FilledInput.tsx | 1 - .../src/FilledInput/FilledInput.types.ts | 21 ++++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/mui-material-next/src/FilledInput/FilledInput.tsx b/packages/mui-material-next/src/FilledInput/FilledInput.tsx index 9f1ef45b316530..3e7438b3662ca8 100644 --- a/packages/mui-material-next/src/FilledInput/FilledInput.tsx +++ b/packages/mui-material-next/src/FilledInput/FilledInput.tsx @@ -461,7 +461,6 @@ FilledInput.propTypes /* remove-proptypes */ = { slots: PropTypes.shape({ input: PropTypes.elementType, root: PropTypes.elementType, - textarea: PropTypes.elementType, }), /** * Start `InputAdornment` for this component. diff --git a/packages/mui-material-next/src/FilledInput/FilledInput.types.ts b/packages/mui-material-next/src/FilledInput/FilledInput.types.ts index aa76a32c9cc3d2..81a694f2085274 100644 --- a/packages/mui-material-next/src/FilledInput/FilledInput.types.ts +++ b/packages/mui-material-next/src/FilledInput/FilledInput.types.ts @@ -7,7 +7,7 @@ import { Theme } from '../styles/Theme.types'; import { InputBaseProps } from '../InputBase/InputBase.types'; import { FilledInputClasses } from './filledInputClasses'; -export type FilledInputOwnProps = StandardProps & { +export type FilledInputOwnProps = StandardProps> & { /** * Override or extend the styles applied to the component. */ @@ -23,12 +23,31 @@ export type FilledInputOwnProps = StandardProps & { * 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', From cc82a279ac90250d0ae98e9be28c17f0b0c5ac45 Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Fri, 6 Oct 2023 16:36:32 +0800 Subject: [PATCH 15/34] Drop componentsProps test --- .../mui-material-next/src/FilledInput/FilledInput.test.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/mui-material-next/src/FilledInput/FilledInput.test.tsx b/packages/mui-material-next/src/FilledInput/FilledInput.test.tsx index cf37ce0f3bd83b..99e22a0b657a06 100644 --- a/packages/mui-material-next/src/FilledInput/FilledInput.test.tsx +++ b/packages/mui-material-next/src/FilledInput/FilledInput.test.tsx @@ -55,12 +55,6 @@ describe('', () => { expect(document.querySelector('.error')).not.to.equal(null); }); - // eslint-disable-next-line mocha/no-skipped-tests - it.skip('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', () => { const { getByTestId } = render( Date: Fri, 6 Oct 2023 16:56:55 +0800 Subject: [PATCH 16/34] Tweak styles --- .../src/FilledInput/FilledInput.tsx | 53 ++++++++++--------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/packages/mui-material-next/src/FilledInput/FilledInput.tsx b/packages/mui-material-next/src/FilledInput/FilledInput.tsx index 3e7438b3662ca8..4619c6fbdb036c 100644 --- a/packages/mui-material-next/src/FilledInput/FilledInput.tsx +++ b/packages/mui-material-next/src/FilledInput/FilledInput.tsx @@ -49,16 +49,17 @@ const FilledInputRoot = styled(InputBaseRoot, { const { vars: tokens } = theme; return { - '--md-comp-filled-input-container-color': tokens.sys.color.surfaceContainerHighest, '--md-comp-filled-input-active-indicator-color': tokens.sys.color.onSurfaceVariant, - '--md-comp-filled-input-hover-active-indicator-color': tokens.sys.color.onSurface, - '--md-comp-filled-input-focus-active-indicator-color': - tokens.sys.color[ownerState.color ?? 'primary'], - '--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-error-focus-active-indicator-color': tokens.sys.color.error, + '--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: 'var(--md-comp-filled-input-container-color)', borderTopLeftRadius: tokens.shape.borderRadius, @@ -68,7 +69,8 @@ const FilledInputRoot = styled(InputBaseRoot, { easing: theme.transitions.easing.easeOut, }), '&:hover': { - backgroundColor: `color-mix(in srgb, ${tokens.sys.color.onSurface} calc(${tokens.sys.state.hover.stateLayerOpacity} * 100%), var(--md-comp-filled-input-container-color))`, + 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: 'var(--md-comp-filled-input-container-color)', @@ -107,7 +109,7 @@ const FilledInputRoot = styled(InputBaseRoot, { }, [`&.${filledInputClasses.error}`]: { '&:before, &:after': { - borderBottomColor: tokens.sys.color.errorContainer, + borderBottomColor: 'var(--md-comp-filled-input-error-active-indicator-color)', }, }, '&:before': { @@ -183,23 +185,22 @@ const FilledInputInput = styled(InputBaseInput, { }, }, } - : {}), - ...(tokens && { - '&:-webkit-autofill': { - borderTopLeftRadius: 'inherit', - borderTopRightRadius: 'inherit', - }, - }), - // TODO: investigate why describeConformance dies without this check - ...(typeof theme.getColorSchemeSelector === 'function' && { - [theme.getColorSchemeSelector('dark')]: { - '&:-webkit-autofill': { - WebkitBoxShadow: '0 0 0 100px #266798 inset', - WebkitTextFillColor: '#fff', - caretColor: '#fff', - }, - }, - }), + : { + '&:-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, From 6f9473f7954da3890031e73b4c68b9264df1d4e6 Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Mon, 9 Oct 2023 18:12:35 +0800 Subject: [PATCH 17/34] Support tertiary color --- .../mui-material-next/src/FilledInput/FilledInput.tsx | 3 +-- packages/mui-material-next/src/InputBase/InputBase.tsx | 10 +++++++++- .../mui-material-next/src/InputBase/InputBase.types.ts | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/mui-material-next/src/FilledInput/FilledInput.tsx b/packages/mui-material-next/src/FilledInput/FilledInput.tsx index 4619c6fbdb036c..776f9252856505 100644 --- a/packages/mui-material-next/src/FilledInput/FilledInput.tsx +++ b/packages/mui-material-next/src/FilledInput/FilledInput.tsx @@ -59,7 +59,6 @@ const FilledInputRoot = styled(InputBaseRoot, { 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: 'var(--md-comp-filled-input-container-color)', borderTopLeftRadius: tokens.shape.borderRadius, @@ -348,7 +347,7 @@ 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, ]), /** diff --git a/packages/mui-material-next/src/InputBase/InputBase.tsx b/packages/mui-material-next/src/InputBase/InputBase.tsx index 5571ff3f31a13e..2446f3c197cd93 100644 --- a/packages/mui-material-next/src/InputBase/InputBase.tsx +++ b/packages/mui-material-next/src/InputBase/InputBase.tsx @@ -582,7 +582,15 @@ InputBase.propTypes /* remove-proptypes */ = { * [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. */ diff --git a/packages/mui-material-next/src/InputBase/InputBase.types.ts b/packages/mui-material-next/src/InputBase/InputBase.types.ts index a1a3d7c85d3dd1..b2bbaf82b111d9 100644 --- a/packages/mui-material-next/src/InputBase/InputBase.types.ts +++ b/packages/mui-material-next/src/InputBase/InputBase.types.ts @@ -85,7 +85,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 >; /** From 873c75f39811c0cbe906140684fa433600ae11d1 Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Fri, 6 Oct 2023 17:44:14 +0800 Subject: [PATCH 18/34] Set up experiments page --- docs/pages/experiments/md3/inputs.tsx | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) 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
+ +
- - + + +
From 2d249b55f7fb88566fb646d674e4719d858b6708 Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Mon, 9 Oct 2023 20:58:50 +0800 Subject: [PATCH 19/34] Fix skipped FormControl test --- .../mui-material-next/src/FormControl/FormControl.test.tsx | 5 ++--- packages/mui-material-next/src/FormControl/FormControl.tsx | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/mui-material-next/src/FormControl/FormControl.test.tsx b/packages/mui-material-next/src/FormControl/FormControl.test.tsx index 56d8eca1169c78..06d4711ec281ed 100644 --- a/packages/mui-material-next/src/FormControl/FormControl.test.tsx +++ b/packages/mui-material-next/src/FormControl/FormControl.test.tsx @@ -224,12 +224,11 @@ describe('', () => { expect(readContext.args[0][0]).to.have.property('filled', true); }); - // eslint-disable-next-line mocha/no-skipped-tests - it.skip('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( - + , ); 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; } From 54b19459339c1a82edb76c1c26a8d947b78250bd Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Mon, 9 Oct 2023 22:39:03 +0800 Subject: [PATCH 20/34] Update docs --- packages/mui-material-next/migration.md | 13 +++++++++++++ .../src/FilledInput/FilledInput.types.ts | 1 + .../src/InputBase/InputBase.test.tsx | 4 ++-- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/mui-material-next/migration.md b/packages/mui-material-next/migration.md index 798ba9235d539c..f29dd05099c4ee 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 deprecated in favor of `slotProps.input`: + +```diff + +``` + ## FormControl ### Renamed `FormControlState` diff --git a/packages/mui-material-next/src/FilledInput/FilledInput.types.ts b/packages/mui-material-next/src/FilledInput/FilledInput.types.ts index 81a694f2085274..b50d0fcdeccb6b 100644 --- a/packages/mui-material-next/src/FilledInput/FilledInput.types.ts +++ b/packages/mui-material-next/src/FilledInput/FilledInput.types.ts @@ -1,4 +1,5 @@ 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 { PolymorphicProps } from '@mui/base/utils'; diff --git a/packages/mui-material-next/src/InputBase/InputBase.test.tsx b/packages/mui-material-next/src/InputBase/InputBase.test.tsx index 9c7ccf34397358..b26fb4b759a6bc 100644 --- a/packages/mui-material-next/src/InputBase/InputBase.test.tsx +++ b/packages/mui-material-next/src/InputBase/InputBase.test.tsx @@ -708,7 +708,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)) { @@ -727,7 +727,7 @@ describe('', () => { }); const { getByRole } = render( - {/* TODO v6: use material-next/TextField or OutlinedInput */} + {/* TODO v6: use material-next/TextField */} , ); From e6908c13e05e9469b5c6f17c9044d4b18a5d42da Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Mon, 9 Oct 2023 23:51:45 +0800 Subject: [PATCH 21/34] Fix proptypes --- .../mui-material-next/src/FilledInput/FilledInput.tsx | 8 +++----- .../src/FilledInput/FilledInput.types.ts | 10 ++++++---- packages/mui-material-next/src/InputBase/InputBase.tsx | 2 ++ 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/mui-material-next/src/FilledInput/FilledInput.tsx b/packages/mui-material-next/src/FilledInput/FilledInput.tsx index 776f9252856505..eb4c5130a1b665 100644 --- a/packages/mui-material-next/src/FilledInput/FilledInput.tsx +++ b/packages/mui-material-next/src/FilledInput/FilledInput.tsx @@ -310,11 +310,13 @@ interface FilledInputComponent { /** * 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; propTypes?: any; + muiName?: string; } FilledInput.propTypes /* remove-proptypes */ = { @@ -332,10 +334,6 @@ 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. */ @@ -391,6 +389,7 @@ FilledInput.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 /* @typescript-to-proptypes-ignore */.elementType, /** @@ -508,7 +507,6 @@ FilledInput.propTypes /* remove-proptypes */ = { value: PropTypes.any, } as any; -// @ts-ignore - internal logic to integrate with FormControl FilledInput.muiName = 'Input'; export default FilledInput; diff --git a/packages/mui-material-next/src/FilledInput/FilledInput.types.ts b/packages/mui-material-next/src/FilledInput/FilledInput.types.ts index b50d0fcdeccb6b..544db2978a678a 100644 --- a/packages/mui-material-next/src/FilledInput/FilledInput.types.ts +++ b/packages/mui-material-next/src/FilledInput/FilledInput.types.ts @@ -2,13 +2,12 @@ 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 { PolymorphicProps } from '@mui/base/utils'; -import { Simplify } from '@mui/types'; +import { OverrideProps, Simplify } from '@mui/types'; import { Theme } from '../styles/Theme.types'; import { InputBaseProps } from '../InputBase/InputBase.types'; import { FilledInputClasses } from './filledInputClasses'; -export type FilledInputOwnProps = StandardProps> & { +export type FilledInputOwnProps = StandardProps> & { /** * Override or extend the styles applied to the component. */ @@ -59,7 +58,10 @@ export interface FilledInputTypeMap< export type FilledInputProps< RootComponentType extends React.ElementType = FilledInputTypeMap['defaultComponent'], -> = PolymorphicProps, RootComponentType>; + AdditionalProps = {}, +> = OverrideProps, RootComponentType> & { + inputComponent?: React.ElementType; +}; export type FilledInputOwnerState = Simplify< FilledInputOwnProps & { diff --git a/packages/mui-material-next/src/InputBase/InputBase.tsx b/packages/mui-material-next/src/InputBase/InputBase.tsx index 2446f3c197cd93..fd8f85522ca6f3 100644 --- a/packages/mui-material-next/src/InputBase/InputBase.tsx +++ b/packages/mui-material-next/src/InputBase/InputBase.tsx @@ -546,6 +546,7 @@ 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, @@ -627,6 +628,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, /** From c60d7fe0ed724898ed62f35a5f96d07179cec380 Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Fri, 13 Oct 2023 15:46:31 +0800 Subject: [PATCH 22/34] Update InputBase test types --- .../src/InputBase/InputBase.test.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/mui-material-next/src/InputBase/InputBase.test.tsx b/packages/mui-material-next/src/InputBase/InputBase.test.tsx index b26fb4b759a6bc..b4f82381e7029c 100644 --- a/packages/mui-material-next/src/InputBase/InputBase.test.tsx +++ b/packages/mui-material-next/src/InputBase/InputBase.test.tsx @@ -249,8 +249,8 @@ describe('', () => { it('should inject onBlur and onFocus', () => { let injectedProps: any = {}; const MyInputBase = React.forwardRef(function MyInputBase( - props: any, - ref: React.ForwardedRef, + props: { ownerState: InputBaseOwnerState } & Record, + ref: React.ForwardedRef, ) { injectedProps = props; const { ownerState, ...other } = props; @@ -285,7 +285,7 @@ describe('', () => { }); MockedValue.propTypes = { onChange: PropTypes.func.isRequired }; - function FilledState(props: any) { + function FilledState(props: { 'data-testid': string }) { const formControlContext = useFormControl(); return filled: {String(formControlContext?.filled)}; } @@ -304,14 +304,14 @@ describe('', () => { it("can expose the input component's ref through the inputComponent prop", () => { const FullTarget = React.forwardRef(function FullTarget( - props: any, + props: { ownerState: InputBaseOwnerState } & Record, ref: React.ForwardedRef, ) { const { ownerState, ...otherProps } = props; return ; }); - function FilledState(props: any) { + function FilledState(props: { 'data-testid': string }) { const formControlContext = useFormControl(); return filled: {String(formControlContext?.filled)}; } @@ -466,7 +466,7 @@ describe('', () => { }); it('propagates focused state', () => { - function FocusedStateLabel(props: any) { + function FocusedStateLabel(props: { 'data-testid': string; htmlFor: string }) { const formControlContext = useFormControl(); return ; } @@ -491,7 +491,7 @@ describe('', () => { }); it('propagates filled state when uncontrolled', () => { - function FilledStateLabel(props: any) { + function FilledStateLabel(props: { 'data-testid': string }) { const formControlContext = useFormControl(); return ; } @@ -515,7 +515,7 @@ describe('', () => { }); it('propagates filled state when controlled', () => { - function FilledStateLabel(props: any) { + function FilledStateLabel(props: { 'data-testid': string }) { const formControlContext = useFormControl(); return ; } @@ -608,7 +608,7 @@ describe('', () => { onChange: (...args: string[]) => void; ownerState: InputBaseOwnerState; }, - ref: React.ForwardedRef, + ref: React.ForwardedRef, ) { const { onChange, ownerState, ...other } = props; From 7d98926a429f2daba571916cebc65fa796e18997 Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Fri, 13 Oct 2023 15:54:38 +0800 Subject: [PATCH 23/34] Misc fixes --- packages/mui-material-next/migration.md | 2 +- packages/mui-material-next/src/FilledInput/FilledInput.test.tsx | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/mui-material-next/migration.md b/packages/mui-material-next/migration.md index f29dd05099c4ee..25ac3a9ccc8f94 100644 --- a/packages/mui-material-next/migration.md +++ b/packages/mui-material-next/migration.md @@ -144,7 +144,7 @@ This is to ensure that default is prevented when the `ButtonBase` root is not a ### Removed `inputProps` -`inputProps` are deprecated in favor of `slotProps.input`: +`inputProps` are removed in favor of `slotProps.input`: ```diff ', () => { testDeepOverrides: { slotName: 'input', slotClassName: classes.input }, testVariantProps: { variant: 'contained', fullWidth: true }, testStateOverrides: { prop: 'size', value: 'small', styleKey: 'sizeSmall' }, - testLegacyComponentsProp: false, slots: { root: { expectedClassName: classes.root }, input: { expectedClassName: classes.input, testWithElement: 'input' }, From 0adc16156664cf08ed2b90675a9332232f5dfbef Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Fri, 13 Oct 2023 15:55:18 +0800 Subject: [PATCH 24/34] Add default component type to custom interface --- packages/mui-material-next/src/FilledInput/FilledInput.tsx | 3 ++- packages/mui-material-next/src/InputBase/InputBase.tsx | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/mui-material-next/src/FilledInput/FilledInput.tsx b/packages/mui-material-next/src/FilledInput/FilledInput.tsx index eb4c5130a1b665..9b5afec580dfda 100644 --- a/packages/mui-material-next/src/FilledInput/FilledInput.tsx +++ b/packages/mui-material-next/src/FilledInput/FilledInput.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import { refType } from '@mui/utils'; import PropTypes from 'prop-types'; import { unstable_composeClasses as composeClasses, useSlotProps } from '@mui/base'; -import { OverrideProps } from '@mui/types'; +import { DefaultComponentProps, OverrideProps } from '@mui/types'; import { useThemeProps, styled } from '../styles'; import { rootShouldForwardProp } from '../styles/styled'; import { @@ -315,6 +315,7 @@ interface FilledInputComponent { inputComponent?: C; } & OverrideProps, ): JSX.Element | null; + (props: DefaultComponentProps): JSX.Element | null; propTypes?: any; muiName?: string; } diff --git a/packages/mui-material-next/src/InputBase/InputBase.tsx b/packages/mui-material-next/src/InputBase/InputBase.tsx index fd8f85522ca6f3..ce9f4ceeaa30eb 100644 --- a/packages/mui-material-next/src/InputBase/InputBase.tsx +++ b/packages/mui-material-next/src/InputBase/InputBase.tsx @@ -12,7 +12,7 @@ import { } from '@mui/base'; import { useInput } from '@mui/base/useInput'; import { CSSInterpolation } from '@mui/system'; -import { OverrideProps } from '@mui/types'; +import { DefaultComponentProps, OverrideProps } from '@mui/types'; import { refType, unstable_capitalize as capitalize, @@ -551,6 +551,7 @@ interface InputBaseComponent { inputComponent?: C; } & OverrideProps, ): JSX.Element | null; + (props: DefaultComponentProps): JSX.Element | null; propTypes?: any; } From 53efd42c99257ca62d66747c0c7f45dd430513e4 Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Fri, 13 Oct 2023 16:06:50 +0800 Subject: [PATCH 25/34] Update proptypes --- .../src/FilledInput/FilledInput.tsx | 4 ++++ .../src/InputBase/InputBase.tsx | 22 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/packages/mui-material-next/src/FilledInput/FilledInput.tsx b/packages/mui-material-next/src/FilledInput/FilledInput.tsx index 9b5afec580dfda..ff0e5625d1f19a 100644 --- a/packages/mui-material-next/src/FilledInput/FilledInput.tsx +++ b/packages/mui-material-next/src/FilledInput/FilledInput.tsx @@ -335,6 +335,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. */ diff --git a/packages/mui-material-next/src/InputBase/InputBase.tsx b/packages/mui-material-next/src/InputBase/InputBase.tsx index ce9f4ceeaa30eb..5121732480ba62 100644 --- a/packages/mui-material-next/src/InputBase/InputBase.tsx +++ b/packages/mui-material-next/src/InputBase/InputBase.tsx @@ -564,6 +564,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. @@ -574,10 +584,18 @@ 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 @@ -672,6 +690,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 */ From 227a2b310e7b40b7ad3acac6f4ca89c3ad67739b Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Fri, 13 Oct 2023 16:20:55 +0800 Subject: [PATCH 26/34] Do not separate SingleLine and MultiLine input props --- .../src/FilledInput/FilledInput.tsx | 16 +--- .../src/FilledInput/FilledInput.types.ts | 5 +- .../src/InputBase/InputBase.types.ts | 74 ++++++------------- 3 files changed, 28 insertions(+), 67 deletions(-) diff --git a/packages/mui-material-next/src/FilledInput/FilledInput.tsx b/packages/mui-material-next/src/FilledInput/FilledInput.tsx index ff0e5625d1f19a..4eb361aafff223 100644 --- a/packages/mui-material-next/src/FilledInput/FilledInput.tsx +++ b/packages/mui-material-next/src/FilledInput/FilledInput.tsx @@ -279,25 +279,13 @@ const FilledInput = React.forwardRef(function FilledInput< className: [classes.input], }); - if (multiline) { - return ( - - ); - } - return ( diff --git a/packages/mui-material-next/src/FilledInput/FilledInput.types.ts b/packages/mui-material-next/src/FilledInput/FilledInput.types.ts index 544db2978a678a..fe229efca59b7f 100644 --- a/packages/mui-material-next/src/FilledInput/FilledInput.types.ts +++ b/packages/mui-material-next/src/FilledInput/FilledInput.types.ts @@ -7,7 +7,8 @@ import { Theme } from '../styles/Theme.types'; import { InputBaseProps } from '../InputBase/InputBase.types'; import { FilledInputClasses } from './filledInputClasses'; -export type FilledInputOwnProps = StandardProps> & { +export interface FilledInputOwnProps + extends StandardProps> { /** * Override or extend the styles applied to the component. */ @@ -33,7 +34,7 @@ export type FilledInputOwnProps = StandardProps; -}; +} export interface FilledInputSlots { /** diff --git a/packages/mui-material-next/src/InputBase/InputBase.types.ts b/packages/mui-material-next/src/InputBase/InputBase.types.ts index b2bbaf82b111d9..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. @@ -208,6 +158,28 @@ export type InputBaseOwnProps = (SingleLineInputProps | MultiLineInputProps) & { * 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 { From 4a821f9557b45eee51b486e03895d45b495efd85 Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Fri, 13 Oct 2023 16:41:46 +0800 Subject: [PATCH 27/34] Update test --- .../mui-material-next/src/InputBase/InputBase.test.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/mui-material-next/src/InputBase/InputBase.test.tsx b/packages/mui-material-next/src/InputBase/InputBase.test.tsx index b4f82381e7029c..bab26682cc4ca6 100644 --- a/packages/mui-material-next/src/InputBase/InputBase.test.tsx +++ b/packages/mui-material-next/src/InputBase/InputBase.test.tsx @@ -599,7 +599,9 @@ describe('', () => { }); 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 + it('should call slotProps.input.onChange callback with all params sent from custom inputComponent', () => { const INPUT_VALUE = 'material'; const OUTPUT_VALUE = 'test'; @@ -630,10 +632,10 @@ describe('', () => { const { getByRole } = render( , + onChange: parentHandleChange as (string, string) => void, }, }} />, From 6c233b25e0a594fcc0eec007ea8be4164b275713 Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Fri, 13 Oct 2023 19:08:34 +0800 Subject: [PATCH 28/34] Fix test and update comments --- packages/mui-material-next/src/InputBase/InputBase.test.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/mui-material-next/src/InputBase/InputBase.test.tsx b/packages/mui-material-next/src/InputBase/InputBase.test.tsx index bab26682cc4ca6..9211552d31bd9e 100644 --- a/packages/mui-material-next/src/InputBase/InputBase.test.tsx +++ b/packages/mui-material-next/src/InputBase/InputBase.test.tsx @@ -601,6 +601,7 @@ describe('', () => { describe('prop: slots and slotProps', () => { // 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'; @@ -608,11 +609,10 @@ describe('', () => { const MyInputBase = React.forwardRef(function MyInputBase( props: { onChange: (...args: string[]) => void; - ownerState: InputBaseOwnerState; }, ref: React.ForwardedRef, ) { - const { onChange, ownerState, ...other } = props; + const { onChange, ...other } = props; const handleChange = (e: React.ChangeEvent) => { onChange(e.target.value, OUTPUT_VALUE); @@ -635,7 +635,7 @@ describe('', () => { inputComponent={MyInputBase} slotProps={{ input: { - onChange: parentHandleChange as (string, string) => void, + onChange: parentHandleChange as unknown as React.ChangeEventHandler, }, }} />, From cbf63bd9e6df2ce0453e3d26d8d9cff3c7e793ca Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Fri, 13 Oct 2023 19:29:40 +0800 Subject: [PATCH 29/34] Pass multiline and type through useSlotProps --- packages/mui-material-next/src/FilledInput/FilledInput.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mui-material-next/src/FilledInput/FilledInput.tsx b/packages/mui-material-next/src/FilledInput/FilledInput.tsx index 4eb361aafff223..8c89dc2bc7c9e4 100644 --- a/packages/mui-material-next/src/FilledInput/FilledInput.tsx +++ b/packages/mui-material-next/src/FilledInput/FilledInput.tsx @@ -266,6 +266,8 @@ const FilledInput = React.forwardRef(function FilledInput< ref: forwardedRef, fullWidth, inputComponent, + multiline, + type, }, externalForwardedProps: other, ownerState: ownerState as FilledInputOwnerState & InputBaseOwnerState, @@ -285,8 +287,6 @@ const FilledInput = React.forwardRef(function FilledInput< slotProps={{ input: inputProps, }} - multiline={multiline} - type={type} {...rootProps} /> ); From b32209636fd8d42fba758ecf292c76f2528aa24a Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Tue, 17 Oct 2023 16:48:37 +0800 Subject: [PATCH 30/34] Remove unrelated expect --- packages/mui-material-next/src/FormControl/FormControl.test.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/mui-material-next/src/FormControl/FormControl.test.tsx b/packages/mui-material-next/src/FormControl/FormControl.test.tsx index 06d4711ec281ed..1068d199c74b54 100644 --- a/packages/mui-material-next/src/FormControl/FormControl.test.tsx +++ b/packages/mui-material-next/src/FormControl/FormControl.test.tsx @@ -58,7 +58,6 @@ describe('', () => { const root = container.firstChild; expect(root).not.to.have.class(classes.marginNormal); - expect(root).not.to.have.class('MuiFormControl-sizeSmall'); }); it('can have the margin normal class', () => { @@ -66,7 +65,6 @@ describe('', () => { const root = container.firstChild; expect(root).to.have.class(classes.marginNormal); - expect(root).not.to.have.class('MuiFormControl-sizeSmall'); }); it('can have the margin dense class', () => { From 1797c40240813ec8ed3fb140f3f10ea3479aebd9 Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Fri, 20 Oct 2023 18:30:57 +0800 Subject: [PATCH 31/34] Fix test --- .../mui-material-next/src/InputBase/InputBase.test.tsx | 9 ++++----- packages/mui-material-next/src/InputBase/InputBase.tsx | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/mui-material-next/src/InputBase/InputBase.test.tsx b/packages/mui-material-next/src/InputBase/InputBase.test.tsx index 9211552d31bd9e..c183219600c51f 100644 --- a/packages/mui-material-next/src/InputBase/InputBase.test.tsx +++ b/packages/mui-material-next/src/InputBase/InputBase.test.tsx @@ -247,7 +247,8 @@ describe('', () => { }); it('should inject onBlur and onFocus', () => { - let injectedProps: any = {}; + let injectedProps: Record = {}; + const MyInputBase = React.forwardRef(function MyInputBase( props: { ownerState: InputBaseOwnerState } & Record, ref: React.ForwardedRef, @@ -259,10 +260,8 @@ describe('', () => { render(); - if (injectedProps) { - expect(typeof injectedProps.onBlur).to.equal('function'); - expect(typeof injectedProps.onFocus).to.equal('function'); - } + expect(typeof injectedProps.onBlur).to.equal('function'); + expect(typeof injectedProps.onFocus).to.equal('function'); }); describe('target mock implementations', () => { diff --git a/packages/mui-material-next/src/InputBase/InputBase.tsx b/packages/mui-material-next/src/InputBase/InputBase.tsx index 5121732480ba62..e5c01f9fc20a32 100644 --- a/packages/mui-material-next/src/InputBase/InputBase.tsx +++ b/packages/mui-material-next/src/InputBase/InputBase.tsx @@ -319,7 +319,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)) { From 073ae1ba9f8d4b27d4ec5da7af6ea5caefca7f04 Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Fri, 20 Oct 2023 18:55:56 +0800 Subject: [PATCH 32/34] Add a test --- .../src/InputBase/InputBase.test.tsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/mui-material-next/src/InputBase/InputBase.test.tsx b/packages/mui-material-next/src/InputBase/InputBase.test.tsx index c183219600c51f..b91ed92548e827 100644 --- a/packages/mui-material-next/src/InputBase/InputBase.test.tsx +++ b/packages/mui-material-next/src/InputBase/InputBase.test.tsx @@ -58,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(); From 2f6bc51f1cb6c406c1146663950123ad75e2f431 Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Fri, 20 Oct 2023 18:56:51 +0800 Subject: [PATCH 33/34] Add a runtime warning for multilne props on a single line input --- .../src/InputBase/InputBase.tsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/mui-material-next/src/InputBase/InputBase.tsx b/packages/mui-material-next/src/InputBase/InputBase.tsx index e5c01f9fc20a32..fcbb3b667027a3 100644 --- a/packages/mui-material-next/src/InputBase/InputBase.tsx +++ b/packages/mui-material-next/src/InputBase/InputBase.tsx @@ -302,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(); From 5d132def976c544fafb24c1c734e124fb0b52b7d Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Mon, 23 Oct 2023 16:50:59 +0800 Subject: [PATCH 34/34] Fix test --- .../mui-material/src/InputLabel/InputLabel.test.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) 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', + }); }); }); });