From 3618e05ef16e08eac6751e5b26e55eaed0b2e306 Mon Sep 17 00:00:00 2001 From: flavien Date: Thu, 12 Dec 2024 12:24:31 +0100 Subject: [PATCH] [pickers] Use the new ownerState object on the PickersTextField component --- .../PickersSectionList/PickersSectionList.tsx | 21 +-- .../PickersSectionList.types.ts | 18 +- .../PickersFilledInput/PickersFilledInput.tsx | 46 ++--- .../pickersFilledInputClasses.ts | 5 +- .../PickersInput/PickersInput.tsx | 37 ++-- .../PickersInput/pickersInputClasses.ts | 5 +- .../PickersInputBase/PickersInputBase.tsx | 88 +++++----- .../PickersInputBase.types.ts | 1 + .../PickersOutlinedInput/Outline.tsx | 32 ++-- .../PickersOutlinedInput.tsx | 39 ++--- .../src/PickersTextField/PickersTextField.tsx | 163 ++++++++++-------- .../PickersTextField.types.ts | 49 +++++- .../usePickerTextFieldOwnerState.ts | 18 ++ .../PickersArrowSwitcher.tsx | 4 +- .../PickersArrowSwitcher.types.tsx | 2 +- .../src/internals/hooks/useFieldOwnerState.ts | 10 +- packages/x-date-pickers/src/models/fields.ts | 12 +- 17 files changed, 316 insertions(+), 234 deletions(-) create mode 100644 packages/x-date-pickers/src/PickersTextField/usePickerTextFieldOwnerState.ts diff --git a/packages/x-date-pickers/src/PickersSectionList/PickersSectionList.tsx b/packages/x-date-pickers/src/PickersSectionList/PickersSectionList.tsx index ae8db0f9ee849..ac7a86aec5881 100644 --- a/packages/x-date-pickers/src/PickersSectionList/PickersSectionList.tsx +++ b/packages/x-date-pickers/src/PickersSectionList/PickersSectionList.tsx @@ -11,6 +11,7 @@ import { PickersSectionListClasses, } from './pickersSectionListClasses'; import { PickersSectionListProps, PickersSectionElement } from './PickersSectionList.types'; +import { usePickerPrivateContext } from '../internals/hooks/usePickerPrivateContext'; export const PickersSectionListRoot = styled('div', { name: 'MuiPickersSectionList', @@ -43,9 +44,7 @@ export const PickersSectionListSectionContent = styled('span', { outline: 'none', }); -const useUtilityClasses = (ownerState: PickersSectionListProps) => { - const { classes } = ownerState; - +const useUtilityClasses = (classes: Partial | undefined) => { const slots = { root: ['root'], section: ['section'], @@ -62,6 +61,7 @@ interface PickersSectionProps extends Pick(null); const handleRootRef = useForkRef(ref, rootRef); @@ -216,7 +217,7 @@ const PickersSectionList = React.forwardRef(function PickersSectionList( suppressContentEditableWarning: true, }, className: classes.root, - ownerState: {}, + ownerState, }); return ( diff --git a/packages/x-date-pickers/src/PickersSectionList/PickersSectionList.types.ts b/packages/x-date-pickers/src/PickersSectionList/PickersSectionList.types.ts index 99781b259a846..62ceafeb5c7fb 100644 --- a/packages/x-date-pickers/src/PickersSectionList/PickersSectionList.types.ts +++ b/packages/x-date-pickers/src/PickersSectionList/PickersSectionList.types.ts @@ -1,6 +1,7 @@ import * as React from 'react'; import { SlotComponentProps } from '@mui/utils'; import { PickersSectionListClasses } from './pickersSectionListClasses'; +import { PickerOwnerState } from '../models'; export interface PickersSectionListSlots { root: React.ElementType; @@ -9,11 +10,20 @@ export interface PickersSectionListSlots { sectionContent: React.ElementType; } +export interface PickerSectionSeparatorOwnerState extends PickerOwnerState { + /** + * The position of the separator. + * `before` if the separator is rendered before the section content. + * `after` if the separator is rendered after the section content. + */ + separatorPosition: 'before' | 'after'; +} + export interface PickersSectionListSlotProps { - root?: SlotComponentProps<'div', {}, {}>; - section?: SlotComponentProps<'span', {}, {}>; - sectionSeparator?: SlotComponentProps<'span', {}, { position: 'before' | 'after' }>; - sectionContent?: SlotComponentProps<'span', {}, {}>; + root?: SlotComponentProps<'div', {}, PickerOwnerState>; + section?: SlotComponentProps<'span', {}, PickerOwnerState>; + sectionSeparator?: SlotComponentProps<'span', {}, PickerSectionSeparatorOwnerState>; + sectionContent?: SlotComponentProps<'span', {}, PickerOwnerState>; } export interface PickersSectionElement { diff --git a/packages/x-date-pickers/src/PickersTextField/PickersFilledInput/PickersFilledInput.tsx b/packages/x-date-pickers/src/PickersTextField/PickersFilledInput/PickersFilledInput.tsx index 74e10a3bd0506..6a0f1423894c0 100644 --- a/packages/x-date-pickers/src/PickersTextField/PickersFilledInput/PickersFilledInput.tsx +++ b/packages/x-date-pickers/src/PickersTextField/PickersFilledInput/PickersFilledInput.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; import PropTypes from 'prop-types'; -import { FormControlState, useFormControl } from '@mui/material/FormControl'; import { styled, useThemeProps } from '@mui/material/styles'; import { shouldForwardProp } from '@mui/system'; import { refType } from '@mui/utils'; @@ -8,12 +7,14 @@ import composeClasses from '@mui/utils/composeClasses'; import { pickersFilledInputClasses, getPickersFilledInputUtilityClass, + PickersFilledInputClasses, } from './pickersFilledInputClasses'; import { PickersInputBaseProps, PickersInputBase } from '../PickersInputBase'; import { PickersInputBaseRoot, PickersInputBaseSectionsContainer, } from '../PickersInputBase/PickersInputBase'; +import { PickersTextFieldOwnerState } from '../PickersTextField.types'; export interface PickersFilledInputProps extends PickersInputBaseProps { disableUnderline?: boolean; @@ -25,7 +26,7 @@ const PickersFilledInputRoot = styled(PickersInputBaseRoot, { slot: 'Root', overridesResolver: (props, styles) => styles.root, shouldForwardProp: (prop) => shouldForwardProp(prop) && prop !== 'disableUnderline', -})<{ ownerState: OwnerStateType }>(({ theme }) => { +})<{ ownerState: PickersTextFieldOwnerState }>(({ theme }) => { 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)'; @@ -58,7 +59,7 @@ const PickersFilledInputRoot = styled(PickersInputBaseRoot, { // @ts-ignore .filter((key) => (theme.vars ?? theme).palette[key].main) .map((color) => ({ - props: { color, disableUnderline: false }, + props: { inputColor: color, disableUnderline: false }, style: { '&::after': { // @ts-ignore @@ -120,13 +121,13 @@ const PickersFilledInputRoot = styled(PickersInputBaseRoot, { }, }, { - props: ({ startAdornment }: OwnerStateType) => !!startAdornment, + props: { isInputAdornedStart: true }, style: { paddingLeft: 12, }, }, { - props: ({ endAdornment }: OwnerStateType) => !!endAdornment, + props: { isInputAdornedEnd: true }, style: { paddingRight: 12, }, @@ -139,27 +140,28 @@ const PickersFilledSectionsContainer = styled(PickersInputBaseSectionsContainer, name: 'MuiPickersFilledInput', slot: 'sectionsContainer', overridesResolver: (props, styles) => styles.sectionsContainer, -})<{ ownerState: OwnerStateType }>({ + shouldForwardProp: (prop) => shouldForwardProp(prop) && prop !== 'hiddenLabel', +})<{ ownerState: PickersTextFieldOwnerState }>({ paddingTop: 25, paddingRight: 12, paddingBottom: 8, paddingLeft: 12, variants: [ { - props: { size: 'small' }, + props: { inputSize: 'small' }, style: { paddingTop: 21, paddingBottom: 4, }, }, { - props: ({ startAdornment }: OwnerStateType) => !!startAdornment, + props: { isInputAdornedStart: true }, style: { paddingLeft: 0, }, }, { - props: ({ endAdornment }: OwnerStateType) => !!endAdornment, + props: { isInputAdornedEnd: true }, style: { paddingRight: 0, }, @@ -172,7 +174,7 @@ const PickersFilledSectionsContainer = styled(PickersInputBaseSectionsContainer, }, }, { - props: { hiddenLabel: true, size: 'small' }, + props: { hiddenLabel: true, inputSize: 'small' }, style: { paddingTop: 8, paddingBottom: 9, @@ -181,11 +183,9 @@ const PickersFilledSectionsContainer = styled(PickersInputBaseSectionsContainer, ], }); -const useUtilityClasses = (ownerState: OwnerStateType) => { - const { classes, disableUnderline } = ownerState; - +const useUtilityClasses = (classes: Partial | undefined) => { const slots = { - root: ['root', !disableUnderline && 'underline'], + root: ['root'], input: ['input'], }; @@ -197,10 +197,6 @@ const useUtilityClasses = (ownerState: OwnerStateType) => { }; }; -interface OwnerStateType - extends FormControlState, - Omit {} - /** * @ignore - internal component. */ @@ -217,24 +213,18 @@ const PickersFilledInput = React.forwardRef(function PickersFilledInput( label, autoFocus, disableUnderline = false, + hiddenLabel = false, + classes: classesProp, ownerState: ownerStateProp, ...other } = props; - const muiFormControl = useFormControl(); - - const ownerState = { - ...props, - ...ownerStateProp, - ...muiFormControl, - color: muiFormControl?.color || 'primary', - }; - const classes = useUtilityClasses(ownerState); + const classes = useUtilityClasses(classesProp); return ( styles.root, -})<{ ownerState: OwnerStateType }>(({ theme }) => { + shouldForwardProp: (prop) => shouldForwardProp(prop) && prop !== 'disableUnderline', +})<{ ownerState: PickersTextFieldOwnerState }>(({ theme }) => { const light = theme.palette.mode === 'light'; let bottomLineColor = light ? 'rgba(0, 0, 0, 0.42)' : 'rgba(255, 255, 255, 0.7)'; if (theme.vars) { @@ -31,7 +37,7 @@ const PickersInputRoot = styled(PickersInputBaseRoot, { // @ts-ignore .filter((key) => (theme.vars ?? theme).palette[key].main) .map((color) => ({ - props: { color }, + props: { inputColor: color }, style: { '&::after': { // @ts-ignore @@ -96,11 +102,9 @@ const PickersInputRoot = styled(PickersInputBaseRoot, { }; }); -const useUtilityClasses = (ownerState: OwnerStateType) => { - const { classes, disableUnderline } = ownerState; - +const useUtilityClasses = (classes: Partial | undefined) => { const slots = { - root: ['root', !disableUnderline && 'underline'], + root: ['root'], input: ['input'], }; @@ -112,10 +116,6 @@ const useUtilityClasses = (ownerState: OwnerStateType) => { }; }; -interface OwnerStateType - extends FormControlState, - Omit {} - /** * @ignore - internal component. */ @@ -133,23 +133,16 @@ const PickersInput = React.forwardRef(function PickersInput( autoFocus, disableUnderline = false, ownerState: ownerStateProp, + classes: classesProp, ...other } = props; - const muiFormControl = useFormControl(); - - const ownerState = { - ...props, - ...ownerStateProp, - ...muiFormControl, - disableUnderline, - color: muiFormControl?.color || 'primary', - }; - const classes = useUtilityClasses(ownerState); + const classes = useUtilityClasses(classesProp); return ( Math.round(value * 1e5) / 1e5; @@ -28,7 +30,7 @@ export const PickersInputBaseRoot = styled('div', { name: 'MuiPickersInputBase', slot: 'Root', overridesResolver: (props, styles) => styles.root, -})<{ ownerState: OwnerStateType }>(({ theme }) => ({ +})<{ ownerState: PickersTextFieldOwnerState }>(({ theme }) => ({ ...theme.typography.body1, color: (theme.vars || theme).palette.text.primary, cursor: 'text', @@ -51,7 +53,7 @@ export const PickersInputBaseSectionsContainer = styled(PickersSectionListRoot, name: 'MuiPickersInputBase', slot: 'SectionsContainer', overridesResolver: (props, styles) => styles.sectionsContainer, -})<{ ownerState: OwnerStateType }>(({ theme }) => ({ +})<{ ownerState: PickersTextFieldOwnerState }>(({ theme }) => ({ padding: '4px 0 5px', fontFamily: theme.typography.fontFamily, fontSize: 'inherit', @@ -66,19 +68,19 @@ export const PickersInputBaseSectionsContainer = styled(PickersSectionListRoot, width: '182px', variants: [ { - props: { isRtl: true }, + props: { fieldDirection: 'rtl' }, style: { textAlign: 'right /*! @noflip */' as any, }, }, { - props: { size: 'small' }, + props: { inputSize: 'small' }, style: { paddingTop: 1, }, }, { - props: { adornedStart: false, focused: false, filled: false }, + props: { isInputAdornedStart: false, isFieldFocused: false, isFieldValueEmpty: true }, style: { color: 'currentColor', opacity: 0, @@ -86,8 +88,12 @@ export const PickersInputBaseSectionsContainer = styled(PickersSectionListRoot, }, { // Can't use the object notation because label can be null or undefined - props: ({ adornedStart, focused, filled, label }: OwnerStateType) => - !adornedStart && !focused && !filled && label == null, + props: { + isInputAdornedStart: false, + isFieldFocused: false, + isFieldValueEmpty: true, + inputHasLabel: false, + }, style: theme.vars ? { opacity: theme.vars.opacity.inputPlaceholder, @@ -140,32 +146,34 @@ const PickersInputBaseInput = styled('input', { ...visuallyHidden, }); -const useUtilityClasses = (ownerState: OwnerStateType) => { +const useUtilityClasses = ( + classes: Partial | undefined, + ownerState: PickersTextFieldOwnerState, +) => { const { - focused, - disabled, - error, - classes, - fullWidth, - readOnly, - color, - size, - endAdornment, - startAdornment, + isFieldFocused, + isFieldDisabled, + isFieldReadOnly, + hasFieldError, + inputSize, + inputFullWidth, + inputColor, + isInputAdornedStart, + isInputAdornedEnd, } = ownerState; const slots = { root: [ 'root', - focused && !disabled && 'focused', - disabled && 'disabled', - readOnly && 'readOnly', - error && 'error', - fullWidth && 'fullWidth', - `color${capitalize(color!)}`, - size === 'small' && 'inputSizeSmall', - Boolean(startAdornment) && 'adornedStart', - Boolean(endAdornment) && 'adornedEnd', + isFieldFocused && !isFieldDisabled && 'focused', + isFieldDisabled && 'disabled', + isFieldReadOnly && 'readOnly', + hasFieldError && 'error', + inputFullWidth && 'fullWidth', + `color${capitalize(inputColor!)}`, + inputSize === 'small' && 'inputSizeSmall', + isInputAdornedStart && 'adornedStart', + isInputAdornedEnd && 'adornedEnd', ], notchedOutline: ['notchedOutline'], input: ['input'], @@ -178,12 +186,6 @@ const useUtilityClasses = (ownerState: OwnerStateType) => { return composeClasses(slots, getPickersInputBaseUtilityClass, classes); }; -interface OwnerStateType - extends FormControlState, - Omit { - isRtl: boolean; -} - /** * @ignore - internal component. */ @@ -195,6 +197,7 @@ const PickersInputBase = React.forwardRef(function PickersInputBase( props: inProps, name: 'MuiPickersInputBase', }); + const ownerState = usePickerTextFieldOwnerState(); const { elements, @@ -223,13 +226,13 @@ const PickersInputBase = React.forwardRef(function PickersInputBase( sectionListRef, onFocus, onBlur, + classes: classesProp, ...other } = props; const rootRef = React.useRef(null); const handleRootRef = useForkRef(ref, rootRef); const handleInputRef = useForkRef(inputProps?.ref, inputRef); - const isRtl = useRtl(); const muiFormControl = useFormControl(); if (!muiFormControl) { throw new Error( @@ -265,13 +268,7 @@ const PickersInputBase = React.forwardRef(function PickersInputBase( } }, [muiFormControl, areAllSectionsEmpty]); - const ownerState: OwnerStateType = { - ...(props as Omit), - ...muiFormControl, - isRtl, - }; - - const classes = useUtilityClasses(ownerState); + const classes = useUtilityClasses(classesProp, ownerState); const InputRoot = slots?.root || PickersInputBaseRoot; const inputRootProps = useSlotProps({ @@ -310,12 +307,13 @@ const PickersInputBase = React.forwardRef(function PickersInputBase( }} slotProps={{ root: { + ...slotProps?.input, ownerState, } as any, sectionContent: { className: pickersInputBaseClasses.sectionContent }, - sectionSeparator: ({ position }) => ({ + sectionSeparator: ({ separatorPosition }) => ({ className: - position === 'before' + separatorPosition === 'before' ? pickersInputBaseClasses.sectionBefore : pickersInputBaseClasses.sectionAfter, }), diff --git a/packages/x-date-pickers/src/PickersTextField/PickersInputBase/PickersInputBase.types.ts b/packages/x-date-pickers/src/PickersTextField/PickersInputBase/PickersInputBase.types.ts index ba7c90dd39a7c..56b540e1c8cff 100644 --- a/packages/x-date-pickers/src/PickersTextField/PickersInputBase/PickersInputBase.types.ts +++ b/packages/x-date-pickers/src/PickersTextField/PickersInputBase/PickersInputBase.types.ts @@ -65,5 +65,6 @@ export interface PickersInputBaseProps */ slotProps?: { root?: any; + input?: any; }; } diff --git a/packages/x-date-pickers/src/PickersTextField/PickersOutlinedInput/Outline.tsx b/packages/x-date-pickers/src/PickersTextField/PickersOutlinedInput/Outline.tsx index 9195cb4bc84a8..7c3f10d654e15 100644 --- a/packages/x-date-pickers/src/PickersTextField/PickersOutlinedInput/Outline.tsx +++ b/packages/x-date-pickers/src/PickersTextField/PickersOutlinedInput/Outline.tsx @@ -1,22 +1,20 @@ import * as React from 'react'; import { styled } from '@mui/material/styles'; +import { shouldForwardProp } from '@mui/system/createStyled'; +import { usePickerTextFieldOwnerState } from '../usePickerTextFieldOwnerState'; +import { PickersTextFieldOwnerState } from '../PickersTextField.types'; interface OutlineProps extends React.HTMLAttributes { notched: boolean; shrink: boolean; label: React.ReactNode; - ownerState: any; -} - -interface OutlineOwnerState extends OutlineProps { - withLabel: boolean; } const OutlineRoot = styled('fieldset', { name: 'MuiPickersOutlinedInput', slot: 'NotchedOutline', overridesResolver: (props, styles) => styles.notchedOutline, -})<{ ownerState: OutlineOwnerState }>(({ theme }) => { +})<{ ownerState: PickersTextFieldOwnerState }>(({ theme }) => { const borderColor = theme.palette.mode === 'light' ? 'rgba(0, 0, 0, 0.23)' : 'rgba(255, 255, 255, 0.23)'; return { @@ -39,18 +37,21 @@ const OutlineRoot = styled('fieldset', { : borderColor, }; }); + const OutlineLabel = styled('span')(({ theme }) => ({ fontFamily: theme.typography.fontFamily, fontSize: 'inherit', })); -const OutlineLegend = styled('legend')<{ ownerState: any }>(({ theme }) => ({ +const OutlineLegend = styled('legend', { + shouldForwardProp: (prop) => shouldForwardProp(prop) && prop !== 'notched', +})<{ ownerState: PickersTextFieldOwnerState; notched: boolean }>(({ theme }) => ({ float: 'unset', // Fix conflict with bootstrap width: 'auto', // Fix conflict with bootstrap overflow: 'hidden', // Fix Horizontal scroll when label too long variants: [ { - props: { withLabel: false }, + props: { inputHasLabel: false }, style: { padding: 0, lineHeight: '11px', // sync with `height` in `legend` styles @@ -61,7 +62,7 @@ const OutlineLegend = styled('legend')<{ ownerState: any }>(({ theme }) => ({ }, }, { - props: { withLabel: true }, + props: { inputHasLabel: true }, style: { display: 'block', // Fix conflict with normalize.css and sanitize.css padding: 0, @@ -84,7 +85,7 @@ const OutlineLegend = styled('legend')<{ ownerState: any }>(({ theme }) => ({ }, }, { - props: { withLabel: true, notched: true }, + props: { inputHasLabel: true, notched: true }, style: { maxWidth: '100%', transition: theme.transitions.create('max-width', { @@ -102,16 +103,13 @@ const OutlineLegend = styled('legend')<{ ownerState: any }>(({ theme }) => ({ */ export default function Outline(props: OutlineProps) { const { children, className, label, notched, shrink, ...other } = props; - const withLabel = label != null && label !== ''; - const ownerState = { - ...props, - withLabel, - }; + const ownerState = usePickerTextFieldOwnerState(); + return ( - + {/* Use the nominal use case of the legend, avoid rendering artefacts. */} - {withLabel ? ( + {label ? ( {label} ) : ( // notranslate needed while Google Translate will not fix zero-width space issue diff --git a/packages/x-date-pickers/src/PickersTextField/PickersOutlinedInput/PickersOutlinedInput.tsx b/packages/x-date-pickers/src/PickersTextField/PickersOutlinedInput/PickersOutlinedInput.tsx index 9ab29a6dfb616..bbd2f7739ca9c 100644 --- a/packages/x-date-pickers/src/PickersTextField/PickersOutlinedInput/PickersOutlinedInput.tsx +++ b/packages/x-date-pickers/src/PickersTextField/PickersOutlinedInput/PickersOutlinedInput.tsx @@ -1,12 +1,13 @@ import * as React from 'react'; import PropTypes from 'prop-types'; -import { FormControlState, useFormControl } from '@mui/material/FormControl'; +import { useFormControl } from '@mui/material/FormControl'; import { styled, useThemeProps } from '@mui/material/styles'; import { refType } from '@mui/utils'; import composeClasses from '@mui/utils/composeClasses'; import { pickersOutlinedInputClasses, getPickersOutlinedInputUtilityClass, + PickersOutlinedInputClasses, } from './pickersOutlinedInputClasses'; import Outline from './Outline'; import { PickersInputBase, PickersInputBaseProps } from '../PickersInputBase'; @@ -14,6 +15,7 @@ import { PickersInputBaseRoot, PickersInputBaseSectionsContainer, } from '../PickersInputBase/PickersInputBase'; +import { PickersTextFieldOwnerState } from '../PickersTextField.types'; export interface PickersOutlinedInputProps extends PickersInputBaseProps { notched?: boolean; @@ -23,7 +25,7 @@ const PickersOutlinedInputRoot = styled(PickersInputBaseRoot, { name: 'MuiPickersOutlinedInput', slot: 'Root', overridesResolver: (props, styles) => styles.root, -})<{ ownerState: OwnerStateType }>(({ theme }) => { +})<{ ownerState: PickersTextFieldOwnerState }>(({ theme }) => { const borderColor = theme.palette.mode === 'light' ? 'rgba(0, 0, 0, 0.23)' : 'rgba(255, 255, 255, 0.23)'; return { @@ -59,7 +61,7 @@ const PickersOutlinedInputRoot = styled(PickersInputBaseRoot, { // @ts-ignore .filter((key) => (theme.vars ?? theme).palette[key]?.main ?? false) .map((color) => ({ - props: { color }, + props: { inputColor: color }, style: { [`&.${pickersOutlinedInputClasses.focused}:not(.${pickersOutlinedInputClasses.error}) .${pickersOutlinedInputClasses.notchedOutline}`]: { @@ -75,11 +77,11 @@ const PickersOutlinedInputSectionsContainer = styled(PickersInputBaseSectionsCon name: 'MuiPickersOutlinedInput', slot: 'SectionsContainer', overridesResolver: (props, styles) => styles.sectionsContainer, -})<{ ownerState: OwnerStateType }>({ +})<{ ownerState: PickersTextFieldOwnerState }>({ padding: '16.5px 0', variants: [ { - props: { size: 'small' }, + props: { inputSize: 'small' }, style: { padding: '8.5px 0', }, @@ -87,9 +89,7 @@ const PickersOutlinedInputSectionsContainer = styled(PickersInputBaseSectionsCon ], }); -const useUtilityClasses = (ownerState: OwnerStateType) => { - const { classes } = ownerState; - +const useUtilityClasses = (classes: Partial | undefined) => { const slots = { root: ['root'], notchedOutline: ['notchedOutline'], @@ -104,10 +104,6 @@ const useUtilityClasses = (ownerState: OwnerStateType) => { }; }; -interface OwnerStateType - extends FormControlState, - Omit {} - /** * @ignore - internal component. */ @@ -120,17 +116,17 @@ const PickersOutlinedInput = React.forwardRef(function PickersOutlinedInput( name: 'MuiPickersOutlinedInput', }); - const { label, autoFocus, ownerState: ownerStateProp, notched, ...other } = props; + const { + label, + autoFocus, + ownerState: ownerStateProp, + classes: classesProp, + notched, + ...other + } = props; const muiFormControl = useFormControl(); - - const ownerState = { - ...props, - ...ownerStateProp, - ...muiFormControl, - color: muiFormControl?.color || 'primary', - }; - const classes = useUtilityClasses(ownerState); + const classes = useUtilityClasses(classesProp); return ( )} {...other} diff --git a/packages/x-date-pickers/src/PickersTextField/PickersTextField.tsx b/packages/x-date-pickers/src/PickersTextField/PickersTextField.tsx index 6969a67b254b0..10e94a73e8e7f 100644 --- a/packages/x-date-pickers/src/PickersTextField/PickersTextField.tsx +++ b/packages/x-date-pickers/src/PickersTextField/PickersTextField.tsx @@ -10,11 +10,16 @@ import useId from '@mui/utils/useId'; import InputLabel from '@mui/material/InputLabel'; import FormHelperText from '@mui/material/FormHelperText'; import FormControl from '@mui/material/FormControl'; -import { getPickersTextFieldUtilityClass } from './pickersTextFieldClasses'; -import { PickersTextFieldProps } from './PickersTextField.types'; +import { + getPickersTextFieldUtilityClass, + PickersTextFieldClasses, +} from './pickersTextFieldClasses'; +import { PickersTextFieldOwnerState, PickersTextFieldProps } from './PickersTextField.types'; import { PickersOutlinedInput } from './PickersOutlinedInput'; import { PickersFilledInput } from './PickersFilledInput'; import { PickersInput } from './PickersInput'; +import { useFieldOwnerState } from '../internals/hooks/useFieldOwnerState'; +import { PickerTextFieldOwnerStateContext } from './usePickerTextFieldOwnerState'; const VARIANT_COMPONENT = { standard: PickersInput, @@ -26,25 +31,26 @@ const PickersTextFieldRoot = styled(FormControl, { name: 'MuiPickersTextField', slot: 'Root', overridesResolver: (props, styles) => styles.root, -})<{ ownerState: OwnerStateType }>({}); +})<{ ownerState: PickersTextFieldOwnerState }>({}); -const useUtilityClasses = (ownerState: PickersTextFieldProps) => { - const { focused, disabled, classes, required } = ownerState; +const useUtilityClasses = ( + classes: Partial | undefined, + ownerState: PickersTextFieldOwnerState, +) => { + const { isFieldFocused, isFieldDisabled, isFieldRequired } = ownerState; const slots = { root: [ 'root', - focused && !disabled && 'focused', - disabled && 'disabled', - required && 'required', + isFieldFocused && !isFieldDisabled && 'focused', + isFieldDisabled && 'disabled', + isFieldRequired && 'required', ], }; return composeClasses(slots, getPickersTextFieldUtilityClass, classes); }; -type OwnerStateType = Partial; - const PickersTextField = React.forwardRef(function PickersTextField( inProps: PickersTextFieldProps, ref: React.Ref, @@ -59,6 +65,7 @@ const PickersTextField = React.forwardRef(function PickersTextField( onFocus, onBlur, className, + classes: classesProp, color = 'primary', disabled = false, error = false, @@ -102,70 +109,90 @@ const PickersTextField = React.forwardRef(function PickersTextField( const helperTextId = helperText && id ? `${id}-helper-text` : undefined; const inputLabelId = label && id ? `${id}-label` : undefined; - const ownerState = { - ...props, - color, - disabled, - error, - focused, - required, - variant, - }; + const fieldOwnerState = useFieldOwnerState(props); + const ownerState = React.useMemo( + () => ({ + ...fieldOwnerState, + isFieldValueEmpty: areAllSectionsEmpty, + isFieldFocused: focused ?? false, + hasFieldError: error ?? false, - const classes = useUtilityClasses(ownerState); + inputSize: props.size ?? 'medium', + inputColor: color ?? 'primary', + inputFullWidth: fullWidth ?? false, + isInputAdornedStart: Boolean(startAdornment), + isInputAdornedEnd: Boolean(endAdornment), + inputHasLabel: !!label, + }), + [ + fieldOwnerState, + areAllSectionsEmpty, + focused, + error, + props.size, + color, + fullWidth, + startAdornment, + endAdornment, + label, + ], + ); + const classes = useUtilityClasses(classesProp, ownerState); const PickersInputComponent = VARIANT_COMPONENT[variant]; return ( - - - {label} - - + - {helperText && ( - - {helperText} - - )} - + required={required} + ownerState={ownerState} + {...other} + > + + {label} + + + {helperText && ( + + {helperText} + + )} + + ); }); diff --git a/packages/x-date-pickers/src/PickersTextField/PickersTextField.types.ts b/packages/x-date-pickers/src/PickersTextField/PickersTextField.types.ts index ceb5909d56d5f..34817992dbe25 100644 --- a/packages/x-date-pickers/src/PickersTextField/PickersTextField.types.ts +++ b/packages/x-date-pickers/src/PickersTextField/PickersTextField.types.ts @@ -1,12 +1,13 @@ import * as React from 'react'; -import { FormControlProps } from '@mui/material/FormControl'; +import { FormControlOwnProps, FormControlProps } from '@mui/material/FormControl'; import { FormHelperTextProps } from '@mui/material/FormHelperText'; import { InputLabelProps } from '@mui/material/InputLabel'; import { TextFieldVariants } from '@mui/material/TextField'; import { PickersInputPropsUsedByField } from './PickersInputBase/PickersInputBase.types'; -import { PickersInputProps } from './PickersInput'; -import { PickersOutlinedInputProps } from './PickersOutlinedInput'; -import { PickersFilledInputProps } from './PickersFilledInput'; +import type { PickersInputProps } from './PickersInput'; +import type { PickersOutlinedInputProps } from './PickersOutlinedInput'; +import type { PickersFilledInputProps } from './PickersFilledInput'; +import { FieldOwnerState } from '../models'; interface PickersTextFieldPropsUsedByField { onFocus: React.FocusEventHandler; @@ -79,3 +80,43 @@ export type PickersTextFieldProps; + /** + * The color of the input. + */ + inputColor: Exclude; + /** + * `true` if the input takes up the full width of its container. + */ + inputFullWidth: boolean; + /** + * `true` if the input has a start adornment, `false` otherwise. + */ + isInputAdornedStart: boolean; + /** + * `true` if the input has an end adornment, `false` otherwise. + */ + isInputAdornedEnd: boolean; + /** + * `true` if the input has a label, `false` otherwise. + */ + inputHasLabel: boolean; +} diff --git a/packages/x-date-pickers/src/PickersTextField/usePickerTextFieldOwnerState.ts b/packages/x-date-pickers/src/PickersTextField/usePickerTextFieldOwnerState.ts new file mode 100644 index 0000000000000..7a8245a25840d --- /dev/null +++ b/packages/x-date-pickers/src/PickersTextField/usePickerTextFieldOwnerState.ts @@ -0,0 +1,18 @@ +import * as React from 'react'; +import { PickersTextFieldOwnerState } from './PickersTextField.types'; + +export const PickerTextFieldOwnerStateContext = + React.createContext(null); + +export const usePickerTextFieldOwnerState = () => { + const value = React.useContext(PickerTextFieldOwnerStateContext); + if (value == null) { + throw new Error( + [ + 'MUI X: The `usePickerTextFieldOwnerState` can only be called in components that are used inside a PickerTextField component', + ].join('\n'), + ); + } + + return value; +}; diff --git a/packages/x-date-pickers/src/internals/components/PickersArrowSwitcher/PickersArrowSwitcher.tsx b/packages/x-date-pickers/src/internals/components/PickersArrowSwitcher/PickersArrowSwitcher.tsx index 65bea5a6bb716..d768b3890b348 100644 --- a/packages/x-date-pickers/src/internals/components/PickersArrowSwitcher/PickersArrowSwitcher.tsx +++ b/packages/x-date-pickers/src/internals/components/PickersArrowSwitcher/PickersArrowSwitcher.tsx @@ -119,7 +119,7 @@ export const PickersArrowSwitcher = React.forwardRef(function PickersArrowSwitch edge: 'end', onClick: previousProps.goTo, }, - ownerState: { ...ownerState, hidden: previousProps.isHidden ?? false }, + ownerState: { ...ownerState, isButtonHidden: previousProps.isHidden ?? false }, className: clsx(classes.button, classes.previousIconButton), }); @@ -135,7 +135,7 @@ export const PickersArrowSwitcher = React.forwardRef(function PickersArrowSwitch edge: 'start', onClick: nextProps.goTo, }, - ownerState: { ...ownerState, hidden: nextProps.isHidden ?? false }, + ownerState: { ...ownerState, isButtonHidden: nextProps.isHidden ?? false }, className: clsx(classes.button, classes.nextIconButton), }); diff --git a/packages/x-date-pickers/src/internals/components/PickersArrowSwitcher/PickersArrowSwitcher.types.tsx b/packages/x-date-pickers/src/internals/components/PickersArrowSwitcher/PickersArrowSwitcher.types.tsx index 725f070b17504..0df4d813ea96a 100644 --- a/packages/x-date-pickers/src/internals/components/PickersArrowSwitcher/PickersArrowSwitcher.types.tsx +++ b/packages/x-date-pickers/src/internals/components/PickersArrowSwitcher/PickersArrowSwitcher.types.tsx @@ -46,7 +46,7 @@ export interface PickersArrowSwitcherOwnerState extends PickerOwnerState { /** * If `true`, this button should be hidden. */ - hidden: boolean; + isButtonHidden: boolean; } export interface PickersArrowSwitcherSlotPropsOverrides {} diff --git a/packages/x-date-pickers/src/internals/hooks/useFieldOwnerState.ts b/packages/x-date-pickers/src/internals/hooks/useFieldOwnerState.ts index bc1c326465dd5..9ec96ac340d73 100644 --- a/packages/x-date-pickers/src/internals/hooks/useFieldOwnerState.ts +++ b/packages/x-date-pickers/src/internals/hooks/useFieldOwnerState.ts @@ -1,19 +1,25 @@ import * as React from 'react'; +import { useRtl } from '@mui/system/RtlProvider'; import { FieldOwnerState } from '../../models'; import { FormProps } from '../models'; import { usePickerPrivateContext } from './usePickerPrivateContext'; export function useFieldOwnerState(parameters: UseFieldOwnerStateParameters) { const { ownerState: pickerOwnerState } = usePickerPrivateContext(); + const isRtl = useRtl(); return React.useMemo( () => ({ ...pickerOwnerState, isFieldDisabled: parameters.disabled ?? false, isFieldReadOnly: parameters.readOnly ?? false, + isFieldRequired: parameters.required ?? false, + fieldDirection: isRtl ? 'rtl' : 'ltr', }), - [pickerOwnerState, parameters.disabled, parameters.readOnly], + [pickerOwnerState, parameters.disabled, parameters.readOnly, parameters.required, isRtl], ); } -interface UseFieldOwnerStateParameters extends FormProps {} +interface UseFieldOwnerStateParameters extends FormProps { + required?: boolean; +} diff --git a/packages/x-date-pickers/src/models/fields.ts b/packages/x-date-pickers/src/models/fields.ts index 05c0c4e0460ff..f256b61648142 100644 --- a/packages/x-date-pickers/src/models/fields.ts +++ b/packages/x-date-pickers/src/models/fields.ts @@ -4,7 +4,7 @@ import type { ExportedUseClearableFieldProps, UseClearableFieldResponse, } from '../hooks/useClearableField'; -import { ExportedPickersSectionListProps } from '../PickersSectionList'; +import type { ExportedPickersSectionListProps } from '../PickersSectionList'; import type { UseFieldInternalProps, UseFieldResponse } from '../internals/hooks/useField'; import type { PickersTextFieldProps } from '../PickersTextField'; import { @@ -142,6 +142,16 @@ export interface FieldOwnerState extends PickerOwnerState { * `true` if the field is read-only, `false` otherwise. */ isFieldReadOnly: boolean; + /** + * `true` if the field is required, `false` otherwise. + */ + isFieldRequired: boolean; + /** + * The direction of the field. + * Is equal to "ltr" when the toolbar is in left-to-right direction. + * Is equal to "rtl" when the toolbar is in right-to-left direction. + */ + fieldDirection: 'ltr' | 'rtl'; } /**