Capítulo 33 de 108

Date Picker

Core Idea

Não é um componente próprio: é o padrão de composição entre Popover e Calendar para criar um seletor de data (ou data+hora) acionado por um input/botão.

Key Concepts

  • Composição obrigatória: Popover > PopoverTrigger (geralmente um Button via render) > PopoverContent > Calendar. Não existe <DatePicker> root.
  • render prop no PopoverTrigger: técnica do Base UI para injetar um elemento customizado (ex: Button) como trigger, preservando comportamento de acessibilidade do Popover.
  • data-empty: atributo customizado usado para estilizar o trigger diferente quando nenhuma data foi selecionada (data-[empty=true]:text-muted-foreground).
  • format (date-fns): usado para formatar a data exibida no trigger (ex: format(date, "PPP")).
  • open/onOpenChange controlado: necessário quando se quer fechar o Popover programaticamente ao selecionar uma data (setOpen(false) dentro de onSelect).
  • chrono-node: lib usada no exemplo de Natural Language Picker para parsear texto livre ("in 2 days") em objeto Date.

Code Examples

const [date, setDate] = React.useState<Date>()

<Popover>
  <PopoverTrigger
    render={
      <Button variant="outline" data-empty={!date}
        className="justify-start text-left font-normal data-[empty=true]:text-muted-foreground" />
    }
  >
    <CalendarIcon />
    {date ? format(date, "PPP") : <span>Pick a date</span>}
  </PopoverTrigger>
  <PopoverContent className="w-auto p-0">
    <Calendar mode="single" selected={date} onSelect={setDate} />
  </PopoverContent>
</Popover>
  • O que demonstra: date picker básico single-date com Popover + Calendar.
const [date, setDate] = React.useState<DateRange | undefined>({
  from: new Date(new Date().getFullYear(), 0, 20),
  to: addDays(new Date(new Date().getFullYear(), 0, 20), 20),
})

<Calendar mode="range" defaultMonth={date?.from} selected={date} onSelect={setDate} numberOfMonths={2} />
  • O que demonstra: range date picker exibindo "LLL dd, y - LLL dd, y" quando from e to estão preenchidos.
<Calendar
  mode="single"
  selected={date}
  defaultMonth={date}
  captionLayout="dropdown"
  onSelect={(date) => {
    setDate(date)
    setOpen(false)
  }}
/>
  • O que demonstra: fechar o Popover automaticamente ao selecionar (padrão "date of birth" com dropdown de mês/ano).
<InputGroupInput
  value={value}
  onChange={(e) => {
    const date = new Date(e.target.value)
    setValue(e.target.value)
    if (isValidDate(date)) { setDate(date); setMonth(date) }
  }}
  onKeyDown={(e) => {
    if (e.key === "ArrowDown") { e.preventDefault(); setOpen(true) }
  }}
/>
  • O que demonstra: date picker com input de texto editável sincronizado ao Calendar, abrindo o popover com a seta para baixo.

Reference Tables

Anti-patterns

  • Esperar um componente <DatePicker> pronto: não existe; a composição Popover+Calendar é a API pretendida.
  • Não sincronizar open/onOpenChange quando o UX exige fechar ao selecionar: sem controle explícito do estado open, o popover permanece aberto após onSelect.

Key Takeaways

  1. Date Picker é um padrão de composição, não um componente instalável isoladamente; instalar Popover + Calendar cobre os pré-requisitos.
  2. data-empty + data-[empty=true]: é o idiom para estilizar estado vazio do trigger sem lógica condicional de className.
  3. Fechamento automático do popover ao selecionar é responsabilidade do callback onSelect, não comportamento built-in.
  4. Variações cobertas: simples, range, date-of-birth (com dropdown), input editável, time picker (combinado com <Input type="time">), e natural language (via chrono-node).
  5. RTL requer passar dir tanto no Button/trigger quanto no PopoverContent e no Calendar, além de trocar locale do date-fns e do react-day-picker.

Connects To

  • Calendar: fornece a grade de seleção de datas usada dentro do PopoverContent.
  • Popover: fornece o mecanismo de abrir/fechar e posicionamento do calendário.
  • Field: usado para envolver o Popover com label e estrutura de formulário.
  • Input Group: usado nas variações de input editável e picker com ícone/addon.