Búsqueda de sitios web

javascript - Material UI DatePicker renderInput personalizado


Recientemente comencé a usar material-ui en mi proyecto de React + TypeScript. El código a continuación fue escrito basándose en el código de ejemplo del sitio web oficial.

import AdapterDateFns from '@mui/lab/AdapterDateFns';
import DatePicker from '@mui/lab/DatePicker';
import LocalizationProvider from '@mui/lab/LocalizationProvider';
import TextField from '@mui/material/TextField';
import React, { useState } from 'react';

const SampleDatePickerOriginal = () => {
  const [date, setDate] = useState<Date>(new Date());

  const onChangeHandler = (_date: Date | null, keyboardInputValue?: string) => {
    if (_date) {
      setDate(_date);
    }
  };
  return (
    <LocalizationProvider dateAdapter={AdapterDateFns}>
      <DatePicker
        onChange={onChangeHandler}
        value={date}
        renderInput={(params) => <TextField {...params} />}
      />
    </LocalizationProvider>
  );
};

export default SampleDatePickerOriginal;

Quería usar un elemento input en lugar de TextField en este código. Así que me referí al problema de material-ui (https://github.com/mui/material-ui-pickers/issues/1751) y escribí el código a continuación.

import AdapterDateFns from '@mui/lab/AdapterDateFns';
import DatePicker from '@mui/lab/DatePicker';
import LocalizationProvider from '@mui/lab/LocalizationProvider';
import React, { useState } from 'react';

const SampleDatePickerCustom = () => {
  const [date, setDate] = useState<Date>(new Date());
  const [isOpen, setIsOpen] = useState<boolean>(false);

  const onChangeHandler = (_date: Date | null, keyboardInputValue?: string) => {
    if(_date){
      setDate(_date);
    }
  };

  return (
    <LocalizationProvider dateAdapter={AdapterDateFns}>
      <DatePicker 
        onChange={onChangeHandler}
        onClose={() => {setIsOpen(false);}}
        open={isOpen}
        value={date} 
        renderInput={({
          ref,
          inputProps,
          disabled,
          onChange,
          value,
          ...other
        }) => (
          <div ref={ref}>
            <input
              style={{ display: 'none' }}
              value={date.toISOString()}
              onChange={onChange}
              disabled={disabled}
              {...inputProps}
            />
            <button onClick={() => {setIsOpen(!isOpen);}}>
              OPEN
            </button>
          </div>)} />
    </LocalizationProvider>
  );
};

export default SampleDatePickerCustom;

Este código funciona bien, pero el calendario que se abre está en la esquina superior izquierda. ¿Cómo puedo hacer que aparezca justo debajo del botón como cuando uso TextField?

Respuesta #1

Me quedé atascado en el mismo problema aquí hoy y afortunadamente ahora lo hice funcionar para mí.

La idea es que tienes que pasar un elemento ancla al componente Popper de Material UI, que básicamente es el contenedor de tu Popup del Selector de Fecha. Eso significa que tienes que crear un objeto ref para tu entrada personalizada y pasar eso al objeto popper como anchorEl. Si revisas la documentación de MaterialUI X para la API del Selector de Fecha, puedes verlo en la sección de Props como PropperProps.

Tu código entonces se vería algo así:

import React, { useState, useRef } from 'react';

const SampleDatePickerCustom = () => {
  const [date, setDate] = useState<Date>(new Date());
  const [isOpen, setIsOpen] = useState<boolean>(false);
  const customInputRef = useRef();

  const onChangeHandler = (_date: Date | null, keyboardInputValue?: string) => {
    if(_date){
      setDate(_date);
    }
  };

  return (
    <LocalizationProvider dateAdapter={AdapterDateFns}>
      <DatePicker 
        onChange={onChangeHandler}
        onClose={() => {setIsOpen(false);}}
        open={isOpen}
        value={date} 
        PopperProps={{ anchorEl: customInputRef.current }}
        renderInput={({
          ref,
          inputProps,
          disabled,
          onChange,
          value,
          ...other
        }) => (
          <div ref={ref}>
            <input
              style={{ display: 'none' }}
              value={date.toISOString()}
              onChange={onChange}
              disabled={disabled}
              ref={customInputRef}
              {...inputProps}
            />
            <button onClick={() => {setIsOpen(!isOpen);}}>
              OPEN
            </button>
          </div>)} />
    </LocalizationProvider>
  );
};

Las únicas cosas que añadí son el objeto customInputRef, que se asigna como una referencia a tu input, y el objeto PropperProps en tu Datepicker, que solo tiene una propiedad, el elemento customInputRef.current. Eso debería hacer que el Popper se ancle en el elemento de input personalizado proporcionado. No escribí los tipos de las adiciones, ya que no soy el mejor en TS, pero espero que sea suficiente para entender cómo funciona :)

Respuesta #2

Pude solucionarlo usando TextField pero ocultándolo con estilos... <TextField style={{ opacity: 0, width: 0, height: 0 }} {...params} />

<LocalizationProvider dateAdapter={AdapterDateFns}>
   <DatePicker
    open={calendarOpen}
    onClose={() => setCalendarOpen(false)}
    value={date}
    onChange={(newValue) => setDate(newValue)}
    renderInput={(params) => (
      <div>
      {/* HACK: this next line was needed to get the calendar to render in the right position */}
        <TextField style={{ opacity: 0, width: 0, height: 0 }} {...params} /> 
        <Button
          className="p-1"
          variant="outlined"
          onClick={() => setCalendarOpen((calendarOpen) => !calendarOpen)}>
          <CalendarIcon />
        </Button>
      </div>
    )}
  />
</LocalizationProvider>

Artículos relacionados