mui / mui-x

MUI X: Build complex and data-rich applications using a growing list of advanced React components, like the Data Grid, Date and Time Pickers, Charts, and more!
https://mui.com/x/
4.07k stars 1.26k forks source link

[pickers] Improve the DX of custom field with custom editing behaviors #14496

Open flaviendelangle opened 1 week ago

flaviendelangle commented 1 week ago

We currently have a several examples on how to create fields with custom UIs (using browser primitives, using Joy UI, ...) and we have 2 demos on how to create fields with custom behaviors (one with a Button, one with an Autocomplete).

The goal of this issue is to improve the 2nd type of demo and to create a few new ones.

Here are the planned work:

Enhancements

Missing doc sections

Recipes for custom behavior field

Search keywords:

flaviendelangle commented 1 week ago

WIP

Exploration of a Base UI DX (for built in editing behavior)

The content below is purely theoretical for now and aims at discussing what the DX could look like to build custom field in 12-18 months and how it could help us decrease the complexity of our codebase. It takes for granted that we don't support @mui/material/TextField anymore and aims at solving the following problem:

  1. People should be able to use the built-in fields as standalone components (import { DateField } from '@mui/x-date-pickers/DateField')
  2. People should be able to easily build fields with a custom UI while keeping the same UX as the built-in fields (use useField behind the scene)
  3. People should be able to easily build a custom UI with a custom UX (e.g: use an AutoComplete, use a maksed-based approach, ...)

New PickersField component

This new component would follow the DX of Base UI and would be the cornerstone to build a field that uses our editing behaviors. It would be used to create DateField, TimeField, SingleInputDateRangeField, etc... and would also be used for people that want to create a field with a custom UI while using our editing behaviors (see this demo to have the current DX).

In short, it would replace both PickersTextField and the useDateField (and equivalent) hook.

Built-in styled field

Here is a simplified version of what the equivalent of the current DateField component could look like:

import * as PickersField from '@mui/x-date-pickers/base'
import * as Field from '@base_ui/react/Field';

// This replaces the current `useDateField` hook 
// and allow `PickersField.Root` to directly call `useField` with the right params
const dateFieldController: PickersField.Controller = {
  valueManager: singleItemValueManager,
  fieldValueManager: singleItemFieldValueManager,
  valueType: 'date',
  validate: validateDate,
}

const DateField = (props: DateFieldProps) => {
  const { label } = props
  return (
    <PickersField.Root controller={dateFieldController}>
      <Field.Label>{label}</Field.Label>
      <PickersField.SectionsContainer>
        {({ element }) => (
          <PickersField.Section element={element} />
        )}
      </PickersField.SectionsContainer>
      <Field.Error show="customError />
    </PickersField.Root>
  )
}

Custom field with same behavior

flaviendelangle commented 5 days ago

Exploration of the hook API (for custom editing behavior)

Very basic example (read-only textfield)

import TextField from '@mui/material/TextField';
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
import { useValidation, validateDate } from '@mui/x-date-pickers/validation';
import { useSplitFieldProps, useFieldPlaceholder } from '@mui/x-date-pickers/hooks';

function ReadonlyDateField(props) {
  const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date');
  const { value, timezone, format, onOpen } = internalProps;

  const placeholder = useFieldPlaceholder(internalProps);
  const { hasValidationError } = useValidation({
    validator: validateDate,
    value,
    timezone,
    props: internalProps,
  });

  return (
    <TextField
      {...forwardedProps}
      value={value == null ? '' : value.format(format)}
      placeholder={placeholder}
      // TODO: Once the pickers no longer return the `InputProps`, 
      // we will also have to add the icon manually here.
      // And we should migrate to slots in the doc when we can, to be future proof.
      InputProps={{ ...forwardedProps.InputProps, readOnly: true }}
      error={hasValidationError}
      onClick={onOpen}
    />
  );
}

function ReadonlyFieldDatePicker(props) {
  return (
    <DatePicker slots={{ ...props.slots, field: ReadonlyDateField }} {...props} />
  );
}