Skip to content

Commit

Permalink
[pickers] Use context to remove props on <PickersPopper />
Browse files Browse the repository at this point in the history
  • Loading branch information
flaviendelangle committed Jan 24, 2025
1 parent 8afcd2f commit d7a750c
Show file tree
Hide file tree
Showing 13 changed files with 83 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
DayCalendar,
DayCalendarSlots,
DayCalendarSlotProps,
useDefaultReduceAnimations,
useReduceAnimations,
useCalendarState,
useDefaultDates,
useUtils,
Expand Down Expand Up @@ -113,17 +113,17 @@ function useDateRangeCalendarDefaultizedProps(
): DateRangeCalendarDefaultizedProps {
const utils = useUtils();
const defaultDates = useDefaultDates();
const defaultReduceAnimations = useDefaultReduceAnimations();
const themeProps = useThemeProps({
props,
name,
});
const reduceAnimations = useReduceAnimations(themeProps.reduceAnimations);

return {
...themeProps,
renderLoading:
themeProps.renderLoading ?? (() => <span data-testid="loading-progress">...</span>),
reduceAnimations: themeProps.reduceAnimations ?? defaultReduceAnimations,
reduceAnimations,
loading: props.loading ?? false,
disablePast: props.disablePast ?? false,
disableFuture: props.disableFuture ?? false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ export const useDesktopRangePicker = <
autoFocus,
disableOpenPicker,
localeText,
reduceAnimations,
} = props;

const fieldContainerRef = React.useRef<HTMLDivElement>(null);
Expand All @@ -80,7 +79,7 @@ export const useDesktopRangePicker = <
fieldRef = endFieldRef;
}

const { providerProps, renderCurrentView, shouldRestoreFocus, ownerState } = usePicker<
const { providerProps, renderCurrentView, ownerState } = usePicker<
PickerRangeValue,
TView,
TExternalProps
Expand Down Expand Up @@ -187,14 +186,10 @@ export const useDesktopRangePicker = <
<Field {...enrichedFieldResponse.fieldProps} />
<PickersPopper
role="tooltip"
placement="bottom-start"
containerRef={popperRef}
anchorEl={providerProps.contextValue.triggerRef.current}
onBlur={handleBlur}
slots={slots}
slotProps={slotProps}
shouldRestoreFocus={shouldRestoreFocus}
reduceAnimations={reduceAnimations}
>
<Layout {...slotProps?.layout} slots={slots} slotProps={slotProps}>
{renderCurrentView()}
Expand Down
6 changes: 3 additions & 3 deletions packages/x-date-pickers/src/DateCalendar/DateCalendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
mergeDateAndTime,
} from '../internals/utils/date-utils';
import { PickerViewRoot } from '../internals/components/PickerViewRoot';
import { useDefaultReduceAnimations } from '../internals/hooks/useDefaultReduceAnimations';
import { useReduceAnimations } from '../internals/hooks/useReduceAnimations';
import { DateCalendarClasses, getDateCalendarUtilityClass } from './dateCalendarClasses';
import { BaseDateValidationProps } from '../internals/models/validation';
import { useControlledValueWithTimezone } from '../internals/hooks/useValueWithTimezone';
Expand All @@ -48,11 +48,11 @@ function useDateCalendarDefaultizedProps(
): DateCalendarDefaultizedProps {
const utils = useUtils();
const defaultDates = useDefaultDates();
const defaultReduceAnimations = useDefaultReduceAnimations();
const themeProps = useThemeProps({
props,
name,
});
const reduceAnimations = useReduceAnimations(themeProps.reduceAnimations);

return {
...themeProps,
Expand All @@ -61,7 +61,7 @@ function useDateCalendarDefaultizedProps(
disableFuture: themeProps.disableFuture ?? false,
openTo: themeProps.openTo ?? 'day',
views: themeProps.views ?? ['year', 'day'],
reduceAnimations: themeProps.reduceAnimations ?? defaultReduceAnimations,
reduceAnimations,
renderLoading:
themeProps.renderLoading ?? (() => <span data-testid="loading-progress">...</span>),
minDate: applyDefaultDate(utils, themeProps.minDate, defaultDates.minDate),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type {
import {
UsePickerViewsActionsContextValue,
UsePickerViewsContextValue,
UsePickerViewsPrivateContextValue,
} from '../hooks/usePicker/usePickerViews';
import { IsValidValueContext } from '../../hooks/useIsValidValue';
import {
Expand All @@ -40,6 +41,8 @@ export const PickerPrivateContext = React.createContext<PickerPrivateContextValu
pickerOrientation: 'portrait',
},
dismissViews: () => {},
hasUIView: true,
doesTheCurrentViewHasAnUI: () => true,
});

/**
Expand Down Expand Up @@ -118,6 +121,11 @@ export interface PickerContextValue<
* Is always equal to "portrait" if the component you are accessing the context from is not wrapped by a picker.
*/
orientation: PickerOrientation;
/**
* Whether the heavy animations should be disabled.
* @default `@media(prefers-reduced-motion: reduce)` || `navigator.userAgent` matches Android <10 or iOS <13
*/
reduceAnimations?: boolean;
/**
* The ref that should be attached to the element that triggers the Picker opening.
* When using a built-in field component, this property is automatically handled.
Expand Down Expand Up @@ -147,7 +155,9 @@ export interface PickerActionsContextValue<
> extends UsePickerValueActionsContextValue<TValue, TError>,
UsePickerViewsActionsContextValue<TView> {}

export interface PickerPrivateContextValue extends UsePickerValuePrivateContextValue {
export interface PickerPrivateContextValue
extends UsePickerValuePrivateContextValue,
UsePickerViewsPrivateContextValue {
/**
* The ownerState of the picker.
*/
Expand Down
31 changes: 10 additions & 21 deletions packages/x-date-pickers/src/internals/components/PickersPopper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import { TransitionProps as MuiTransitionProps } from '@mui/material/transitions
import { SlotComponentPropsFromProps } from '@mui/x-internals/types';
import { getPickersPopperUtilityClass, PickersPopperClasses } from './pickersPopperClasses';
import { getActiveElement } from '../utils/utils';
import { useDefaultReduceAnimations } from '../hooks/useDefaultReduceAnimations';
import { usePickerPrivateContext } from '../hooks/usePickerPrivateContext';
import { PickerOwnerState } from '../../models';
import { usePickerContext } from '../../hooks';
Expand Down Expand Up @@ -75,11 +74,6 @@ export interface PickersPopperSlotProps {

export interface PickerPopperProps {
role: 'tooltip' | 'dialog';
anchorEl: MuiPopperProps['anchorEl'];
/**
* @default "bottom"
*/
placement?: MuiPopperProps['placement'];
containerRef?: React.Ref<HTMLDivElement>;
children?: React.ReactNode;
onBlur?: () => void;
Expand All @@ -89,8 +83,6 @@ export interface PickerPopperProps {
* Override or extend the styles applied to the component.
*/
classes?: Partial<PickersPopperClasses>;
shouldRestoreFocus?: () => boolean;
reduceAnimations?: boolean;
}

const useUtilityClasses = (classes: Partial<PickersPopperClasses> | undefined) => {
Expand Down Expand Up @@ -333,21 +325,17 @@ const PickersPopperPaperWrapper = React.forwardRef(
export function PickersPopper(inProps: PickerPopperProps) {
const props = useThemeProps({ props: inProps, name: 'MuiPickersPopper' });
const {
anchorEl,
children,
containerRef = null,
shouldRestoreFocus,
onBlur,
role,
placement = 'bottom',
slots,
slotProps,
reduceAnimations: inReduceAnimations,
classes: classesProp,
} = props;

const { open } = usePickerContext();
const { dismissViews } = usePickerPrivateContext();
const { open, triggerRef, reduceAnimations } = usePickerContext();
const { dismissViews, doesTheCurrentViewHasAnUI } = usePickerPrivateContext();

React.useEffect(() => {
function handleKeyDown(nativeEvent: KeyboardEvent) {
Expand All @@ -365,7 +353,7 @@ export function PickersPopper(inProps: PickerPopperProps) {

const lastFocusedElementRef = React.useRef<Element | null>(null);
React.useEffect(() => {
if (role === 'tooltip' || (shouldRestoreFocus && !shouldRestoreFocus())) {
if (role === 'tooltip' || !doesTheCurrentViewHasAnUI()) {
return;
}

Expand All @@ -383,7 +371,7 @@ export function PickersPopper(inProps: PickerPopperProps) {
}
});
}
}, [open, role, shouldRestoreFocus]);
}, [open, role, doesTheCurrentViewHasAnUI]);

const [clickAwayRef, onPaperClick, onPaperTouchStart] = useClickAwayListener(
open,
Expand All @@ -394,10 +382,11 @@ export function PickersPopper(inProps: PickerPopperProps) {
const handlePaperRef = useForkRef(handleRef, clickAwayRef as React.Ref<HTMLDivElement>);

const classes = useUtilityClasses(classesProp);
const defaultReduceAnimations = useDefaultReduceAnimations();
const reduceAnimations = inReduceAnimations ?? defaultReduceAnimations;
const { ownerState: pickerOwnerState } = usePickerPrivateContext();
const ownerState: PickerPopperOwnerState = { ...pickerOwnerState, popperPlacement: placement };
const ownerState: PickerPopperOwnerState = {
...pickerOwnerState,
popperPlacement: 'bottom-start',
};

const handleKeyDown = (event: React.KeyboardEvent) => {
if (event.key === 'Escape') {
Expand All @@ -419,8 +408,8 @@ export function PickersPopper(inProps: PickerPopperProps) {
transition: true,
role,
open,
anchorEl,
placement,
anchorEl: triggerRef.current,
placement: 'bottom-start' as const,
onKeyDown: handleKeyDown,
},
className: classes.root,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,14 @@ export const useDesktopPicker = <
readOnly,
autoFocus,
localeText,
reduceAnimations,
} = props;

const fieldRef = React.useRef<FieldRef<PickerValue>>(null);

const labelId = useId();
const isToolbarHidden = innerSlotProps?.toolbar?.hidden ?? false;

const { providerProps, renderCurrentView, shouldRestoreFocus, ownerState } = usePicker<
const { providerProps, renderCurrentView, ownerState } = usePicker<
PickerValue,
TView,
TExternalProps
Expand Down Expand Up @@ -115,15 +114,7 @@ export const useDesktopPicker = <
<PickerProvider {...providerProps}>
<PickerFieldUIContextProvider slots={slots} slotProps={slotProps}>
<Field {...fieldProps} unstableFieldRef={handleFieldRef} />
<PickersPopper
role="dialog"
placement="bottom-start"
anchorEl={providerProps.contextValue.triggerRef!.current}
slots={slots}
slotProps={slotProps}
shouldRestoreFocus={shouldRestoreFocus}
reduceAnimations={reduceAnimations}
>
<PickersPopper role="dialog" slots={slots} slotProps={slotProps}>
<Layout {...slotProps?.layout} slots={slots} slotProps={slotProps}>
{renderCurrentView()}
</Layout>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
export { usePicker } from './usePicker';
export type {
UsePickerProps,
UsePickerBaseProps,
UsePickerParams,
UsePickerResponse,
} from './usePicker.types';
export type { UsePickerProps, UsePickerBaseProps, UsePickerParams } from './usePicker.types';

export type {
PickerValueManager,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { warnOnce } from '@mui/x-internals/warning';
import { UsePickerParams, UsePickerProps, UsePickerResponse } from './usePicker.types';
import { UsePickerParams, UsePickerProps, UsePickerReturnValue } from './usePicker.types';
import { usePickerValue } from './usePickerValue';
import { usePickerViews } from './usePickerViews';
import { DateOrTimeViewWithMeridiem, PickerValidValue } from '../../models';
Expand All @@ -19,7 +19,7 @@ export const usePicker = <
rendererInterceptor,
fieldRef,
localeText,
}: UsePickerParams<TValue, TView, TExternalProps>): UsePickerResponse<TValue, TView> => {
}: UsePickerParams<TValue, TView, TExternalProps>): UsePickerReturnValue<TValue> => {
if (process.env.NODE_ENV !== 'production') {
if ((props as any).renderInput != null) {
warnOnce([
Expand Down Expand Up @@ -56,7 +56,6 @@ export const usePicker = <
return {
// Picker views
renderCurrentView: pickerViewsResponse.renderCurrentView,
shouldRestoreFocus: pickerViewsResponse.shouldRestoreFocus,

// Picker provider
providerProps,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
import {
UsePickerViewsProps,
UsePickerViewParams,
UsePickerViewsResponse,
UsePickerViewsBaseProps,
} from './usePickerViews';
import { InferError, PickerOwnerState } from '../../../models';
Expand Down Expand Up @@ -57,10 +56,8 @@ export interface UsePickerParams<
props: TExternalProps;
}

export interface UsePickerResponse<
TValue extends PickerValidValue,
TView extends DateOrTimeViewWithMeridiem,
> extends Pick<UsePickerViewsResponse<TView>, 'shouldRestoreFocus' | 'renderCurrentView'> {
export interface UsePickerReturnValue<TValue extends PickerValidValue> {
ownerState: PickerOwnerState;
renderCurrentView: () => React.ReactNode;
providerProps: UsePickerProviderReturnValue<TValue>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { useUtils } from '../useUtils';
import { arrayIncludes } from '../../utils/utils';
import { UsePickerViewsProviderParams } from './usePickerViews';
import { PickerFieldPrivateContextValue } from '../useField/useFieldInternalPropsWithDefaults';
import { useReduceAnimations } from '../useReduceAnimations';

function getOrientation(): PickerOrientation {
if (typeof window === 'undefined') {
Expand Down Expand Up @@ -79,6 +80,8 @@ export function usePickerProvider<

const utils = useUtils();
const orientation = usePickerOrientation(paramsFromUsePickerViews.views, props.orientation);
const reduceAnimations = useReduceAnimations(props.reduceAnimations);

const triggerRef = React.useRef<HTMLElement>(null);

const ownerState = React.useMemo<PickerOwnerState>(
Expand Down Expand Up @@ -126,6 +129,7 @@ export function usePickerProvider<
readOnly: props.readOnly ?? false,
variant,
orientation,
reduceAnimations,
triggerRef,
triggerStatus,
fieldFormat: props.format ?? '',
Expand All @@ -135,6 +139,7 @@ export function usePickerProvider<
paramsFromUsePickerViews.contextValue,
variant,
orientation,
reduceAnimations,
props.disabled,
props.readOnly,
triggerRef,
Expand All @@ -144,8 +149,16 @@ export function usePickerProvider<
);

const privateContextValue = React.useMemo<PickerPrivateContextValue>(
() => ({ ...paramsFromUsePickerValue.privateContextValue, ownerState }),
[paramsFromUsePickerValue, ownerState],
() => ({
...paramsFromUsePickerValue.privateContextValue,
...paramsFromUsePickerViews.privateContextValue,
ownerState,
}),
[
paramsFromUsePickerValue.privateContextValue,
paramsFromUsePickerViews.privateContextValue,
ownerState,
],
);

const actionsContextValue = React.useMemo(
Expand Down Expand Up @@ -205,6 +218,11 @@ export interface UsePickerProviderProps extends FormProps {
* Force rendering in particular orientation.
*/
orientation?: PickerOrientation;
/**
* If `true`, disable heavy animations.
* @default `@media(prefers-reduced-motion: reduce)` || `navigator.userAgent` matches Android <10 or iOS <13
*/
reduceAnimations?: boolean;
}

/**
Expand Down
Loading

0 comments on commit d7a750c

Please sign in to comment.