Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[core] Fix default props behavior on all pickers #4451

Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ module.exports = {
'useDatePickerDefaultizedProps',
'useTimePickerDefaultizedProps',
'useDateTimePickerDefaultizedProps',
'useDateRangePickerDefaultizedProps',
],
},
],
Expand Down
175 changes: 32 additions & 143 deletions packages/x-date-pickers-pro/src/DateRangePicker/DateRangePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,90 +2,24 @@ import PropTypes from 'prop-types';
import * as React from 'react';
import { useThemeProps } from '@mui/material/styles';
import { useLicenseVerifier } from '@mui/x-license-pro';
import {
ResponsiveTooltipWrapper,
ResponsiveWrapperProps,
useDefaultDates,
useUtils,
ValidationProps,
usePickerState,
PickerStateValueManager,
DateInputPropsLike,
} from '@mui/x-date-pickers/internals';
import { DateRangePickerView, ExportedDateRangePickerViewProps } from './DateRangePickerView';
import { DateRangePickerInput, ExportedDateRangePickerInputProps } from './DateRangePickerInput';
import { RangeInput, DateRange } from '../internal/models/dateRange';
import {
useDateRangeValidation,
DateRangeValidationError,
} from '../internal/hooks/validation/useDateRangeValidation';
import { parseRangeInputValue } from '../internal/utils/date-utils';
import useMediaQuery from '@mui/material/useMediaQuery';
import { getReleaseInfo } from '../internal/utils/releaseInfo';
import { DesktopDateRangePicker, DesktopDateRangePickerProps } from '../DesktopDateRangePicker';
import { MobileDateRangePicker, MobileDateRangePickerProps } from '../MobileDateRangePicker';

const releaseInfo = getReleaseInfo();

interface BaseDateRangePickerProps<TDate>
extends ExportedDateRangePickerViewProps<TDate>,
ValidationProps<DateRangeValidationError, RangeInput<TDate>>,
ExportedDateRangePickerInputProps {
/**
* The components used for each slot.
* Either a string to use an HTML element or a component.
* @default {}
*/
components?: ExportedDateRangePickerViewProps<TDate>['components'] &
ExportedDateRangePickerInputProps['components'];
/**
* Text for end input label and toolbar placeholder.
* @default 'End'
*/
endText?: React.ReactNode;
/**
* Custom mask. Can be used to override generate from format. (e.g. `__/__/____ __:__` or `__/__/____ __:__ _M`).
* @default '__/__/____'
*/
mask?: ExportedDateRangePickerInputProps['mask'];
/**
* Min selectable date. @DateIOType
* @default defaultMinDate
*/
minDate?: TDate;
/**
* Max selectable date. @DateIOType
* @default defaultMaxDate
*/
maxDate?: TDate;
/**
* Callback fired when the value (the selected date range) changes @DateIOType.
* @template TDate
* @param {DateRange<TDate>} date The new parsed date range.
* @param {string} keyboardInputValue The current value of the keyboard input.
*/
onChange: (date: DateRange<TDate>, keyboardInputValue?: string) => void;
/**
* Text for start input label and toolbar placeholder.
* @default 'Start'
*/
startText?: React.ReactNode;
export interface DateRangePickerProps<TDate>
extends DesktopDateRangePickerProps<TDate>,
MobileDateRangePickerProps<TDate> {
/**
* The value of the date range picker.
* CSS media query when `Mobile` mode will be changed to `Desktop`.
* @default '@media (pointer: fine)'
* @example '@media (min-width: 720px)' or theme.breakpoints.up("sm")
*/
value: RangeInput<TDate>;
desktopModeMediaQuery?: string;
}

const KeyboardDateInputComponent = DateRangePickerInput as unknown as React.FC<DateInputPropsLike>;
const PureDateInputComponent = DateRangePickerInput as unknown as React.FC<DateInputPropsLike>;

const rangePickerValueManager: PickerStateValueManager<any, any> = {
emptyValue: [null, null],
parseInput: parseRangeInputValue,
areValuesEqual: (utils, a, b) => utils.isEqual(a[0], b[0]) && utils.isEqual(a[1], b[1]),
};

export interface DateRangePickerProps<TDate>
extends BaseDateRangePickerProps<TDate>,
ResponsiveWrapperProps {}

type DateRangePickerComponent = (<TDate>(
props: DateRangePickerProps<TDate> & React.RefAttributes<HTMLDivElement>,
) => JSX.Element) & { propTypes?: any };
Expand All @@ -108,78 +42,33 @@ export const DateRangePicker = React.forwardRef(function DateRangePicker<TDate>(
useLicenseVerifier('x-date-pickers-pro', releaseInfo);

const {
calendars = 2,
value,
onChange,
mask = '__/__/____',
startText = 'Start',
endText = 'End',
inputFormat: passedInputFormat,
minDate: minDateProp,
maxDate: maxDateProp,
cancelText,
desktopModeMediaQuery = '@media (pointer: fine)',
DialogProps,
okText,
PopperProps,
showTodayButton,
todayText,
TransitionComponent,
flaviendelangle marked this conversation as resolved.
Show resolved Hide resolved
...other
} = props;

const utils = useUtils<TDate>();
const defaultDates = useDefaultDates<TDate>();
const minDate = minDateProp ?? defaultDates.minDate;
const maxDate = maxDateProp ?? defaultDates.maxDate;
const [currentlySelectingRangeEnd, setCurrentlySelectingRangeEnd] = React.useState<
'start' | 'end'
>('start');

const pickerStateProps = {
...other,
value,
onChange,
};

const restProps = {
...other,
minDate,
maxDate,
};

const { pickerProps, inputProps, wrapperProps } = usePickerState<
RangeInput<TDate>,
DateRange<TDate>
>(pickerStateProps, rangePickerValueManager);

const validationError = useDateRangeValidation(props);
const isDesktop = useMediaQuery(desktopModeMediaQuery);

const DateInputProps = {
...inputProps,
...restProps,
currentlySelectingRangeEnd,
inputFormat: passedInputFormat || utils.formats.keyboardDate,
setCurrentlySelectingRangeEnd,
startText,
endText,
mask,
validationError,
ref,
};
if (isDesktop) {
return <DesktopDateRangePicker ref={ref} PopperProps={PopperProps} {...other} />;
}

return (
<ResponsiveTooltipWrapper
{...restProps}
{...wrapperProps}
DateInputProps={DateInputProps}
KeyboardDateInputComponent={KeyboardDateInputComponent}
PureDateInputComponent={PureDateInputComponent}
>
<DateRangePickerView<any>
open={wrapperProps.open}
DateInputProps={DateInputProps}
calendars={calendars}
currentlySelectingRangeEnd={currentlySelectingRangeEnd}
setCurrentlySelectingRangeEnd={setCurrentlySelectingRangeEnd}
startText={startText}
endText={endText}
{...pickerProps}
{...restProps}
/>
</ResponsiveTooltipWrapper>
<MobileDateRangePicker
ref={ref}
cancelText={cancelText}
DialogProps={DialogProps}
okText={okText}
showTodayButton={showTodayButton}
todayText={todayText}
{...other}
/>
);
}) as DateRangePickerComponent;

Expand Down Expand Up @@ -242,7 +131,7 @@ DateRangePicker.propTypes = {
/**
* CSS media query when `Mobile` mode will be changed to `Desktop`.
* @default '@media (pointer: fine)'
* @example '@media (min-width: 720px)' or theme.breakpoints.up('sm')
* @example '@media (min-width: 720px)' or theme.breakpoints.up("sm")
*/
desktopModeMediaQuery: PropTypes.string,
/**
Expand Down
90 changes: 90 additions & 0 deletions packages/x-date-pickers-pro/src/DateRangePicker/shared.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import * as React from 'react';
import { useDefaultDates, useUtils, ValidationProps } from '@mui/x-date-pickers/internals';
import { useThemeProps } from '@mui/material/styles';
import { ExportedDateRangePickerViewProps } from './DateRangePickerView';
import { DateRangeValidationError } from '../internal/hooks/validation/useDateRangeValidation';
import { DateRange, RangeInput } from '../internal/models';
import { ExportedDateRangePickerInputProps } from './DateRangePickerInput';

export interface BaseDateRangePickerProps<TDate>
extends ExportedDateRangePickerViewProps<TDate>,
ValidationProps<DateRangeValidationError, RangeInput<TDate>>,
ExportedDateRangePickerInputProps {
/**
* The components used for each slot.
* Either a string to use an HTML element or a component.
* @default {}
*/
components?: ExportedDateRangePickerViewProps<TDate>['components'] &
ExportedDateRangePickerInputProps['components'];
/**
* Text for end input label and toolbar placeholder.
* @default 'End'
*/
endText?: React.ReactNode;
/**
* Custom mask. Can be used to override generate from format. (e.g. `__/__/____ __:__` or `__/__/____ __:__ _M`).
* @default '__/__/____'
*/
mask?: ExportedDateRangePickerInputProps['mask'];
/**
* Min selectable date. @DateIOType
* @default defaultMinDate
*/
minDate?: TDate;
/**
* Max selectable date. @DateIOType
* @default defaultMaxDate
*/
maxDate?: TDate;
/**
* Callback fired when the value (the selected date range) changes @DateIOType.
* @template TDate
* @param {DateRange<TDate>} date The new parsed date range.
* @param {string} keyboardInputValue The current value of the keyboard input.
*/
onChange: (date: DateRange<TDate>, keyboardInputValue?: string) => void;
/**
* Text for start input label and toolbar placeholder.
* @default 'Start'
*/
startText?: React.ReactNode;
/**
* The value of the date range picker.
*/
value: RangeInput<TDate>;
}

export type DefaultizedProps<Props> = Props & { inputFormat: string };

export function useDateRangePickerDefaultizedProps<
TDate,
Props extends BaseDateRangePickerProps<TDate>,
>(
props: Props,
name: string,
): DefaultizedProps<Props> &
Required<
Pick<BaseDateRangePickerProps<unknown>, 'calendars' | 'mask' | 'startText' | 'endText'>
> {
const utils = useUtils<TDate>();
const defaultDates = useDefaultDates();

// This is technically unsound if the type parameters appear in optional props.
// Optional props can be filled by `useThemeProps` with types that don't match the type parameters.
const themeProps = useThemeProps({
props,
name,
});

return {
calendars: 2,
mask: '__/__/____',
startText: 'Start',
endText: 'End',
inputFormat: utils.formats.keyboardDate,
minDate: defaultDates.minDate,
maxDate: defaultDates.maxDate,
...themeProps,
};
}
Loading