Date Picker
ProntoCalendário visual em popover para selecionar data. Use junto com Date Input quando o usuário precisa explorar (audiência futura, agenda).
Usar quando
Seleção de data em contexto onde calendário ajuda (ver fim de semana, ver feriados, ver semana). Audiências, prazos, eventos.
Não usar quando
Data de nascimento (use Date Input — usuário sabe a data). Mobile (use input type=date nativo).
Variantes
Estados
Microinterações
| Microinteração | Disparada por | Comportamento | Timing |
|---|---|---|---|
| Open popover | click no input/calendário | Calendário desce com fade + translateY | 200ms cubic-bezier(0.32,0.72,0,1) |
| Hover dia | mouseenter num dia | Background brassTint + ring brass | 100ms ease |
| Mês anterior/próximo | click chevron | Slide horizontal (direção) + fade | 200ms ease |
| Today highlight | render | Hoje tem ring brass sem ser selected | instant |
| Selected day | click num dia | Background ink + texto branco | 150ms ease |
| Range selection | click num 2º dia (range mode) | Background brassTint entre as datas | 200ms |
| Disabled day | fora de min/max | Opacity 0.3 + cursor not-allowed | instant |
Acessibilidade
Acessibilidade — checklist
Teclado
| Tab | Foco no botão calendário |
| Enter / Space | Abre popover |
| ← / → | Move dia anterior/próximo |
| ↑ / ↓ | Move semana anterior/próxima |
| Page Up/Down | Mês anterior/próximo |
| Shift+Page Up/Down | Ano anterior/próximo |
| Enter | Seleciona dia em foco |
| Esc | Fecha sem selecionar |
ARIA esperado
- role="dialog" aria-modal="true" para o popover
- <table role="grid"><tr role="row"><td role="gridcell" aria-selected />
- aria-label nos chevrons ("Mês anterior", "Próximo mês")
- aria-current="date" no dia de hoje
Notas
- Em mobile, prefira <input type="date"> nativo (UX melhor).
- Calendário forense: marque feriados nacionais visualmente diferenciados.
- Range: 2 cliques (start + end). Hover entre cliques mostra preview.
- Sempre permita teclado completo — usuários power preferem teclar.
Código
'use client';
import { useState } from 'react';
import { Calendar } from 'lucide-react';
import { T, TYPE, SP, RADIUS, transition } from '@/lib/tokens';
interface DatePickerProps {
value: Date | null;
onChange: (d: Date | null) => void;
min?: Date;
max?: Date;
}
export function DatePicker({ value, onChange, min, max }: DatePickerProps) {
const [open, setOpen] = useState(false);
return (
<div style={{ position: 'relative' }}>
<button onClick={() => setOpen(!open)} style={{
display: 'flex', alignItems: 'center', gap: SP[2], height: 36,
padding: `0 ${SP[3]}px`, background: T.surface,
border: `1px solid ${open ? T.borderInk : T.border}`, borderRadius: RADIUS.md,
fontSize: TYPE.base, color: value ? T.ink : T.inkSubtle,
}}>
<Calendar size={14} color={T.inkMuted} />
{value ? value.toLocaleDateString('pt-BR') : 'Selecionar data'}
</button>
{open && <Calendar value={value} onChange={onChange} min={min} max={max} />}
</div>
);
}Regras
Faça
- ✓Calendário em PT-BR (semana começa segunda).
- ✓Marque feriados nacionais (calendário forense é crítico em jurídico).
- ✓Hoje sempre tem ring (mesmo se não selected).
- ✓Suporte teclado completo (← → ↑ ↓ PgUp PgDn Enter Esc).
- ✓Em mobile, use input nativo type=date.
- ✓Range: hover entre cliques mostra preview do range.
Não faça
- ✗Não use popover em mobile (use full-screen sheet ou nativo).
- ✗Não force seleção (deixe Esc fechar sem mudar).
- ✗Não esconda o input quando popover aberto.
- ✗Não dificulte navegar anos (ano-picker rápido se range > 5 anos).
- ✗Não use spinner numérico cru (UX ruim).
Tokens usados
| Token | Valor | Papel |
|---|---|---|
T.ink | #0A0A0A | background do dia selecionado |
T.surface | #FFFFFF | background do popover |
T.brassTint | rgba(164,124,43,0.08) | hover de dia |
T.brass | #A47C2B | ring de "hoje" |
T.shadowMd | 0 4px 14px ... | sombra do popover |
RADIUS.md | 12 | borderRadius |