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

[pickers] Strictly type the props a picker passes to its field, and migrate all the custom field demos accordingly #15197

Merged
29 changes: 7 additions & 22 deletions docs/data/date-pickers/custom-field/BrowserV7Field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,14 @@ import useForkRef from '@mui/utils/useForkRef';
import { styled } from '@mui/material/styles';
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Use DatePickerFieldProps

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks immensely cleaner. 😱 💯

import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import { DatePicker, DatePickerProps } from '@mui/x-date-pickers/DatePicker';
import {
unstable_useDateField as useDateField,
UseDateFieldProps,
} from '@mui/x-date-pickers/DateField';
DatePicker,
DatePickerFieldProps,
DatePickerProps,
} from '@mui/x-date-pickers/DatePicker';
import { unstable_useDateField as useDateField } from '@mui/x-date-pickers/DateField';
import { useClearableField } from '@mui/x-date-pickers/hooks';
import {
BaseSingleInputPickersTextFieldProps,
BaseSingleInputFieldProps,
DateValidationError,
FieldSection,
PickerValidDate,
} from '@mui/x-date-pickers/models';
import { BaseSingleInputPickersTextFieldProps } from '@mui/x-date-pickers/models';
import { Unstable_PickersSectionList as PickersSectionList } from '@mui/x-date-pickers/PickersSectionList';

const BrowserFieldRoot = styled('div', { name: 'BrowserField', slot: 'Root' })({
Expand Down Expand Up @@ -104,18 +99,8 @@ const BrowserTextField = React.forwardRef(
},
);

interface BrowserDateFieldProps
extends UseDateFieldProps<true>,
BaseSingleInputFieldProps<
// This usage of PickerValidDate will go away with TIsRange
PickerValidDate | null,
FieldSection,
true,
DateValidationError
> {}

const BrowserDateField = React.forwardRef(
(props: BrowserDateFieldProps, ref: React.Ref<HTMLDivElement>) => {
(props: DatePickerFieldProps, ref: React.Ref<HTMLDivElement>) => {
const { slots, slotProps, ...textFieldProps } = props;

const fieldResponse = useDateField<true, typeof textFieldProps>(textFieldProps);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { styled } from '@mui/material/styles';
import Stack from '@mui/material/Stack';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';

import { DateRangePicker } from '@mui/x-date-pickers-pro/DateRangePicker';
import { unstable_useMultiInputDateRangeField as useMultiInputDateRangeField } from '@mui/x-date-pickers-pro/MultiInputDateRangeField';
import { Unstable_PickersSectionList as PickersSectionList } from '@mui/x-date-pickers/PickersSectionList';
Expand All @@ -25,8 +24,6 @@ const BrowserFieldContent = styled('div', { name: 'BrowserField', slot: 'Content
},
);

// This demo uses `BasePickersTextFieldProps` instead of `BaseMultiInputPickersTextFieldProps`,
// That way you can reuse the same `BrowserTextField` for all your pickers, range or not.
const BrowserTextField = React.forwardRef((props, ref) => {
const {
// Should be ignored
Expand Down Expand Up @@ -84,12 +81,10 @@ const BrowserMultiInputDateRangeField = React.forwardRef((props, ref) => {
const {
slotProps,
value,
defaultValue,
format,
onChange,
readOnly,
disabled,
onError,
shouldDisableDate,
minDate,
maxDate,
Expand Down Expand Up @@ -117,12 +112,10 @@ const BrowserMultiInputDateRangeField = React.forwardRef((props, ref) => {
const fieldResponse = useMultiInputDateRangeField({
sharedProps: {
value,
defaultValue,
format,
onChange,
readOnly,
disabled,
onError,
shouldDisableDate,
minDate,
maxDate,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,17 @@ import { styled } from '@mui/material/styles';
import Stack from '@mui/material/Stack';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Use DateRangePickerFieldProps (with a small adaptation to work with multi input range field, I can create a DateRangePickerMultiInputFieldProps if you want that in the lib, my assumption that since we migrate away from multi input, it's not needed).
  • Replace BasePickersTextFieldProps with BaseMultiInputPickersTextFieldProps (BaseMultiInputPickersTextFieldProps and BaseSingleInputPickersTextFieldProps are now incompatible so BasePickersTextFieldProps can't exist, and since we are migrating away from multi input I think we don't have to support this abstraction)
  • Remove defaultValue and onError props which are never passed by the picker to the field so a field built to be used inside a picker doesn't have to support them

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with the assumptions and the resulting compromises. 👍

import { PickerValidDate } from '@mui/x-date-pickers/models';
import {
DateRangePicker,
DateRangePickerFieldProps,
DateRangePickerProps,
} from '@mui/x-date-pickers-pro/DateRangePicker';
import { unstable_useMultiInputDateRangeField as useMultiInputDateRangeField } from '@mui/x-date-pickers-pro/MultiInputDateRangeField';
import { Unstable_PickersSectionList as PickersSectionList } from '@mui/x-date-pickers/PickersSectionList';
import {
RangeFieldSection,
BaseMultiInputFieldProps,
BasePickersTextFieldProps,
MultiInputFieldSlotTextFieldProps,
DateRangeValidationError,
DateRange,
UseDateRangeFieldProps,
MultiInputFieldRefs,
BaseMultiInputPickersTextFieldProps,
} from '@mui/x-date-pickers-pro/models';

const BrowserFieldRoot = styled('div', { name: 'BrowserField', slot: 'Root' })({
Expand All @@ -38,14 +34,12 @@ const BrowserFieldContent = styled('div', { name: 'BrowserField', slot: 'Content
);

interface BrowserTextFieldProps
extends BasePickersTextFieldProps<true>,
extends BaseMultiInputPickersTextFieldProps<true>,
Omit<
React.HTMLAttributes<HTMLDivElement>,
keyof BasePickersTextFieldProps<true>
keyof BaseMultiInputPickersTextFieldProps<true>
> {}

// This demo uses `BasePickersTextFieldProps` instead of `BaseMultiInputPickersTextFieldProps`,
// That way you can reuse the same `BrowserTextField` for all your pickers, range or not.
const BrowserTextField = React.forwardRef(
(props: BrowserTextFieldProps, ref: React.Ref<unknown>) => {
const {
Expand Down Expand Up @@ -108,14 +102,11 @@ const BrowserTextField = React.forwardRef(
);

interface BrowserMultiInputDateRangeFieldProps
extends UseDateRangeFieldProps<true>,
BaseMultiInputFieldProps<
// This usage of PickerValidDate will go away with TIsRange
DateRange<PickerValidDate>,
RangeFieldSection,
true,
DateRangeValidationError
> {}
extends Omit<
DateRangePickerFieldProps,
'unstableFieldRef' | 'clearable' | 'onClear'
>,
MultiInputFieldRefs {}

type BrowserMultiInputDateRangeFieldComponent = ((
props: BrowserMultiInputDateRangeFieldProps & React.RefAttributes<HTMLDivElement>,
Expand All @@ -126,12 +117,10 @@ const BrowserMultiInputDateRangeField = React.forwardRef(
const {
slotProps,
value,
defaultValue,
format,
onChange,
readOnly,
disabled,
onError,
shouldDisableDate,
minDate,
maxDate,
Expand Down Expand Up @@ -162,12 +151,10 @@ const BrowserMultiInputDateRangeField = React.forwardRef(
>({
sharedProps: {
value,
defaultValue,
format,
onChange,
readOnly,
disabled,
onError,
shouldDisableDate,
minDate,
maxDate,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import { DateRangePicker } from '@mui/x-date-pickers-pro/DateRangePicker';
import { unstable_useSingleInputDateRangeField as useSingleInputDateRangeField } from '@mui/x-date-pickers-pro/SingleInputDateRangeField';
import { useClearableField } from '@mui/x-date-pickers/hooks';
import { useClearableField, usePickerContext } from '@mui/x-date-pickers/hooks';
import { Unstable_PickersSectionList as PickersSectionList } from '@mui/x-date-pickers/PickersSectionList';

const BrowserFieldRoot = styled('div', { name: 'BrowserField', slot: 'Root' })({
Expand Down Expand Up @@ -84,7 +84,16 @@ const BrowserTextField = React.forwardRef((props, ref) => {
});

const BrowserSingleInputDateRangeField = React.forwardRef((props, ref) => {
const { slots, slotProps, onAdornmentClick, ...other } = props;
const { slots, slotProps, ...other } = props;

const pickerContext = usePickerContext();
const handleTogglePicker = (event) => {
if (pickerContext.open) {
pickerContext.onClose(event);
} else {
pickerContext.onOpen(event);
}
};

const textFieldProps = useSlotProps({
elementType: 'input',
Expand All @@ -97,7 +106,7 @@ const BrowserSingleInputDateRangeField = React.forwardRef((props, ref) => {
...textFieldProps.InputProps,
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={onAdornmentClick}>
<IconButton onClick={handleTogglePicker}>
<DateRangeIcon />
</IconButton>
</InputAdornment>
Expand Down Expand Up @@ -127,29 +136,11 @@ const BrowserSingleInputDateRangeField = React.forwardRef((props, ref) => {
BrowserSingleInputDateRangeField.fieldType = 'single-input';

const BrowserSingleInputDateRangePicker = React.forwardRef((props, ref) => {
const [isOpen, setIsOpen] = React.useState(false);

const toggleOpen = () => setIsOpen((currentOpen) => !currentOpen);

const handleOpen = () => setIsOpen(true);

const handleClose = () => setIsOpen(false);

return (
<DateRangePicker
ref={ref}
{...props}
open={isOpen}
onClose={handleClose}
onOpen={handleOpen}
slots={{ ...props.slots, field: BrowserSingleInputDateRangeField }}
slotProps={{
...props.slotProps,
field: {
onAdornmentClick: toggleOpen,
...props.slotProps?.field,
},
}}
/>
);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,14 @@ import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Use DateRangePickerFieldProps
  • Replace BasePickersTextFieldProps with BaseSingleInputPickersTextFieldProps (BaseMultiInputPickersTextFieldProps and BaseSingleInputPickersTextFieldProps are now incompatible so BasePickersTextFieldProps can't exist, and since we are migrating away from multi input I think we don't have to support this abstraction)
  • Use usePickersContext to open the picker instead of passing a custom prop using slotProps

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes look epic and way cleaner without the need to control nested slotProps. 🎉

import {
DateRangePicker,
DateRangePickerFieldProps,
DateRangePickerProps,
} from '@mui/x-date-pickers-pro/DateRangePicker';
import {
unstable_useSingleInputDateRangeField as useSingleInputDateRangeField,
UseSingleInputDateRangeFieldProps,
} from '@mui/x-date-pickers-pro/SingleInputDateRangeField';
import { useClearableField } from '@mui/x-date-pickers/hooks';
import { unstable_useSingleInputDateRangeField as useSingleInputDateRangeField } from '@mui/x-date-pickers-pro/SingleInputDateRangeField';
import { useClearableField, usePickerContext } from '@mui/x-date-pickers/hooks';
import { Unstable_PickersSectionList as PickersSectionList } from '@mui/x-date-pickers/PickersSectionList';
import {
BasePickersTextFieldProps,
DateRangeValidationError,
RangeFieldSection,
DateRange,
FieldType,
} from '@mui/x-date-pickers-pro/models';
import {
BaseSingleInputFieldProps,
PickerValidDate,
} from '@mui/x-date-pickers/models';
import { FieldType } from '@mui/x-date-pickers-pro/models';
import { BaseSingleInputPickersTextFieldProps } from '@mui/x-date-pickers/models';

const BrowserFieldRoot = styled('div', { name: 'BrowserField', slot: 'Root' })({
display: 'flex',
Expand All @@ -48,10 +37,10 @@ const BrowserFieldContent = styled('div', { name: 'BrowserField', slot: 'Content
);

interface BrowserTextFieldProps
extends BasePickersTextFieldProps<true>,
extends BaseSingleInputPickersTextFieldProps<true>,
Omit<
React.HTMLAttributes<HTMLDivElement>,
keyof BasePickersTextFieldProps<true>
keyof BaseSingleInputPickersTextFieldProps<true>
> {}

const BrowserTextField = React.forwardRef(
Expand Down Expand Up @@ -115,25 +104,24 @@ const BrowserTextField = React.forwardRef(
},
);

interface BrowserSingleInputDateRangeFieldProps
extends UseSingleInputDateRangeFieldProps<true>,
BaseSingleInputFieldProps<
// This usage of PickerValidDate will go away with TIsRange
DateRange<PickerValidDate>,
RangeFieldSection,
true,
DateRangeValidationError
> {
onAdornmentClick?: () => void;
}
interface BrowserSingleInputDateRangeFieldProps extends DateRangePickerFieldProps {}

type BrowserSingleInputDateRangeFieldComponent = ((
props: BrowserSingleInputDateRangeFieldProps & React.RefAttributes<HTMLDivElement>,
) => React.JSX.Element) & { fieldType?: FieldType };

const BrowserSingleInputDateRangeField = React.forwardRef(
(props: BrowserSingleInputDateRangeFieldProps, ref: React.Ref<HTMLDivElement>) => {
const { slots, slotProps, onAdornmentClick, ...other } = props;
const { slots, slotProps, ...other } = props;

const pickerContext = usePickerContext();
const handleTogglePicker = (event: React.UIEvent) => {
if (pickerContext.open) {
pickerContext.onClose(event);
} else {
pickerContext.onOpen(event);
}
};

const textFieldProps: typeof props = useSlotProps({
elementType: 'input',
Expand All @@ -146,7 +134,7 @@ const BrowserSingleInputDateRangeField = React.forwardRef(
...textFieldProps.InputProps,
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={onAdornmentClick}>
<IconButton onClick={handleTogglePicker}>
<DateRangeIcon />
</IconButton>
</InputAdornment>
Expand Down Expand Up @@ -180,29 +168,11 @@ BrowserSingleInputDateRangeField.fieldType = 'single-input';

const BrowserSingleInputDateRangePicker = React.forwardRef(
(props: DateRangePickerProps, ref: React.Ref<HTMLDivElement>) => {
const [isOpen, setIsOpen] = React.useState(false);

const toggleOpen = () => setIsOpen((currentOpen) => !currentOpen);

const handleOpen = () => setIsOpen(true);

const handleClose = () => setIsOpen(false);

return (
<DateRangePicker
ref={ref}
{...props}
open={isOpen}
onClose={handleClose}
onOpen={handleOpen}
slots={{ ...props.slots, field: BrowserSingleInputDateRangeField }}
slotProps={{
...props.slotProps,
field: {
onAdornmentClick: toggleOpen,
...props.slotProps?.field,
} as any,
}}
/>
);
},
Expand Down
Loading