diff --git a/packages/x-date-pickers/src/PickersSectionList/PickersSectionList.tsx b/packages/x-date-pickers/src/PickersSectionList/PickersSectionList.tsx index ae8db0f9ee84..ac7a86aec588 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 99781b259a84..62ceafeb5c7f 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 74e10a3bd050..5f8e95e6e12d 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,24 +7,34 @@ import composeClasses from '@mui/utils/composeClasses'; import { pickersFilledInputClasses, getPickersFilledInputUtilityClass, + PickersFilledInputClasses, } from './pickersFilledInputClasses'; import { PickersInputBaseProps, PickersInputBase } from '../PickersInputBase'; import { PickersInputBaseRoot, PickersInputBaseSectionsContainer, } from '../PickersInputBase/PickersInputBase'; +import { PickerTextFieldOwnerState } from '../PickersTextField.types'; +import { usePickerTextFieldOwnerState } from '../usePickerTextFieldOwnerState'; export interface PickersFilledInputProps extends PickersInputBaseProps { disableUnderline?: boolean; hiddenLabel?: boolean; } +export interface PickerFilledInputOwnerState extends PickerTextFieldOwnerState { + /** + * `true` if the input has an underline, `false` otherwise. + */ + inputHasUnderline: boolean; +} + const PickersFilledInputRoot = styled(PickersInputBaseRoot, { name: 'MuiPickersFilledInput', slot: 'Root', overridesResolver: (props, styles) => styles.root, shouldForwardProp: (prop) => shouldForwardProp(prop) && prop !== 'disableUnderline', -})<{ ownerState: OwnerStateType }>(({ theme }) => { +})<{ ownerState: PickerFilledInputOwnerState }>(({ 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 +67,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 +129,13 @@ const PickersFilledInputRoot = styled(PickersInputBaseRoot, { }, }, { - props: ({ startAdornment }: OwnerStateType) => !!startAdornment, + props: { hasStartAdornment: true }, style: { paddingLeft: 12, }, }, { - props: ({ endAdornment }: OwnerStateType) => !!endAdornment, + props: { hasEndAdornment: true }, style: { paddingRight: 12, }, @@ -139,27 +148,28 @@ const PickersFilledSectionsContainer = styled(PickersInputBaseSectionsContainer, name: 'MuiPickersFilledInput', slot: 'sectionsContainer', overridesResolver: (props, styles) => styles.sectionsContainer, -})<{ ownerState: OwnerStateType }>({ + shouldForwardProp: (prop) => shouldForwardProp(prop) && prop !== 'hiddenLabel', +})<{ ownerState: PickerFilledInputOwnerState }>({ 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: { hasStartAdornment: true }, style: { paddingLeft: 0, }, }, { - props: ({ endAdornment }: OwnerStateType) => !!endAdornment, + props: { hasEndAdornment: true }, style: { paddingRight: 0, }, @@ -172,7 +182,7 @@ const PickersFilledSectionsContainer = styled(PickersInputBaseSectionsContainer, }, }, { - props: { hiddenLabel: true, size: 'small' }, + props: { hiddenLabel: true, inputSize: 'small' }, style: { paddingTop: 8, paddingBottom: 9, @@ -181,11 +191,14 @@ const PickersFilledSectionsContainer = styled(PickersInputBaseSectionsContainer, ], }); -const useUtilityClasses = (ownerState: OwnerStateType) => { - const { classes, disableUnderline } = ownerState; +const useUtilityClasses = ( + classes: Partial | undefined, + ownerState: PickerFilledInputOwnerState, +) => { + const { inputHasUnderline } = ownerState; const slots = { - root: ['root', !disableUnderline && 'underline'], + root: ['root', inputHasUnderline && 'underline'], input: ['input'], }; @@ -197,10 +210,6 @@ const useUtilityClasses = (ownerState: OwnerStateType) => { }; }; -interface OwnerStateType - extends FormControlState, - Omit {} - /** * @ignore - internal component. */ @@ -217,28 +226,27 @@ const PickersFilledInput = React.forwardRef(function PickersFilledInput( label, autoFocus, disableUnderline = false, - ownerState: ownerStateProp, + hiddenLabel = false, + classes: classesProp, ...other } = props; - const muiFormControl = useFormControl(); - - const ownerState = { - ...props, - ...ownerStateProp, - ...muiFormControl, - color: muiFormControl?.color || 'primary', + const pickerTextFieldOwnerState = usePickerTextFieldOwnerState(); + const ownerState: PickerFilledInputOwnerState = { + ...pickerTextFieldOwnerState, + inputHasUnderline: !disableUnderline, }; - const classes = useUtilityClasses(ownerState); + const classes = useUtilityClasses(classesProp, ownerState); return ( ); }); diff --git a/packages/x-date-pickers/src/PickersTextField/PickersFilledInput/pickersFilledInputClasses.ts b/packages/x-date-pickers/src/PickersTextField/PickersFilledInput/pickersFilledInputClasses.ts index 96b70458ad49..fc5150048ff1 100644 --- a/packages/x-date-pickers/src/PickersTextField/PickersFilledInput/pickersFilledInputClasses.ts +++ b/packages/x-date-pickers/src/PickersTextField/PickersFilledInput/pickersFilledInputClasses.ts @@ -4,9 +4,8 @@ import { PickersInputBaseClasses, pickersInputBaseClasses } from '../PickersInpu export interface PickersFilledInputClasses extends PickersInputBaseClasses { /** Styles applied to the root element unless `disableUnderline={true}`. */ - underline?: string; + underline: string; } - export type PickersFilledInputClassKey = keyof PickersFilledInputClasses; export function getPickersFilledInputUtilityClass(slot: string) { diff --git a/packages/x-date-pickers/src/PickersTextField/PickersInput/PickersInput.tsx b/packages/x-date-pickers/src/PickersTextField/PickersInput/PickersInput.tsx index 877d9dde9221..15faf794f47f 100644 --- a/packages/x-date-pickers/src/PickersTextField/PickersInput/PickersInput.tsx +++ b/packages/x-date-pickers/src/PickersTextField/PickersInput/PickersInput.tsx @@ -1,22 +1,36 @@ 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/createStyled'; import { refType } from '@mui/utils'; import composeClasses from '@mui/utils/composeClasses'; -import { pickersInputClasses, getPickersInputUtilityClass } from './pickersInputClasses'; +import { + pickersInputClasses, + getPickersInputUtilityClass, + PickersInputClasses, +} from './pickersInputClasses'; import { PickersInputBase, PickersInputBaseProps } from '../PickersInputBase'; import { PickersInputBaseRoot } from '../PickersInputBase/PickersInputBase'; +import { PickerTextFieldOwnerState } from '../PickersTextField.types'; +import { usePickerTextFieldOwnerState } from '../usePickerTextFieldOwnerState'; export interface PickersInputProps extends PickersInputBaseProps { disableUnderline?: boolean; } +interface PickerInputOwnerState extends PickerTextFieldOwnerState { + /** + * `true` if the input has an underline, `false` otherwise. + */ + inputHasUnderline: boolean; +} + const PickersInputRoot = styled(PickersInputBaseRoot, { name: 'MuiPickersInput', slot: 'Root', overridesResolver: (props, styles) => styles.root, -})<{ ownerState: OwnerStateType }>(({ theme }) => { + shouldForwardProp: (prop) => shouldForwardProp(prop) && prop !== 'disableUnderline', +})<{ ownerState: PickerInputOwnerState }>(({ 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 +45,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 +110,14 @@ const PickersInputRoot = styled(PickersInputBaseRoot, { }; }); -const useUtilityClasses = (ownerState: OwnerStateType) => { - const { classes, disableUnderline } = ownerState; +const useUtilityClasses = ( + classes: Partial | undefined, + ownerState: PickerInputOwnerState, +) => { + const { inputHasUnderline } = ownerState; const slots = { - root: ['root', !disableUnderline && 'underline'], + root: ['root', !inputHasUnderline && 'underline'], input: ['input'], }; @@ -112,10 +129,6 @@ const useUtilityClasses = (ownerState: OwnerStateType) => { }; }; -interface OwnerStateType - extends FormControlState, - Omit {} - /** * @ignore - internal component. */ @@ -133,23 +146,21 @@ 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 pickerTextFieldOwnerState = usePickerTextFieldOwnerState(); + const ownerState: PickerInputOwnerState = { + ...pickerTextFieldOwnerState, + inputHasUnderline: !disableUnderline, }; - const classes = useUtilityClasses(ownerState); + const classes = useUtilityClasses(classesProp, ownerState); return ( ('MuiPickersInput', ['root', 'input']), + ...generateUtilityClasses('MuiPickersInput', ['root', 'underline', 'input']), }; diff --git a/packages/x-date-pickers/src/PickersTextField/PickersInputBase/PickersInputBase.tsx b/packages/x-date-pickers/src/PickersTextField/PickersInputBase/PickersInputBase.tsx index 16042be1e189..4ea33c8b59b7 100644 --- a/packages/x-date-pickers/src/PickersTextField/PickersInputBase/PickersInputBase.tsx +++ b/packages/x-date-pickers/src/PickersTextField/PickersInputBase/PickersInputBase.tsx @@ -1,6 +1,6 @@ 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 useForkRef from '@mui/utils/useForkRef'; import { refType } from '@mui/utils'; @@ -8,10 +8,10 @@ import composeClasses from '@mui/utils/composeClasses'; import capitalize from '@mui/utils/capitalize'; import useSlotProps from '@mui/utils/useSlotProps'; import visuallyHidden from '@mui/utils/visuallyHidden'; -import { useRtl } from '@mui/system/RtlProvider'; import { pickersInputBaseClasses, getPickersInputBaseUtilityClass, + PickersInputBaseClasses, } from './pickersInputBaseClasses'; import { PickersInputBaseProps } from './PickersInputBase.types'; import { @@ -21,6 +21,8 @@ import { Unstable_PickersSectionListSectionSeparator as PickersSectionListSectionSeparator, Unstable_PickersSectionListSectionContent as PickersSectionListSectionContent, } from '../../PickersSectionList'; +import { usePickerTextFieldOwnerState } from '../usePickerTextFieldOwnerState'; +import { PickerTextFieldOwnerState } from '../PickersTextField.types'; const round = (value: number) => 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: PickerTextFieldOwnerState }>(({ theme }) => ({ ...theme.typography.body1, color: (theme.vars || theme).palette.text.primary, cursor: 'text', @@ -41,7 +43,7 @@ export const PickersInputBaseRoot = styled('div', { letterSpacing: `${round(0.15 / 16)}em`, variants: [ { - props: { fullWidth: true }, + props: { isInputInFullWidth: true }, style: { width: '100%' }, }, ], @@ -51,7 +53,7 @@ export const PickersInputBaseSectionsContainer = styled(PickersSectionListRoot, name: 'MuiPickersInputBase', slot: 'SectionsContainer', overridesResolver: (props, styles) => styles.sectionsContainer, -})<{ ownerState: OwnerStateType }>(({ theme }) => ({ +})<{ ownerState: PickerTextFieldOwnerState }>(({ theme }) => ({ padding: '4px 0 5px', fontFamily: theme.typography.fontFamily, fontSize: 'inherit', @@ -66,28 +68,31 @@ 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: { hasStartAdornment: false, isFieldFocused: false, isFieldValueEmpty: true }, style: { color: 'currentColor', opacity: 0, }, }, { - // 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: { + hasStartAdornment: false, + isFieldFocused: false, + isFieldValueEmpty: true, + inputHasLabel: false, + }, style: theme.vars ? { opacity: theme.vars.opacity.inputPlaceholder, @@ -140,32 +145,34 @@ const PickersInputBaseInput = styled('input', { ...visuallyHidden, }); -const useUtilityClasses = (ownerState: OwnerStateType) => { +const useUtilityClasses = ( + classes: Partial | undefined, + ownerState: PickerTextFieldOwnerState, +) => { const { - focused, - disabled, - error, - classes, - fullWidth, - readOnly, - color, - size, - endAdornment, - startAdornment, + isFieldFocused, + isFieldDisabled, + isFieldReadOnly, + hasFieldError, + inputSize, + isInputInFullWidth, + inputColor, + hasStartAdornment, + hasEndAdornment, } = 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', + isInputInFullWidth && 'fullWidth', + `color${capitalize(inputColor!)}`, + inputSize === 'small' && 'inputSizeSmall', + hasStartAdornment && 'adornedStart', + hasEndAdornment && 'adornedEnd', ], notchedOutline: ['notchedOutline'], input: ['input'], @@ -178,12 +185,6 @@ const useUtilityClasses = (ownerState: OwnerStateType) => { return composeClasses(slots, getPickersInputBaseUtilityClass, classes); }; -interface OwnerStateType - extends FormControlState, - Omit { - isRtl: boolean; -} - /** * @ignore - internal component. */ @@ -223,13 +224,15 @@ const PickersInputBase = React.forwardRef(function PickersInputBase( sectionListRef, onFocus, onBlur, + classes: classesProp, + ownerState: ownerStateProp, ...other } = props; + const ownerStateContext = usePickerTextFieldOwnerState(); 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( @@ -237,6 +240,8 @@ const PickersInputBase = React.forwardRef(function PickersInputBase( ); } + const ownerState = ownerStateProp ?? ownerStateContext; + const handleInputFocus = (event: React.FocusEvent) => { muiFormControl.onFocus?.(event); onFocus?.(event); @@ -265,13 +270,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 +309,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 ba7c90dd39a7..56b540e1c8cf 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 9195cb4bc84a..58178c087262 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 { PickerTextFieldOwnerState } 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: PickerTextFieldOwnerState }>(({ 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: PickerTextFieldOwnerState; 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 9ab29a6dfb61..c9e28dc5257e 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 { PickerTextFieldOwnerState } 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: PickerTextFieldOwnerState }>(({ 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: PickerTextFieldOwnerState }>({ 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 6969a67b254b..daccf5ec262b 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 { PickerTextFieldOwnerState, 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: PickerTextFieldOwnerState }>({}); -const useUtilityClasses = (ownerState: PickersTextFieldProps) => { - const { focused, disabled, classes, required } = ownerState; +const useUtilityClasses = ( + classes: Partial | undefined, + ownerState: PickerTextFieldOwnerState, +) => { + 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,95 @@ 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 classes = useUtilityClasses(ownerState); + const fieldOwnerState = useFieldOwnerState({ + disabled: props.disabled, + required: props.required, + readOnly: InputProps?.readOnly, + }); + const ownerState = React.useMemo( + () => ({ + ...fieldOwnerState, + isFieldValueEmpty: areAllSectionsEmpty, + isFieldFocused: focused ?? false, + hasFieldError: error ?? false, + inputSize: props.size ?? 'medium', + inputColor: color ?? 'primary', + isInputInFullWidth: fullWidth ?? false, + hasStartAdornment: Boolean(startAdornment ?? InputProps?.startAdornment), + hasEndAdornment: Boolean(endAdornment ?? InputProps?.endAdornment), + inputHasLabel: !!label, + }), + [ + fieldOwnerState, + areAllSectionsEmpty, + focused, + error, + props.size, + color, + fullWidth, + startAdornment, + endAdornment, + InputProps?.startAdornment, + InputProps?.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 ceb5909d56d5..494a039adc77 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,45 @@ export type PickersTextFieldProps; + /** + * The color of the input. + */ + inputColor: Exclude; + /** + * `true` if the input takes up the full width of its container. + */ + isInputInFullWidth: boolean; + /** + * `true` if the input has a start adornment, `false` otherwise. + */ + hasStartAdornment: boolean; + /** + * `true` if the input has an end adornment, `false` otherwise. + */ + hasEndAdornment: 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 000000000000..056f48d579c2 --- /dev/null +++ b/packages/x-date-pickers/src/PickersTextField/usePickerTextFieldOwnerState.ts @@ -0,0 +1,19 @@ +'use client'; +import * as React from 'react'; +import { PickerTextFieldOwnerState } 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 65bea5a6bb71..48455438b411 100644 --- a/packages/x-date-pickers/src/internals/components/PickersArrowSwitcher/PickersArrowSwitcher.tsx +++ b/packages/x-date-pickers/src/internals/components/PickersArrowSwitcher/PickersArrowSwitcher.tsx @@ -44,7 +44,7 @@ const PickersArrowSwitcherButton = styled(IconButton, { }>({ variants: [ { - props: { hidden: true }, + props: { isButtonHidden: true }, style: { visibility: 'hidden' }, }, ], @@ -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 725f070b1750..0df4d813ea96 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 bc1c326465dd..9ec96ac340d7 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 05c0c4e0460f..eac8aedd90db 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 field is in left-to-right direction. + * Is equal to "rtl" when the field is in right-to-left direction. + */ + fieldDirection: 'ltr' | 'rtl'; } /**