From 66fa221d8fec5b43b2f92e6376e9e1ec91d5b309 Mon Sep 17 00:00:00 2001 From: Flavien DELANGLE Date: Wed, 6 Nov 2024 13:18:32 +0100 Subject: [PATCH] [pickers] Use the new `ownerState` in `DateCalendar`, `DateRangeCalendar`, `MonthCalendar` and `YearCalendar` (#15171) --- .../DateRangeCalendar/DateRangeCalendar.tsx | 25 +++++++---- .../DateRangeCalendar.types.ts | 16 +++---- .../src/DateCalendar/DateCalendar.tsx | 19 +++++---- .../src/DateCalendar/DateCalendar.types.ts | 4 +- .../src/DateCalendar/DayCalendar.tsx | 35 +++++++++------- .../x-date-pickers/src/DateCalendar/index.ts | 1 + .../src/MonthCalendar/MonthCalendar.tsx | 16 +++---- .../src/MonthCalendar/MonthCalendar.types.ts | 10 +++-- .../src/MonthCalendar/PickersMonth.tsx | 42 +++++++++++++------ .../src/YearCalendar/PickersYear.tsx | 42 +++++++++++++------ .../src/YearCalendar/YearCalendar.tsx | 16 +++---- .../src/YearCalendar/YearCalendar.types.ts | 10 +++-- .../internals/components/PickersProvider.tsx | 9 +++- .../hooks/usePickersPrivateContext.ts | 13 +----- .../x-date-pickers/src/internals/index.ts | 1 + packages/x-date-pickers/src/models/pickers.ts | 6 ++- scripts/x-date-pickers-pro.exports.json | 1 + scripts/x-date-pickers.exports.json | 1 + 18 files changed, 166 insertions(+), 101 deletions(-) diff --git a/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.tsx b/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.tsx index 0e7a18a2e7af..66abda774987 100644 --- a/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.tsx +++ b/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.tsx @@ -26,11 +26,13 @@ import { useControlledValueWithTimezone, useViews, PickerRangeValue, + usePickersPrivateContext, } from '@mui/x-date-pickers/internals'; import { warnOnce } from '@mui/x-internals/warning'; import { PickerValidDate } from '@mui/x-date-pickers/models'; import { getReleaseInfo } from '../internals/utils/releaseInfo'; import { + DateRangeCalendarClasses, dateRangeCalendarClasses, getDateRangeCalendarUtilityClass, } from './dateRangeCalendarClasses'; @@ -134,12 +136,14 @@ function useDateRangeCalendarDefaultizedProps( }; } -const useUtilityClasses = (ownerState: DateRangeCalendarOwnerState) => { - const { classes, isDragging } = ownerState; +const useUtilityClasses = ( + classes: Partial | undefined, + ownerState: DateRangeCalendarOwnerState, +) => { const slots = { root: ['root'], monthContainer: ['monthContainer'], - dayCalendar: [isDragging && 'dayDragging'], + dayCalendar: [ownerState.isDraggingDay && 'dayDragging'], }; return composeClasses(slots, getDateRangeCalendarUtilityClass, classes); @@ -174,6 +178,7 @@ const DateRangeCalendar = React.forwardRef(function DateRangeCalendar( referenceDate, onChange, className, + classes: classesProp, disableFuture, disablePast, minDate, @@ -300,8 +305,12 @@ const DateRangeCalendar = React.forwardRef(function DateRangeCalendar( timezone, }); - const ownerState = { ...props, isDragging }; - const classes = useUtilityClasses(ownerState); + const { ownerState: pickersOwnerState } = usePickersPrivateContext(); + const ownerState: DateRangeCalendarOwnerState = { + ...pickersOwnerState, + isDraggingDay: isDragging, + }; + const classes = useUtilityClasses(classesProp, ownerState); const draggingRange = React.useMemo(() => { if (!valueDayRange[0] || !valueDayRange[1] || !rangeDragDay) { @@ -369,7 +378,7 @@ const DateRangeCalendar = React.forwardRef(function DateRangeCalendar( slots, slotProps, }, - ownerState: props, + ownerState, }); const prevValue = React.useRef(null); @@ -454,7 +463,7 @@ const DateRangeCalendar = React.forwardRef(function DateRangeCalendar( const slotPropsForDayCalendar = { ...slotProps, day: (dayOwnerState) => { - const { day } = dayOwnerState; + const { day, isDaySelected } = dayOwnerState; const isSelectedStartDate = isStartOfRange(utils, day, valueDayRange); const isSelectedEndDate = isEndOfRange(utils, day, valueDayRange); const shouldInitDragging = !shouldDisableDragEditing && valueDayRange[0] && valueDayRange[1]; @@ -487,7 +496,7 @@ const DateRangeCalendar = React.forwardRef(function DateRangeCalendar( onMouseEnter: shouldHavePreview ? handleDayMouseEnter : undefined, // apply selected styling to the dragging start or end day isVisuallySelected: - dayOwnerState.selected || (isDragging && (isStartOfHighlighting || isEndOfHighlighting)), + isDaySelected || (isDragging && (isStartOfHighlighting || isEndOfHighlighting)), 'data-position': datePosition, ...dragEventHandlers, draggable: isElementDraggable ? true : undefined, diff --git a/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.types.ts b/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.types.ts index 4bb144a7c0cb..58643ecdf32d 100644 --- a/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.types.ts +++ b/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.types.ts @@ -3,12 +3,13 @@ import { SxProps } from '@mui/system'; import { SlotComponentProps } from '@mui/utils'; import { Theme } from '@mui/material/styles'; import { DefaultizedProps } from '@mui/x-internals/types'; -import { PickerValidDate, TimezoneProps } from '@mui/x-date-pickers/models'; +import { PickerOwnerState, PickerValidDate, TimezoneProps } from '@mui/x-date-pickers/models'; import { PickersCalendarHeader, PickersCalendarHeaderSlots, PickersCalendarHeaderSlotProps, } from '@mui/x-date-pickers/PickersCalendarHeader'; +import { PickerDayOwnerState } from '@mui/x-date-pickers/DateCalendar'; import { BaseDateValidationProps, ExportedDayCalendarProps, @@ -16,7 +17,6 @@ import { DayCalendarSlotProps, PickersArrowSwitcherSlots, PickersArrowSwitcherSlotProps, - DayCalendarProps, ExportedUseViewsOptions, PickerRangeValue, } from '@mui/x-date-pickers/internals'; @@ -49,12 +49,12 @@ export interface DateRangeCalendarSlotProps extends PickersArrowSwitcherSlotProps, Omit, PickersCalendarHeaderSlotProps { - calendarHeader?: SlotComponentProps; - day?: SlotComponentProps< - typeof DateRangePickerDay, + calendarHeader?: SlotComponentProps< + typeof PickersCalendarHeader, {}, - DayCalendarProps & { day: PickerValidDate; selected: boolean } + DateRangeCalendarOwnerState >; + day?: SlotComponentProps; } export interface ExportedDateRangeCalendarProps @@ -151,8 +151,8 @@ export interface DateRangeCalendarProps availableRangePositions?: RangePosition[]; } -export interface DateRangeCalendarOwnerState extends DateRangeCalendarProps { - isDragging: boolean; +export interface DateRangeCalendarOwnerState extends PickerOwnerState { + isDraggingDay: boolean; } export type DateRangeCalendarDefaultizedProps = DefaultizedProps< diff --git a/packages/x-date-pickers/src/DateCalendar/DateCalendar.tsx b/packages/x-date-pickers/src/DateCalendar/DateCalendar.tsx index f5bd789a6862..4826dc78b659 100644 --- a/packages/x-date-pickers/src/DateCalendar/DateCalendar.tsx +++ b/packages/x-date-pickers/src/DateCalendar/DateCalendar.tsx @@ -25,15 +25,15 @@ import { } from '../internals/utils/date-utils'; import { PickerViewRoot } from '../internals/components/PickerViewRoot'; import { useDefaultReduceAnimations } from '../internals/hooks/useDefaultReduceAnimations'; -import { getDateCalendarUtilityClass } from './dateCalendarClasses'; +import { DateCalendarClasses, getDateCalendarUtilityClass } from './dateCalendarClasses'; import { BaseDateValidationProps } from '../internals/models/validation'; import { useControlledValueWithTimezone } from '../internals/hooks/useValueWithTimezone'; import { singleItemValueManager } from '../internals/utils/valueManagers'; import { VIEW_HEIGHT } from '../internals/constants/dimensions'; -import { PickerValidDate } from '../models'; +import { PickerOwnerState, PickerValidDate } from '../models'; +import { usePickersPrivateContext } from '../internals/hooks/usePickersPrivateContext'; -const useUtilityClasses = (ownerState: DateCalendarProps) => { - const { classes } = ownerState; +const useUtilityClasses = (classes: Partial | undefined) => { const slots = { root: ['root'], viewTransitionContainer: ['viewTransitionContainer'], @@ -73,7 +73,7 @@ const DateCalendarRoot = styled(PickerViewRoot, { name: 'MuiDateCalendar', slot: 'Root', overridesResolver: (props, styles) => styles.root, -})<{ ownerState: DateCalendarProps }>({ +})<{ ownerState: PickerOwnerState }>({ display: 'flex', flexDirection: 'column', height: VIEW_HEIGHT, @@ -83,7 +83,7 @@ const DateCalendarViewTransitionContainer = styled(PickersFadeTransitionGroup, { name: 'MuiDateCalendar', slot: 'ViewTransitionContainer', overridesResolver: (props, styles) => styles.viewTransitionContainer, -})<{ ownerState: DateCalendarProps }>({}); +})<{ ownerState: PickerOwnerState }>({}); type DateCalendarComponent = (( props: DateCalendarProps & React.RefAttributes, @@ -105,6 +105,7 @@ export const DateCalendar = React.forwardRef(function DateCalendar( ref: React.Ref, ) { const utils = useUtils(); + const { ownerState } = usePickersPrivateContext(); const id = useId(); const props = useDateCalendarDefaultizedProps(inProps, 'MuiDateCalendar'); @@ -127,6 +128,7 @@ export const DateCalendar = React.forwardRef(function DateCalendar( views, openTo, className, + classes: classesProp, disabled, readOnly, minDate, @@ -217,7 +219,7 @@ export const DateCalendar = React.forwardRef(function DateCalendar( timezone, labelId: gridLabelId, }, - ownerState: props, + ownerState, }); const handleDateMonthChange = useEventCallback((newDate: PickerValidDate) => { @@ -295,8 +297,7 @@ export const DateCalendar = React.forwardRef(function DateCalendar( } }, [value]); // eslint-disable-line - const ownerState = props; - const classes = useUtilityClasses(ownerState); + const classes = useUtilityClasses(classesProp); const baseDateValidationProps: Required = { disablePast, diff --git a/packages/x-date-pickers/src/DateCalendar/DateCalendar.types.ts b/packages/x-date-pickers/src/DateCalendar/DateCalendar.types.ts index 09e7c7d3067b..95e4e4edea2d 100644 --- a/packages/x-date-pickers/src/DateCalendar/DateCalendar.types.ts +++ b/packages/x-date-pickers/src/DateCalendar/DateCalendar.types.ts @@ -13,7 +13,7 @@ import { DayCalendarSlots, DayCalendarSlotProps, ExportedDayCalendarProps } from import { DateCalendarClasses } from './dateCalendarClasses'; import { BaseDateValidationProps } from '../internals/models/validation'; import { ExportedUseViewsOptions } from '../internals/hooks/useViews'; -import { DateView, PickerValidDate, TimezoneProps } from '../models'; +import { DateView, PickerOwnerState, PickerValidDate, TimezoneProps } from '../models'; import { ExportedYearCalendarProps, YearCalendarSlots, @@ -44,7 +44,7 @@ export interface DateCalendarSlotProps DayCalendarSlotProps, MonthCalendarSlotProps, YearCalendarSlotProps { - calendarHeader?: SlotComponentProps; + calendarHeader?: SlotComponentProps; } export interface ExportedDateCalendarProps diff --git a/packages/x-date-pickers/src/DateCalendar/DayCalendar.tsx b/packages/x-date-pickers/src/DateCalendar/DayCalendar.tsx index b11eb878113d..2c56d7b9d8cf 100644 --- a/packages/x-date-pickers/src/DateCalendar/DayCalendar.tsx +++ b/packages/x-date-pickers/src/DateCalendar/DayCalendar.tsx @@ -29,7 +29,9 @@ import { import { useIsDateDisabled } from './useIsDateDisabled'; import { findClosestEnabledDate, getWeekdays } from '../internals/utils/date-utils'; import { DayCalendarClasses, getDayCalendarUtilityClass } from './dayCalendarClasses'; -import { PickerValidDate, TimezoneProps } from '../models'; +import { PickerOwnerState, PickerValidDate, TimezoneProps } from '../models'; +import { usePickersPrivateContext } from '../internals/hooks/usePickersPrivateContext'; +import { DateCalendarClasses } from './dateCalendarClasses'; export interface DayCalendarSlots { /** @@ -41,11 +43,13 @@ export interface DayCalendarSlots { } export interface DayCalendarSlotProps { - day?: SlotComponentPropsFromProps< - PickersDayProps, - {}, - DayCalendarProps & { day: PickerValidDate; selected: boolean } - >; + day?: SlotComponentPropsFromProps; +} + +export interface PickerDayOwnerState extends PickerOwnerState { + isDaySelected: boolean; + isDayDisabled: boolean; + day: PickerValidDate; } export interface ExportedDayCalendarProps extends ExportedPickersDayProps { @@ -119,8 +123,7 @@ export interface DayCalendarProps slotProps?: DayCalendarSlotProps; } -const useUtilityClasses = (ownerState: DayCalendarProps) => { - const { classes } = ownerState; +const useUtilityClasses = (classes: Partial | undefined) => { const slots = { root: ['root'], header: ['header'], @@ -266,11 +269,17 @@ function WrappedDay({ const utils = useUtils(); const now = useNow(timezone); + const { ownerState } = usePickersPrivateContext(); const isFocusableDay = focusableDay !== null && utils.isSameDay(day, focusableDay); const isSelected = selectedDays.some((selectedDay) => utils.isSameDay(selectedDay, day)); const isToday = utils.isSameDay(day, now); + const isDisabled = React.useMemo( + () => disabled || isDateDisabled(day), + [disabled, isDateDisabled, day], + ); + const Day = slots?.day ?? PickersDay; // We don't want to pass to ownerState down, to avoid re-rendering all the day whenever a prop changes. const { ownerState: dayOwnerState, ...dayProps } = useSlotProps({ @@ -285,14 +294,9 @@ function WrappedDay({ 'data-timestamp': utils.toJsDate(day).valueOf(), ...other, }, - ownerState: { ...parentProps, day, selected: isSelected }, + ownerState: { ...ownerState, day, isDayDisabled: isDisabled, isDaySelected: isSelected }, }); - const isDisabled = React.useMemo( - () => disabled || isDateDisabled(day), - [disabled, isDateDisabled, day], - ); - const outsideCurrentMonth = React.useMemo( () => utils.getMonth(day) !== currentMonthNumber, [utils, day, currentMonthNumber], @@ -342,6 +346,7 @@ export function DayCalendar(inProps: DayCalendarProps) { const { onFocusedDayChange, className, + classes: classesProp, currentMonth, selectedDays, focusedDay, @@ -371,7 +376,7 @@ export function DayCalendar(inProps: DayCalendarProps) { } = props; const now = useNow(timezone); - const classes = useUtilityClasses(props); + const classes = useUtilityClasses(classesProp); const isRtl = useRtl(); const isDateDisabled = useIsDateDisabled({ diff --git a/packages/x-date-pickers/src/DateCalendar/index.ts b/packages/x-date-pickers/src/DateCalendar/index.ts index 8e33e9e81f2a..8aaa199a8fef 100644 --- a/packages/x-date-pickers/src/DateCalendar/index.ts +++ b/packages/x-date-pickers/src/DateCalendar/index.ts @@ -4,6 +4,7 @@ export type { DateCalendarSlots, DateCalendarSlotProps, } from './DateCalendar.types'; +export type { PickerDayOwnerState } from './DayCalendar'; export { getDateCalendarUtilityClass, dateCalendarClasses } from './dateCalendarClasses'; export type { DateCalendarClassKey, DateCalendarClasses } from './dateCalendarClasses'; diff --git a/packages/x-date-pickers/src/MonthCalendar/MonthCalendar.tsx b/packages/x-date-pickers/src/MonthCalendar/MonthCalendar.tsx index 546fc4061421..59c14776740e 100644 --- a/packages/x-date-pickers/src/MonthCalendar/MonthCalendar.tsx +++ b/packages/x-date-pickers/src/MonthCalendar/MonthCalendar.tsx @@ -12,18 +12,17 @@ import { import { DefaultizedProps } from '@mui/x-internals/types'; import { PickersMonth } from './PickersMonth'; import { useUtils, useNow, useDefaultDates } from '../internals/hooks/useUtils'; -import { getMonthCalendarUtilityClass } from './monthCalendarClasses'; +import { getMonthCalendarUtilityClass, MonthCalendarClasses } from './monthCalendarClasses'; import { applyDefaultDate, getMonthsInYear } from '../internals/utils/date-utils'; import { MonthCalendarProps } from './MonthCalendar.types'; import { singleItemValueManager } from '../internals/utils/valueManagers'; import { SECTION_TYPE_GRANULARITY } from '../internals/utils/getDefaultReferenceDate'; import { useControlledValueWithTimezone } from '../internals/hooks/useValueWithTimezone'; import { DIALOG_WIDTH } from '../internals/constants/dimensions'; -import { PickerValidDate } from '../models'; - -const useUtilityClasses = (ownerState: MonthCalendarProps) => { - const { classes } = ownerState; +import { PickerOwnerState, PickerValidDate } from '../models'; +import { usePickersPrivateContext } from '../internals/hooks/usePickersPrivateContext'; +const useUtilityClasses = (classes: Partial | undefined) => { const slots = { root: ['root'], }; @@ -55,7 +54,7 @@ const MonthCalendarRoot = styled('div', { name: 'MuiMonthCalendar', slot: 'Root', overridesResolver: (props, styles) => styles.root, -})<{ ownerState: MonthCalendarProps }>({ +})<{ ownerState: PickerOwnerState }>({ display: 'flex', flexWrap: 'wrap', alignContent: 'stretch', @@ -85,6 +84,7 @@ export const MonthCalendar = React.forwardRef(function MonthCalendar( const props = useMonthCalendarDefaultizedProps(inProps, 'MuiMonthCalendar'); const { className, + classes: classesProp, value: valueProp, defaultValue, referenceDate: referenceDateProp, @@ -121,6 +121,7 @@ export const MonthCalendar = React.forwardRef(function MonthCalendar( const now = useNow(timezone); const isRtl = useRtl(); const utils = useUtils(); + const { ownerState } = usePickersPrivateContext(); const referenceDate = React.useMemo( () => @@ -135,8 +136,7 @@ export const MonthCalendar = React.forwardRef(function MonthCalendar( [], // eslint-disable-line react-hooks/exhaustive-deps ); - const ownerState = props; - const classes = useUtilityClasses(ownerState); + const classes = useUtilityClasses(classesProp); const todayMonth = React.useMemo(() => utils.getMonth(now), [utils, now]); diff --git a/packages/x-date-pickers/src/MonthCalendar/MonthCalendar.types.ts b/packages/x-date-pickers/src/MonthCalendar/MonthCalendar.types.ts index 8aef916e9f0d..427026de066f 100644 --- a/packages/x-date-pickers/src/MonthCalendar/MonthCalendar.types.ts +++ b/packages/x-date-pickers/src/MonthCalendar/MonthCalendar.types.ts @@ -4,8 +4,12 @@ import { Theme } from '@mui/material/styles'; import { SlotComponentPropsFromProps } from '@mui/x-internals/types'; import { MonthCalendarClasses } from './monthCalendarClasses'; import { BaseDateValidationProps, MonthValidationProps } from '../internals/models/validation'; -import { PickerValidDate, TimezoneProps } from '../models'; -import type { PickersMonthProps } from './PickersMonth'; +import { PickerOwnerState, PickerValidDate, TimezoneProps } from '../models'; + +export interface PickerMonthOwnerState extends PickerOwnerState { + isMonthSelected: boolean; + isMonthDisabled: boolean; +} export interface MonthCalendarSlots { /** @@ -19,7 +23,7 @@ export interface MonthCalendarSlotProps { monthButton?: SlotComponentPropsFromProps< React.HTMLAttributes & { sx: SxProps }, {}, - PickersMonthProps + PickerMonthOwnerState >; } diff --git a/packages/x-date-pickers/src/MonthCalendar/PickersMonth.tsx b/packages/x-date-pickers/src/MonthCalendar/PickersMonth.tsx index 996df34b165b..1472fa21daa3 100644 --- a/packages/x-date-pickers/src/MonthCalendar/PickersMonth.tsx +++ b/packages/x-date-pickers/src/MonthCalendar/PickersMonth.tsx @@ -9,7 +9,12 @@ import { pickersMonthClasses, PickersMonthClasses, } from './pickersMonthClasses'; -import { MonthCalendarSlotProps, MonthCalendarSlots } from './MonthCalendar.types'; +import { + MonthCalendarSlotProps, + MonthCalendarSlots, + PickerMonthOwnerState, +} from './MonthCalendar.types'; +import { usePickersPrivateContext } from '../internals/hooks/usePickersPrivateContext'; export interface ExportedPickersMonthProps { classes?: Partial; @@ -34,12 +39,17 @@ export interface PickersMonthProps extends ExportedPickersMonthProps { slotProps?: MonthCalendarSlotProps; } -const useUtilityClasses = (ownerState: PickersMonthProps) => { - const { disabled, selected, classes } = ownerState; - +const useUtilityClasses = ( + classes: Partial | undefined, + ownerState: PickerMonthOwnerState, +) => { const slots = { root: ['root'], - monthButton: ['monthButton', disabled && 'disabled', selected && 'selected'], + monthButton: [ + 'monthButton', + ownerState.isMonthDisabled && 'disabled', + ownerState.isMonthSelected && 'selected', + ], }; return composeClasses(slots, getPickersMonthUtilityClass, classes); @@ -50,7 +60,7 @@ const PickersMonthRoot = styled('div', { slot: 'Root', overridesResolver: (_, styles) => [styles.root], })<{ - ownerState: PickersMonthProps; + ownerState: PickerMonthOwnerState; }>({ display: 'flex', alignItems: 'center', @@ -68,7 +78,7 @@ const MonthCalendarButton = styled('button', { { [`&.${pickersMonthClasses.selected}`]: styles.selected }, ], })<{ - ownerState?: PickersMonthProps; + ownerState?: PickerMonthOwnerState; }>(({ theme }) => ({ color: 'unset', backgroundColor: 'transparent', @@ -117,9 +127,10 @@ export const PickersMonth = React.memo(function PickersMonth(inProps: PickersMon const { autoFocus, className, + classes: classesProp, children, - disabled, - selected, + disabled = false, + selected = false, value, tabIndex, onClick, @@ -136,7 +147,14 @@ export const PickersMonth = React.memo(function PickersMonth(inProps: PickersMon } = props; const ref = React.useRef(null); - const classes = useUtilityClasses(props); + const { ownerState: pickerOwnerState } = usePickersPrivateContext(); + const ownerState: PickerMonthOwnerState = { + ...pickerOwnerState, + isMonthDisabled: disabled, + isMonthSelected: selected, + }; + + const classes = useUtilityClasses(classesProp, ownerState); // We can't forward the `autoFocus` to the button because it is a native button, not a MUI Button useEnhancedEffect(() => { @@ -165,7 +183,7 @@ export const PickersMonth = React.memo(function PickersMonth(inProps: PickersMon onFocus: (event: React.FocusEvent) => onFocus(event, value), onBlur: (event: React.FocusEvent) => onBlur(event, value), }, - ownerState: props, + ownerState, className: classes.monthButton, }); @@ -173,7 +191,7 @@ export const PickersMonth = React.memo(function PickersMonth(inProps: PickersMon diff --git a/packages/x-date-pickers/src/YearCalendar/PickersYear.tsx b/packages/x-date-pickers/src/YearCalendar/PickersYear.tsx index 6425f9936431..e214e4bf2b3d 100644 --- a/packages/x-date-pickers/src/YearCalendar/PickersYear.tsx +++ b/packages/x-date-pickers/src/YearCalendar/PickersYear.tsx @@ -9,7 +9,13 @@ import { pickersYearClasses, PickersYearClasses, } from './pickersYearClasses'; -import { YearCalendarSlotProps, YearCalendarSlots } from './YearCalendar.types'; +import { + PickerYearOwnerState, + YearCalendarSlotProps, + YearCalendarSlots, +} from './YearCalendar.types'; +import { usePickersPrivateContext } from '../internals/hooks/usePickersPrivateContext'; +import { PickerOwnerState } from '../models/pickers'; export interface ExportedPickersYearProps { classes?: Partial; @@ -33,12 +39,17 @@ export interface PickersYearProps extends ExportedPickersYearProps { slotProps?: YearCalendarSlotProps; } -const useUtilityClasses = (ownerState: PickersYearProps) => { - const { disabled, selected, classes } = ownerState; - +const useUtilityClasses = ( + classes: Partial | undefined, + ownerState: PickerYearOwnerState, +) => { const slots = { root: ['root'], - yearButton: ['yearButton', disabled && 'disabled', selected && 'selected'], + yearButton: [ + 'yearButton', + ownerState.isYearDisabled && 'disabled', + ownerState.isYearSelected && 'selected', + ], }; return composeClasses(slots, getPickersYearUtilityClass, classes); @@ -48,7 +59,7 @@ const PickersYearRoot = styled('div', { name: 'MuiPickersYear', slot: 'Root', overridesResolver: (_, styles) => [styles.root], -})<{ ownerState: PickersYearProps }>({ +})<{ ownerState: PickerOwnerState }>({ display: 'flex', alignItems: 'center', justifyContent: 'center', @@ -64,7 +75,7 @@ const YearCalendarButton = styled('button', { { [`&.${pickersYearClasses.disabled}`]: styles.disabled }, { [`&.${pickersYearClasses.selected}`]: styles.selected }, ], -})<{ ownerState: PickersYearProps }>(({ theme }) => ({ +})<{ ownerState: PickerOwnerState }>(({ theme }) => ({ color: 'unset', backgroundColor: 'transparent', border: 0, @@ -112,9 +123,10 @@ export const PickersYear = React.memo(function PickersYear(inProps: PickersYearP const { autoFocus, className, + classes: classesProp, children, - disabled, - selected, + disabled = false, + selected = false, value, tabIndex, onClick, @@ -130,7 +142,13 @@ export const PickersYear = React.memo(function PickersYear(inProps: PickersYearP } = props; const ref = React.useRef(null); - const classes = useUtilityClasses(props); + const { ownerState: pickerOwnerState } = usePickersPrivateContext(); + const ownerState: PickerYearOwnerState = { + ...pickerOwnerState, + isYearDisabled: disabled, + isYearSelected: selected, + }; + const classes = useUtilityClasses(classesProp, ownerState); // We can't forward the `autoFocus` to the button because it is a native button, not a MUI Button useEnhancedEffect(() => { @@ -158,7 +176,7 @@ export const PickersYear = React.memo(function PickersYear(inProps: PickersYearP onFocus: (event: React.FocusEvent) => onFocus(event, value), onBlur: (event: React.FocusEvent) => onBlur(event, value), }, - ownerState: props, + ownerState, className: classes.yearButton, }); @@ -166,7 +184,7 @@ export const PickersYear = React.memo(function PickersYear(inProps: PickersYearP diff --git a/packages/x-date-pickers/src/YearCalendar/YearCalendar.tsx b/packages/x-date-pickers/src/YearCalendar/YearCalendar.tsx index 72cc8c460cde..fb1c4fdd4844 100644 --- a/packages/x-date-pickers/src/YearCalendar/YearCalendar.tsx +++ b/packages/x-date-pickers/src/YearCalendar/YearCalendar.tsx @@ -13,18 +13,17 @@ import { import { DefaultizedProps } from '@mui/x-internals/types'; import { PickersYear } from './PickersYear'; import { useUtils, useNow, useDefaultDates } from '../internals/hooks/useUtils'; -import { getYearCalendarUtilityClass } from './yearCalendarClasses'; +import { getYearCalendarUtilityClass, YearCalendarClasses } from './yearCalendarClasses'; import { applyDefaultDate } from '../internals/utils/date-utils'; import { YearCalendarProps } from './YearCalendar.types'; import { singleItemValueManager } from '../internals/utils/valueManagers'; import { SECTION_TYPE_GRANULARITY } from '../internals/utils/getDefaultReferenceDate'; import { useControlledValueWithTimezone } from '../internals/hooks/useValueWithTimezone'; import { DIALOG_WIDTH, MAX_CALENDAR_HEIGHT } from '../internals/constants/dimensions'; -import { PickerValidDate } from '../models'; - -const useUtilityClasses = (ownerState: YearCalendarProps) => { - const { classes } = ownerState; +import { PickerOwnerState, PickerValidDate } from '../models'; +import { usePickersPrivateContext } from '../internals/hooks/usePickersPrivateContext'; +const useUtilityClasses = (classes: Partial | undefined) => { const slots = { root: ['root'], }; @@ -60,7 +59,7 @@ const YearCalendarRoot = styled('div', { name: 'MuiYearCalendar', slot: 'Root', overridesResolver: (props, styles) => styles.root, -})<{ ownerState: YearCalendarProps }>({ +})<{ ownerState: PickerOwnerState }>({ display: 'flex', flexDirection: 'row', flexWrap: 'wrap', @@ -95,6 +94,7 @@ export const YearCalendar = React.forwardRef(function YearCalendar( const { autoFocus, className, + classes: classesProp, value: valueProp, defaultValue, referenceDate: referenceDateProp, @@ -131,6 +131,7 @@ export const YearCalendar = React.forwardRef(function YearCalendar( const now = useNow(timezone); const isRtl = useRtl(); const utils = useUtils(); + const { ownerState } = usePickersPrivateContext(); const referenceDate = React.useMemo( () => @@ -145,8 +146,7 @@ export const YearCalendar = React.forwardRef(function YearCalendar( [], // eslint-disable-line react-hooks/exhaustive-deps ); - const ownerState = props; - const classes = useUtilityClasses(ownerState); + const classes = useUtilityClasses(classesProp); const todayYear = React.useMemo(() => utils.getYear(now), [utils, now]); const selectedYear = React.useMemo(() => { diff --git a/packages/x-date-pickers/src/YearCalendar/YearCalendar.types.ts b/packages/x-date-pickers/src/YearCalendar/YearCalendar.types.ts index 4e7ec216856c..821314c326a8 100644 --- a/packages/x-date-pickers/src/YearCalendar/YearCalendar.types.ts +++ b/packages/x-date-pickers/src/YearCalendar/YearCalendar.types.ts @@ -4,8 +4,12 @@ import { Theme } from '@mui/material/styles'; import { SlotComponentPropsFromProps } from '@mui/x-internals/types'; import { YearCalendarClasses } from './yearCalendarClasses'; import { BaseDateValidationProps, YearValidationProps } from '../internals/models/validation'; -import { PickerValidDate, TimezoneProps } from '../models'; -import type { PickersYearProps } from './PickersYear'; +import { PickerOwnerState, PickerValidDate, TimezoneProps } from '../models'; + +export interface PickerYearOwnerState extends PickerOwnerState { + isYearSelected: boolean; + isYearDisabled: boolean; +} export interface YearCalendarSlots { /** @@ -19,7 +23,7 @@ export interface YearCalendarSlotProps { yearButton?: SlotComponentPropsFromProps< React.HTMLAttributes & { sx: SxProps }, {}, - PickersYearProps + PickerYearOwnerState >; } diff --git a/packages/x-date-pickers/src/internals/components/PickersProvider.tsx b/packages/x-date-pickers/src/internals/components/PickersProvider.tsx index 0f1d32981472..36c5794492d4 100644 --- a/packages/x-date-pickers/src/internals/components/PickersProvider.tsx +++ b/packages/x-date-pickers/src/internals/components/PickersProvider.tsx @@ -5,7 +5,14 @@ import { LocalizationProvider } from '../../LocalizationProvider'; export const PickersContext = React.createContext(null); -export const PickersPrivateContext = React.createContext(null); +export const PickersPrivateContext = React.createContext({ + ownerState: { + isPickerDisabled: false, + isPickerReadOnly: false, + isPickerValueEmpty: false, + isPickerOpen: false, + }, +}); /** * Provides the context for the various parts of a picker component: diff --git a/packages/x-date-pickers/src/internals/hooks/usePickersPrivateContext.ts b/packages/x-date-pickers/src/internals/hooks/usePickersPrivateContext.ts index 9d03e894944a..d862d11186d0 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePickersPrivateContext.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePickersPrivateContext.ts @@ -1,17 +1,8 @@ 'use client'; import * as React from 'react'; -import { PickersContext, PickersPrivateContextValue } from '../components/PickersProvider'; +import { PickersPrivateContext } from '../components/PickersProvider'; /** * Returns the private context passed by the picker that wraps the current component. */ -export const usePickersPrivateContext = () => { - const value = React.useContext(PickersContext) as PickersPrivateContextValue | null; - if (value == null) { - throw new Error( - 'MUI X: The `usePickersPrivateContext` can only be called in components that are used inside a picker component.', - ); - } - - return value; -}; +export const usePickersPrivateContext = () => React.useContext(PickersPrivateContext); diff --git a/packages/x-date-pickers/src/internals/index.ts b/packages/x-date-pickers/src/internals/index.ts index 11a6e4657b7f..80fcd9908060 100644 --- a/packages/x-date-pickers/src/internals/index.ts +++ b/packages/x-date-pickers/src/internals/index.ts @@ -84,6 +84,7 @@ export type { PickerViewRenderer, UsePickerViewsProps, } from './hooks/usePicker/usePickerViews'; +export { usePickersPrivateContext } from './hooks/usePickersPrivateContext'; export { useStaticPicker } from './hooks/useStaticPicker'; export type { StaticOnlyPickerProps, diff --git a/packages/x-date-pickers/src/models/pickers.ts b/packages/x-date-pickers/src/models/pickers.ts index d255b1519e29..69a2750e20f1 100644 --- a/packages/x-date-pickers/src/models/pickers.ts +++ b/packages/x-date-pickers/src/models/pickers.ts @@ -17,19 +17,23 @@ export type PickerValidDate = keyof PickerValidDateLookup extends never export interface PickerOwnerState { /** - * `true` if the value is currently empty. + * `true` if the value of the picker is currently empty. + * Is always `false` if the component you are accessing the ownerState from is not wrapped by a picker. */ isPickerValueEmpty: boolean; /** * `true` if the picker is open, `false` otherwise. + * Is always `false` if the component you are accessing the ownerState from is not wrapped by a picker. */ isPickerOpen: boolean; /** * `true` if the picker is disabled, `false` otherwise. + * Is always `false` if the component you are accessing the ownerState from is not wrapped by a picker. */ isPickerDisabled: boolean; /** * `true` if the picker is read-only, `false` otherwise. + * Is always `false` if the component you are accessing the ownerState from is not wrapped by a picker. */ isPickerReadOnly: boolean; } diff --git a/scripts/x-date-pickers-pro.exports.json b/scripts/x-date-pickers-pro.exports.json index 836e164724c1..b25c77e06134 100644 --- a/scripts/x-date-pickers-pro.exports.json +++ b/scripts/x-date-pickers-pro.exports.json @@ -249,6 +249,7 @@ { "name": "NonEmptyDateRange", "kind": "TypeAlias" }, { "name": "OnErrorProps", "kind": "Interface" }, { "name": "PickerChangeHandlerContext", "kind": "Interface" }, + { "name": "PickerDayOwnerState", "kind": "Interface" }, { "name": "PickerOwnerState", "kind": "Interface" }, { "name": "PickersActionBar", "kind": "Function" }, { "name": "PickersActionBarAction", "kind": "TypeAlias" }, diff --git a/scripts/x-date-pickers.exports.json b/scripts/x-date-pickers.exports.json index 94e004c0da2d..5ec0e96bfc2e 100644 --- a/scripts/x-date-pickers.exports.json +++ b/scripts/x-date-pickers.exports.json @@ -163,6 +163,7 @@ { "name": "MultiSectionDigitalClockSlots", "kind": "Interface" }, { "name": "OnErrorProps", "kind": "Interface" }, { "name": "PickerChangeHandlerContext", "kind": "Interface" }, + { "name": "PickerDayOwnerState", "kind": "Interface" }, { "name": "PickerOwnerState", "kind": "Interface" }, { "name": "PickersActionBar", "kind": "Function" }, { "name": "PickersActionBarAction", "kind": "TypeAlias" },