diff --git a/.eslintrc.js b/.eslintrc.js index f26508999664..a914295fe73a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -78,6 +78,7 @@ module.exports = { 'useDatePickerDefaultizedProps', 'useTimePickerDefaultizedProps', 'useDateTimePickerDefaultizedProps', + 'useDateRangePickerDefaultizedProps', ], }, ], diff --git a/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePicker.tsx b/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePicker.tsx index 5d679693e938..0ad4a9475c80 100644 --- a/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePicker.tsx +++ b/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePicker.tsx @@ -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 - extends ExportedDateRangePickerViewProps, - ValidationProps>, - ExportedDateRangePickerInputProps { - /** - * The components used for each slot. - * Either a string to use an HTML element or a component. - * @default {} - */ - components?: ExportedDateRangePickerViewProps['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} date The new parsed date range. - * @param {string} keyboardInputValue The current value of the keyboard input. - */ - onChange: (date: DateRange, keyboardInputValue?: string) => void; - /** - * Text for start input label and toolbar placeholder. - * @default 'Start' - */ - startText?: React.ReactNode; +export interface DateRangePickerProps + extends DesktopDateRangePickerProps, + MobileDateRangePickerProps { /** - * 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; + desktopModeMediaQuery?: string; } -const KeyboardDateInputComponent = DateRangePickerInput as unknown as React.FC; -const PureDateInputComponent = DateRangePickerInput as unknown as React.FC; - -const rangePickerValueManager: PickerStateValueManager = { - emptyValue: [null, null], - parseInput: parseRangeInputValue, - areValuesEqual: (utils, a, b) => utils.isEqual(a[0], b[0]) && utils.isEqual(a[1], b[1]), -}; - -export interface DateRangePickerProps - extends BaseDateRangePickerProps, - ResponsiveWrapperProps {} - type DateRangePickerComponent = (( props: DateRangePickerProps & React.RefAttributes, ) => JSX.Element) & { propTypes?: any }; @@ -108,78 +42,40 @@ export const DateRangePicker = React.forwardRef(function DateRangePicker( 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, ...other } = props; - const utils = useUtils(); - const defaultDates = useDefaultDates(); - const minDate = minDateProp ?? defaultDates.minDate; - const maxDate = maxDateProp ?? defaultDates.maxDate; - const [currentlySelectingRangeEnd, setCurrentlySelectingRangeEnd] = React.useState< - 'start' | 'end' - >('start'); - - const pickerStateProps = { - ...other, - value, - onChange, - }; + const isDesktop = useMediaQuery(desktopModeMediaQuery); - const restProps = { - ...other, - minDate, - maxDate, - }; - - const { pickerProps, inputProps, wrapperProps } = usePickerState< - RangeInput, - DateRange - >(pickerStateProps, rangePickerValueManager); - - const validationError = useDateRangeValidation(props); - - const DateInputProps = { - ...inputProps, - ...restProps, - currentlySelectingRangeEnd, - inputFormat: passedInputFormat || utils.formats.keyboardDate, - setCurrentlySelectingRangeEnd, - startText, - endText, - mask, - validationError, - ref, - }; + if (isDesktop) { + return ( + + ); + } return ( - - - open={wrapperProps.open} - DateInputProps={DateInputProps} - calendars={calendars} - currentlySelectingRangeEnd={currentlySelectingRangeEnd} - setCurrentlySelectingRangeEnd={setCurrentlySelectingRangeEnd} - startText={startText} - endText={endText} - {...pickerProps} - {...restProps} - /> - + ); }) as DateRangePickerComponent; @@ -242,7 +138,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, /** diff --git a/packages/x-date-pickers-pro/src/DateRangePicker/shared.ts b/packages/x-date-pickers-pro/src/DateRangePicker/shared.ts new file mode 100644 index 000000000000..9c06c1c6930e --- /dev/null +++ b/packages/x-date-pickers-pro/src/DateRangePicker/shared.ts @@ -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 + extends ExportedDateRangePickerViewProps, + ValidationProps>, + ExportedDateRangePickerInputProps { + /** + * The components used for each slot. + * Either a string to use an HTML element or a component. + * @default {} + */ + components?: ExportedDateRangePickerViewProps['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} date The new parsed date range. + * @param {string} keyboardInputValue The current value of the keyboard input. + */ + onChange: (date: DateRange, 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; +} + +export type DefaultizedProps = Props & { inputFormat: string }; + +export function useDateRangePickerDefaultizedProps< + TDate, + Props extends BaseDateRangePickerProps, +>( + props: Props, + name: string, +): DefaultizedProps & + Required< + Pick, 'calendars' | 'mask' | 'startText' | 'endText'> + > { + const utils = useUtils(); + 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, + }; +} diff --git a/packages/x-date-pickers-pro/src/DesktopDateRangePicker/DesktopDateRangePicker.tsx b/packages/x-date-pickers-pro/src/DesktopDateRangePicker/DesktopDateRangePicker.tsx index 81e47798f495..9ca40c57b869 100644 --- a/packages/x-date-pickers-pro/src/DesktopDateRangePicker/DesktopDateRangePicker.tsx +++ b/packages/x-date-pickers-pro/src/DesktopDateRangePicker/DesktopDateRangePicker.tsx @@ -1,84 +1,25 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { useLicenseVerifier } from '@mui/x-license-pro'; -import { useThemeProps } from '@mui/material/styles'; import { DesktopTooltipWrapper, - useDefaultDates, - useUtils, - ValidationProps, usePickerState, PickerStateValueManager, DateInputPropsLike, DesktopWrapperProps, } from '@mui/x-date-pickers/internals'; -import { - DateRangePickerView, - ExportedDateRangePickerViewProps, -} from '../DateRangePicker/DateRangePickerView'; -import { - DateRangePickerInput, - ExportedDateRangePickerInputProps, -} from '../DateRangePicker/DateRangePickerInput'; -import { RangeInput, DateRange } from '../internal/models/dateRange'; -import { - DateRangeValidationError, - useDateRangeValidation, -} from '../internal/hooks/validation/useDateRangeValidation'; +import { DateRangePickerView } from '../DateRangePicker/DateRangePickerView'; +import { DateRangePickerInput } from '../DateRangePicker/DateRangePickerInput'; +import { useDateRangeValidation } from '../internal/hooks/validation/useDateRangeValidation'; import { parseRangeInputValue } from '../internal/utils/date-utils'; import { getReleaseInfo } from '../internal/utils/releaseInfo'; +import { + BaseDateRangePickerProps, + useDateRangePickerDefaultizedProps, +} from '../DateRangePicker/shared'; const releaseInfo = getReleaseInfo(); -interface BaseDateRangePickerProps - extends ExportedDateRangePickerViewProps, - ValidationProps>, - ExportedDateRangePickerInputProps { - /** - * The components used for each slot. - * Either a string to use an HTML element or a component. - * @default {} - */ - components?: ExportedDateRangePickerViewProps['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} date The new parsed date range. - * @param {string} keyboardInputValue The current value of the keyboard input. - */ - onChange: (date: DateRange, 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; -} - const KeyboardDateInputComponent = DateRangePickerInput as unknown as React.FC; const rangePickerValueManager: PickerStateValueManager = { @@ -109,28 +50,16 @@ export const DesktopDateRangePicker = React.forwardRef(function DesktopDateRange inProps: DesktopDateRangePickerProps, ref: React.Ref, ) { - const props = useThemeProps({ props: inProps, name: 'MuiDesktopDateRangePicker' }); useLicenseVerifier('x-date-pickers-pro', releaseInfo); - const { - calendars = 2, - value, - onChange, - mask = '__/__/____', - startText = 'Start', - endText = 'End', - inputFormat: passedInputFormat, - minDate: minDateProp, - maxDate: maxDateProp, - PopperProps, - TransitionComponent, - ...other - } = props; + // TODO: TDate needs to be instantiated at every usage. + const props = useDateRangePickerDefaultizedProps( + inProps as DesktopDateRangePickerProps, + 'MuiDesktopDateRangePicker', + ); + + const { value, onChange, PopperProps, TransitionComponent, ...other } = props; - const utils = useUtils(); - const defaultDates = useDefaultDates(); - const minDate = minDateProp ?? defaultDates.minDate; - const maxDate = maxDateProp ?? defaultDates.maxDate; const [currentlySelectingRangeEnd, setCurrentlySelectingRangeEnd] = React.useState< 'start' | 'end' >('start'); @@ -141,28 +70,18 @@ export const DesktopDateRangePicker = React.forwardRef(function DesktopDateRange onChange, }; - const restProps = { - ...other, - minDate, - maxDate, - }; - - const { pickerProps, inputProps, wrapperProps } = usePickerState< - RangeInput, - DateRange - >(pickerStateProps, rangePickerValueManager); + const { pickerProps, inputProps, wrapperProps } = usePickerState( + pickerStateProps, + rangePickerValueManager, + ); const validationError = useDateRangeValidation(props); const DateInputProps = { ...inputProps, - ...restProps, + ...other, currentlySelectingRangeEnd, - inputFormat: passedInputFormat || utils.formats.keyboardDate, setCurrentlySelectingRangeEnd, - startText, - endText, - mask, validationError, ref, }; @@ -178,13 +97,10 @@ export const DesktopDateRangePicker = React.forwardRef(function DesktopDateRange open={wrapperProps.open} DateInputProps={DateInputProps} - calendars={calendars} currentlySelectingRangeEnd={currentlySelectingRangeEnd} setCurrentlySelectingRangeEnd={setCurrentlySelectingRangeEnd} - startText={startText} - endText={endText} {...pickerProps} - {...restProps} + {...other} /> ); diff --git a/packages/x-date-pickers-pro/src/MobileDateRangePicker/MobileDateRangePicker.tsx b/packages/x-date-pickers-pro/src/MobileDateRangePicker/MobileDateRangePicker.tsx index 32f6c794f070..519f971331fa 100644 --- a/packages/x-date-pickers-pro/src/MobileDateRangePicker/MobileDateRangePicker.tsx +++ b/packages/x-date-pickers-pro/src/MobileDateRangePicker/MobileDateRangePicker.tsx @@ -1,84 +1,25 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { useLicenseVerifier } from '@mui/x-license-pro'; -import { useThemeProps } from '@mui/material/styles'; import { MobileWrapper, MobileWrapperProps, - ValidationProps, - useDefaultDates, - useUtils, usePickerState, PickerStateValueManager, DateInputPropsLike, } from '@mui/x-date-pickers/internals'; -import { RangeInput, DateRange } from '../internal/models/dateRange'; -import { - DateRangeValidationError, - useDateRangeValidation, -} from '../internal/hooks/validation/useDateRangeValidation'; -import { - DateRangePickerView, - ExportedDateRangePickerViewProps, -} from '../DateRangePicker/DateRangePickerView'; -import { - DateRangePickerInput, - ExportedDateRangePickerInputProps, -} from '../DateRangePicker/DateRangePickerInput'; +import { useDateRangeValidation } from '../internal/hooks/validation/useDateRangeValidation'; +import { DateRangePickerView } from '../DateRangePicker/DateRangePickerView'; +import { DateRangePickerInput } from '../DateRangePicker/DateRangePickerInput'; import { parseRangeInputValue } from '../internal/utils/date-utils'; import { getReleaseInfo } from '../internal/utils/releaseInfo'; +import { + BaseDateRangePickerProps, + useDateRangePickerDefaultizedProps, +} from '../DateRangePicker/shared'; const releaseInfo = getReleaseInfo(); -interface BaseDateRangePickerProps - extends ExportedDateRangePickerViewProps, - ValidationProps>, - ExportedDateRangePickerInputProps { - /** - * The components used for each slot. - * Either a string to use an HTML element or a component. - * @default {} - */ - components?: ExportedDateRangePickerViewProps['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} date The new parsed date range. - * @param {string} keyboardInputValue The current value of the keyboard input. - */ - onChange: (date: DateRange, 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; -} - const PureDateInputComponent = DateRangePickerInput as unknown as React.FC; const rangePickerValueManager: PickerStateValueManager = { @@ -109,26 +50,16 @@ export const MobileDateRangePicker = React.forwardRef(function MobileDateRangePi inProps: MobileDateRangePickerProps, ref: React.Ref, ) { - const props = useThemeProps({ props: inProps, name: 'MuiMobileDateRangePicker' }); useLicenseVerifier('x-date-pickers-pro', releaseInfo); - const { - calendars = 2, - value, - onChange, - mask = '__/__/____', - startText = 'Start', - endText = 'End', - inputFormat: passedInputFormat, - minDate: minDateProp, - maxDate: maxDateProp, - ...other - } = props; + // TODO: TDate needs to be instantiated at every usage. + const props = useDateRangePickerDefaultizedProps( + inProps as MobileDateRangePickerProps, + 'MuiMobileDateRangePicker', + ); + + const { value, onChange, ...other } = props; - const utils = useUtils(); - const defaultDates = useDefaultDates(); - const minDate = minDateProp ?? defaultDates.minDate; - const maxDate = maxDateProp ?? defaultDates.maxDate; const [currentlySelectingRangeEnd, setCurrentlySelectingRangeEnd] = React.useState< 'start' | 'end' >('start'); @@ -139,35 +70,25 @@ export const MobileDateRangePicker = React.forwardRef(function MobileDateRangePi onChange, }; - const restProps = { - ...other, - minDate, - maxDate, - }; - - const { pickerProps, inputProps, wrapperProps } = usePickerState< - RangeInput, - DateRange - >(pickerStateProps, rangePickerValueManager); + const { pickerProps, inputProps, wrapperProps } = usePickerState( + pickerStateProps, + rangePickerValueManager, + ); const validationError = useDateRangeValidation(props); const DateInputProps = { ...inputProps, - ...restProps, + ...other, currentlySelectingRangeEnd, - inputFormat: passedInputFormat || utils.formats.keyboardDate, setCurrentlySelectingRangeEnd, - startText, - endText, - mask, validationError, ref, }; return ( open={wrapperProps.open} DateInputProps={DateInputProps} - calendars={calendars} currentlySelectingRangeEnd={currentlySelectingRangeEnd} setCurrentlySelectingRangeEnd={setCurrentlySelectingRangeEnd} - startText={startText} - endText={endText} {...pickerProps} - {...restProps} + {...other} /> ); diff --git a/packages/x-date-pickers/src/DatePicker/DatePicker.tsx b/packages/x-date-pickers/src/DatePicker/DatePicker.tsx index 649a1240e91b..e6f49965e263 100644 --- a/packages/x-date-pickers/src/DatePicker/DatePicker.tsx +++ b/packages/x-date-pickers/src/DatePicker/DatePicker.tsx @@ -38,8 +38,6 @@ export const DatePicker = React.forwardRef(function DatePicker( const props = useThemeProps({ props: inProps, name: 'MuiDatePicker' }); const { cancelText, - clearable, - clearText, desktopModeMediaQuery = '@media (pointer: fine)', DialogProps, okText, @@ -52,21 +50,21 @@ export const DatePicker = React.forwardRef(function DatePicker( const isDesktop = useMediaQuery(desktopModeMediaQuery); - return isDesktop ? ( - - ) : ( + if (isDesktop) { + return ( + + ); + } + + return ( ( export type DefaultizedProps = Props & { inputFormat: string }; export function useDatePickerDefaultizedProps>( - { - openTo = 'day', - views = ['year', 'day'], - minDate: minDateProp, - maxDate: maxDateProp, - ...other - }: Props, + props: Props, name: string, ): DefaultizedProps & Required, 'openTo' | 'views'>> { const utils = useUtils(); const defaultDates = useDefaultDates(); - const minDate = minDateProp ?? defaultDates.minDate; - const maxDate = maxDateProp ?? defaultDates.maxDate; // 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. - return useThemeProps({ - props: { - views, - openTo, - minDate, - maxDate, - ...getFormatAndMaskByViews(views, utils), - ...(other as Props), - }, + const themeProps = useThemeProps({ + props, name, }); + + const views = themeProps.views ?? ['year', 'day']; + + return { + openTo: 'day', + minDate: defaultDates.minDate, + maxDate: defaultDates.maxDate, + ...getFormatAndMaskByViews(views, utils), + ...themeProps, + views, + }; } diff --git a/packages/x-date-pickers/src/DateTimePicker/DateTimePicker.test.tsx b/packages/x-date-pickers/src/DateTimePicker/DateTimePicker.test.tsx index 944e161da591..edbe11d8462d 100644 --- a/packages/x-date-pickers/src/DateTimePicker/DateTimePicker.test.tsx +++ b/packages/x-date-pickers/src/DateTimePicker/DateTimePicker.test.tsx @@ -1,6 +1,8 @@ import * as React from 'react'; import TextField from '@mui/material/TextField'; import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker'; +import { screen } from '@mui/monorepo/test/utils'; +import { expect } from 'chai'; import { createPickerRenderer } from '../../../../test/utils/pickers-utils'; describe('', () => { @@ -16,4 +18,18 @@ describe('', () => { />, ); }); + + it('prop `showToolbar` – renders toolbar in DateTimePicker', () => { + render( + {}} + value={null} + renderInput={(params) => } + />, + ); + + expect(screen.getByMuiTest('picker-toolbar')).toBeVisible(); + }); }); diff --git a/packages/x-date-pickers/src/DateTimePicker/DateTimePicker.tsx b/packages/x-date-pickers/src/DateTimePicker/DateTimePicker.tsx index c04507665437..95e3eb739712 100644 --- a/packages/x-date-pickers/src/DateTimePicker/DateTimePicker.tsx +++ b/packages/x-date-pickers/src/DateTimePicker/DateTimePicker.tsx @@ -38,8 +38,6 @@ const DateTimePicker = React.forwardRef(function DateTimePicker( const props = useThemeProps({ props: inProps, name: 'MuiDateTimePicker' }); const { cancelText, - clearable, - clearText, desktopModeMediaQuery = '@media (pointer: fine)', DialogProps, okText, @@ -52,19 +50,21 @@ const DateTimePicker = React.forwardRef(function DateTimePicker( const isDesktop = useMediaQuery(desktopModeMediaQuery); - return isDesktop ? ( - - ) : ( + if (isDesktop) { + return ( + + ); + } + + return ( , >( - { - ampm, - inputFormat, - maxDate: maxDateProp, - maxDateTime, - maxTime, - minDate: minDateProp, - minDateTime, - minTime, - openTo = 'day', - orientation = 'portrait', - views = ['year', 'day', 'hours', 'minutes'], - ...other - }: Props, + props: Props, name: string, ): DefaultizedProps & Required, 'openTo' | 'views'>> { + // 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, + }); + const utils = useUtils(); const defaultDates = useDefaultDates(); - const minDate = minDateProp ?? defaultDates.minDate; - const maxDate = maxDateProp ?? defaultDates.maxDate; - const willUseAmPm = ampm ?? utils.is12HourCycleInCurrentLocale(); + const ampm = themeProps.ampm ?? utils.is12HourCycleInCurrentLocale(); - if (orientation !== 'portrait') { + if (themeProps.orientation != null && themeProps.orientation !== 'portrait') { throw new Error('We are not supporting custom orientation for DateTimePicker yet :('); } - return useThemeProps({ - props: { - openTo, - views, - ampm: willUseAmPm, - ampmInClock: true, - orientation, - showToolbar: true, - allowSameDateSelection: true, - minDate: minDateTime ?? minDate, - minTime: minDateTime ?? minTime, - maxDate: maxDateTime ?? maxDate, - maxTime: maxDateTime ?? maxTime, - disableIgnoringDatePartForTimeValidation: Boolean(minDateTime || maxDateTime), - acceptRegex: willUseAmPm ? /[\dap]/gi : /\d/gi, - mask: '__/__/____ __:__', - disableMaskedInput: willUseAmPm, - inputFormat: pick12hOr24hFormat(inputFormat, willUseAmPm, { - localized: utils.formats.keyboardDateTime, - '12h': utils.formats.keyboardDateTime12h, - '24h': utils.formats.keyboardDateTime24h, - }), - ...(other as Props), - }, - name, - }); + return { + ampm, + orientation: 'portrait', + openTo: 'day', + views: ['year', 'day', 'hours', 'minutes'], + ampmInClock: true, + showToolbar: false, + allowSameDateSelection: true, + mask: '__/__/____ __:__', + acceptRegex: ampm ? /[\dap]/gi : /\d/gi, + disableMaskedInput: ampm, + inputFormat: ampm ? utils.formats.keyboardDateTime12h : utils.formats.keyboardDateTime24h, + disableIgnoringDatePartForTimeValidation: Boolean( + themeProps.minDateTime || themeProps.maxDateTime, + ), + ...themeProps, + minDate: themeProps.minDateTime ?? themeProps.minDate ?? defaultDates.minDate, + maxDate: themeProps.maxDateTime ?? themeProps.maxDate ?? defaultDates.maxDate, + minTime: themeProps.minDateTime ?? themeProps.minTime, + maxTime: themeProps.maxDateTime ?? themeProps.maxTime, + }; } diff --git a/packages/x-date-pickers/src/DesktopDateTimePicker/DesktopDateTimePicker.test.tsx b/packages/x-date-pickers/src/DesktopDateTimePicker/DesktopDateTimePicker.test.tsx index 289cf6a820b5..4e6508a51249 100644 --- a/packages/x-date-pickers/src/DesktopDateTimePicker/DesktopDateTimePicker.test.tsx +++ b/packages/x-date-pickers/src/DesktopDateTimePicker/DesktopDateTimePicker.test.tsx @@ -89,6 +89,7 @@ describe('', () => { renderInput={(params) => } value={null} open + showToolbar onClose={handleClose} />, ); diff --git a/packages/x-date-pickers/src/MobileDateTimePicker/MobileDateTimePicker.test.tsx b/packages/x-date-pickers/src/MobileDateTimePicker/MobileDateTimePicker.test.tsx index 33c9535f0515..0fccaf665c51 100644 --- a/packages/x-date-pickers/src/MobileDateTimePicker/MobileDateTimePicker.test.tsx +++ b/packages/x-date-pickers/src/MobileDateTimePicker/MobileDateTimePicker.test.tsx @@ -51,6 +51,7 @@ describe('', () => { setDate(newDate); onChangeMock(newDate); }} + showToolbar renderInput={(params) => } /> ); @@ -128,6 +129,7 @@ describe('', () => { renderInput={(params) => } onChange={() => {}} open + showToolbar value={adapterToUse.date('2021-11-20T10:01:22.000')} />, ); @@ -139,12 +141,27 @@ describe('', () => { expect(screen.getByMuiTest('datetimepicker-toolbar-day')).to.have.text('Nov 20'); }); + it('prop `showToolbar` – renders toolbar in MobileDateTimePicker', () => { + render( + {}} + value={adapterToUse.date('2021-11-20T10:01:22.000')} + renderInput={(params) => } + />, + ); + + expect(screen.getByMuiTest('picker-toolbar')).toBeVisible(); + }); + it('can render seconds on view', () => { render( } onChange={() => {}} open + showToolbar views={['seconds']} value={adapterToUse.date('2021-11-20T10:01:22.000')} />, diff --git a/packages/x-date-pickers/src/TimePicker/TimePicker.tsx b/packages/x-date-pickers/src/TimePicker/TimePicker.tsx index f3a54761cb2b..0e8a16ac7df8 100644 --- a/packages/x-date-pickers/src/TimePicker/TimePicker.tsx +++ b/packages/x-date-pickers/src/TimePicker/TimePicker.tsx @@ -38,8 +38,6 @@ export const TimePicker = React.forwardRef(function TimePicker( const props = useThemeProps({ props: inProps, name: 'MuiTimePicker' }); const { cancelText, - clearable, - clearText, desktopModeMediaQuery = '@media (pointer: fine)', DialogProps, okText, @@ -67,8 +65,6 @@ export const TimePicker = React.forwardRef(function TimePicker( (value: ParseableDate, utils: MuiPick type DefaultizedProps = Props & { inputFormat: string }; export function useTimePickerDefaultizedProps>( - { - ampm, - components, - inputFormat, - openTo = 'hours', - views = ['hours', 'minutes'], - ...other - }: Props, + props: Props, name: string, ): DefaultizedProps & Required, 'openTo' | 'views'>> { + // 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 }); + const utils = useUtils(); - const willUseAmPm = ampm ?? utils.is12HourCycleInCurrentLocale(); + const ampm = themeProps.ampm ?? utils.is12HourCycleInCurrentLocale(); - return useThemeProps({ - props: { - views, - openTo, - ampm: willUseAmPm, - acceptRegex: willUseAmPm ? /[\dapAP]/gi : /\d/gi, - mask: '__:__', - disableMaskedInput: willUseAmPm, - getOpenDialogAriaText: getTextFieldAriaText, - components: { - OpenPickerIcon: Clock, - ...components, - }, - inputFormat: pick12hOr24hFormat(inputFormat, willUseAmPm, { - localized: utils.formats.fullTime, - '12h': utils.formats.fullTime12h, - '24h': utils.formats.fullTime24h, - }), - ...(other as Props), + return { + ampm, + openTo: 'hours', + views: ['hours', 'minutes'], + acceptRegex: ampm ? /[\dapAP]/gi : /\d/gi, + mask: '__:__', + disableMaskedInput: ampm, + getOpenDialogAriaText: getTextFieldAriaText, + inputFormat: ampm ? utils.formats.fullTime12h : utils.formats.fullTime24h, + ...themeProps, + components: { + OpenPickerIcon: Clock, + ...themeProps.components, }, - name, - }); + }; } diff --git a/packages/x-date-pickers/src/internals/components/wrappers/ResponsiveWrapper.tsx b/packages/x-date-pickers/src/internals/components/wrappers/ResponsiveWrapper.tsx deleted file mode 100644 index ad94a36805cd..000000000000 --- a/packages/x-date-pickers/src/internals/components/wrappers/ResponsiveWrapper.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import * as React from 'react'; -import useMediaQuery from '@mui/material/useMediaQuery'; -import { MobileWrapper, MobileWrapperProps } from './MobileWrapper'; -import { DesktopWrapperProps } from './DesktopWrapper'; -import { DesktopTooltipWrapper } from './DesktopTooltipWrapper'; -import { DateInputPropsLike, PrivateWrapperProps } from './WrapperProps'; - -export interface ResponsiveWrapperProps extends MobileWrapperProps, DesktopWrapperProps { - /** - * 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') - */ - desktopModeMediaQuery?: string; -} - -interface InternalResponsiveWrapperProps extends ResponsiveWrapperProps, PrivateWrapperProps { - DateInputProps: DateInputPropsLike & { ref?: React.Ref }; - KeyboardDateInputComponent: React.JSXElementConstructor< - DateInputPropsLike & { ref?: React.Ref } - >; - PureDateInputComponent: React.JSXElementConstructor; -} - -export function ResponsiveTooltipWrapper(props: InternalResponsiveWrapperProps) { - const { - cancelText, - clearable, - clearText, - DateInputProps, - desktopModeMediaQuery = '@media (pointer: fine)', - DialogProps, - KeyboardDateInputComponent, - okText, - PopperProps, - PureDateInputComponent, - showTodayButton, - todayText, - TransitionComponent, - ...other - } = props; - const isDesktop = useMediaQuery(desktopModeMediaQuery); - - return isDesktop ? ( - - ) : ( - - ); -} diff --git a/packages/x-date-pickers/src/internals/index.ts b/packages/x-date-pickers/src/internals/index.ts index 0020c77b6c14..b21a3c24b3cb 100644 --- a/packages/x-date-pickers/src/internals/index.ts +++ b/packages/x-date-pickers/src/internals/index.ts @@ -14,8 +14,6 @@ export type { ExportedDateInputProps, MuiTextFieldProps, } from './components/PureDateInput'; -export { ResponsiveTooltipWrapper } from './components/wrappers/ResponsiveWrapper'; -export type { ResponsiveWrapperProps } from './components/wrappers/ResponsiveWrapper'; export type { DateInputPropsLike } from './components/wrappers/WrapperProps'; export { WrapperVariantContext } from './components/wrappers/WrapperVariantContext'; export type { WrapperVariant } from './components/wrappers/WrapperVariantContext'; diff --git a/packages/x-date-pickers/src/internals/utils/text-field-helper.test.ts b/packages/x-date-pickers/src/internals/utils/text-field-helper.test.ts index 0aa0565aa4ed..69071c0338a2 100644 --- a/packages/x-date-pickers/src/internals/utils/text-field-helper.test.ts +++ b/packages/x-date-pickers/src/internals/utils/text-field-helper.test.ts @@ -1,10 +1,6 @@ import { expect } from 'chai'; import { adapterToUse } from '../../../../../test/utils/pickers-utils'; -import { - maskedDateFormatter, - pick12hOr24hFormat, - checkMaskIsValidForCurrentFormat, -} from './text-field-helper'; +import { maskedDateFormatter, checkMaskIsValidForCurrentFormat } from './text-field-helper'; describe('text-field-helper', () => { it('maskedDateFormatter for date', () => { @@ -28,22 +24,6 @@ describe('text-field-helper', () => { expect(formatterFn('10:00 A')).to.equal('10:00 AM'); }); - it('pick12hOr24hFormat', () => { - expect( - pick12hOr24hFormat(undefined, true, { localized: 'T', '12h': 'hh:mm a', '24h': 'HH:mm' }), - ).to.equal('hh:mm a'); - expect( - pick12hOr24hFormat(undefined, undefined, { - localized: 'T', - '12h': 'hh:mm a', - '24h': 'HH:mm', - }), - ).to.equal('T'); - expect( - pick12hOr24hFormat(undefined, false, { localized: 'T', '12h': 'hh:mm a', '24h': 'HH:mm' }), - ).to.equal('HH:mm'); - }); - [ { mask: '__.__.____', format: adapterToUse.formats.keyboardDate, isValid: false }, { mask: '__/__/____', format: adapterToUse.formats.keyboardDate, isValid: true }, diff --git a/packages/x-date-pickers/src/internals/utils/text-field-helper.ts b/packages/x-date-pickers/src/internals/utils/text-field-helper.ts index 189b7af22f19..6dbcc1d1f0cb 100644 --- a/packages/x-date-pickers/src/internals/utils/text-field-helper.ts +++ b/packages/x-date-pickers/src/internals/utils/text-field-helper.ts @@ -36,22 +36,6 @@ export const getDisplayDate = ( : ''; }; -export function pick12hOr24hFormat( - userFormat: string | undefined, - ampm: boolean | undefined, - formats: { localized: string; '12h': string; '24h': string }, -) { - if (userFormat) { - return userFormat; - } - - if (typeof ampm === 'undefined') { - return formats.localized; - } - - return ampm ? formats['12h'] : formats['24h']; -} - const MASK_USER_INPUT_SYMBOL = '_'; const staticDateWith2DigitTokens = '2019-11-21T22:30:00.000'; const staticDateWith1DigitTokens = '2019-01-01T09:00:00.000';