# DatePicker

> Un campo que abre un calendario. Reemplaza a `<input type="date">`, cuyo calendario es del navegador y no se puede estilar.

```tsx
import { DatePicker } from "sebs7n-ui/date-picker"
```

## Ejemplos

### Básico

El campo es un botón: el `<Label htmlFor>` lo nombra y, al hacerle clic, abre el calendario.

```tsx
import { DatePicker } from "sebs7n-ui/date-picker"
import { Label } from "sebs7n-ui/label"
import { useId } from "react"

function Basico() {
  const id = useId()
  return (
    <div className="flex w-full max-w-xs flex-col gap-2">
      <Label htmlFor={id}>Vencimiento</Label>
      <DatePicker defaultValue={new Date(2026, 8, 27)} id={id} name="vencimiento" />
    </div>
  )
}
```

### Un rango

Se queda abierto después del desde y se cierra con el hasta.

```tsx
import { DatePicker } from "sebs7n-ui/date-picker"
import { DateRange } from "sebs7n-ui/lib/dates"
import { Label } from "sebs7n-ui/label"
import { useId, useState } from "react"

function Rango() {
  const id = useId()
  const [periodo, setPeriodo] = useState<DateRange>({ from: null, to: null })
  return (
    <div className="flex w-full max-w-xs flex-col gap-2">
      <Label htmlFor={id}>Período</Label>
      <DatePicker id={id} mode="range" onValueChange={setPeriodo} value={periodo} />
    </div>
  )
}
```

### Con Limpiar

`clearable` suma un pie para vaciar la fecha: un filtro o un campo opcional no puede ser un camino sin vuelta.

```tsx
import { DatePicker } from "sebs7n-ui/date-picker"
import { Label } from "sebs7n-ui/label"
import { useId } from "react"

function ConLimpiar() {
  const id = useId()
  return (
    <div className="flex w-full max-w-xs flex-col gap-2">
      <Label htmlFor={id}>Facturas desde</Label>
      <DatePicker clearable defaultValue={new Date(2026, 8, 1)} id={id} name="desde" />
    </div>
  )
}
```

### Tamaños, formato y límites

Las mismas tres alturas que `Input`. `format` es el de `Intl.DateTimeFormat`.

```tsx
import { DatePicker } from "sebs7n-ui/date-picker"

function TamanosYFormato() {
  return (
    <div className="flex w-full max-w-xs flex-col gap-3">
      <DatePicker aria-label="Fecha corta" defaultValue={new Date(2026, 8, 27)} format={{ dateStyle: "short" }} size="sm" />
      <DatePicker aria-label="Fecha larga" defaultValue={new Date(2026, 8, 27)} format={{ dateStyle: "long" }} />
      <DatePicker aria-label="Turno" max={new Date(2026, 9, 16)} min={new Date(2026, 8, 7)} size="lg" />
      <DatePicker aria-label="Sin turnos" disabled />
    </div>
  )
}
```

### Una fecha lejana

En el calendario, el título abre la grilla de meses y su año la de años. Escape vuelve a los días sin cerrar el panel.

```tsx
import { DatePicker } from "sebs7n-ui/date-picker"
import { Label } from "sebs7n-ui/label"
import { useId } from "react"

function FechaDeAlta() {
  const id = useId()
  return (
    <div className="flex w-full max-w-xs flex-col gap-2">
      <Label htmlFor={id}>Fecha de alta del cliente</Label>
      <DatePicker defaultValue={new Date(2006, 3, 12)} id={id} max={new Date(2026, 8, 29)} name="alta" />
    </div>
  )
}
```

### Idioma global

`LabelsProvider` con `dates`: idioma, semana y formato para `DatePicker`, `DateTimePicker` y `Calendar` de abajo (y `CalendarView`). La prop de cada uno le gana.

```tsx
import { Calendar } from "sebs7n-ui/calendar"
import { DatePicker } from "sebs7n-ui/date-picker"
import { DateTimePicker } from "sebs7n-ui/date-time-picker"
import { Label } from "sebs7n-ui/label"
import { LabelsProvider } from "sebs7n-ui/labels"
import { useState } from "react"

function IdiomaGlobal() {
  const [due, setDue] = useState<Date | null>(new Date(2026, 9, 15))
  return (
    <LabelsProvider value={{ dates: { locale: "en-US", weekStartsOn: 0, format: NUMERIC } }}>
      <div className="flex flex-col gap-4">
        <div className="flex flex-col gap-1.5">
          <Label htmlFor="due-global">Due date</Label>
          <DatePicker className="w-56" id="due-global" onValueChange={setDue} value={due} />
        </div>
        <DateTimePicker aria-label="Issued at" className="w-72" defaultValue={new Date(2026, 9, 1, 9, 30)} />
        <Calendar className="self-start" defaultMonth={new Date(2026, 9, 1)} />
      </div>
    </LabelsProvider>
  )
}
```

### En un Field

`FieldLabel`, ayuda y error se enganchan solos; con `required`, sin fecha u hora `Form` no envía y enfoca el campo.

```tsx
import { Button } from "sebs7n-ui/button"
import { DatePicker } from "sebs7n-ui/date-picker"
import { Field, FieldDescription, FieldError, FieldLabel } from "sebs7n-ui/field"
import { Form } from "sebs7n-ui/form"
import { TimePicker } from "sebs7n-ui/time-picker"
import { useState } from "react"

function EnField() {
  const [sent, setSent] = useState<string | null>(null)
  return (
    <Form className="flex w-full max-w-sm flex-col gap-4" onFormSubmit={(values) => setSent(JSON.stringify(values))}>
      <Field name="due">
        <FieldLabel>Vencimiento</FieldLabel>
        <DatePicker required />
        <FieldDescription>El día que vence la factura.</FieldDescription>
        <FieldError>Elegí una fecha.</FieldError>
      </Field>
      <Field name="sendAt">
        <FieldLabel>Hora de envío</FieldLabel>
        <TimePicker required />
        <FieldError>Elegí una hora.</FieldError>
      </Field>
      <Button className="self-start" type="submit">
        Programar
      </Button>
      {sent && <p className="text-footnote text-label-secondary">{sent}</p>}
    </Form>
  )
}
```

## Props

### DatePicker

| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
| `clearable` | `boolean` | — | Suma un «Limpiar» al pie del calendario cuando hay fecha elegida: para un filtro o un campo opcional, donde elegir una fecha no puede ser un camino sin vuelta. |
| `defaultValue` | `Date \| DateRange` | — | La fecha elegida al montar, sin controlar. |
| `format` | `Intl.DateTimeFormatOptions` | — | Cómo se escribe la fecha en el campo. Por defecto, `{ dateStyle: "medium" }`: «27 sept 2026». |
| `isDateDisabled` | `(date: Date) => boolean` | — | Apaga fechas sueltas: feriados, fines de semana, días sin turno. |
| `labels` | `Partial<DatePickerLabels & Labels["calendar"]>` | — | El texto del campo vacío y los nombres de los botones del calendario. |
| `locale` | `string` | — | El idioma de la fecha y del calendario, como lo entiende `Intl`. Por defecto, `es-AR`. |
| `max` | `Date` | — | La última fecha que se puede elegir. |
| `min` | `Date` | — | La primera fecha que se puede elegir. |
| `mode` | `"single" \| "range"` | — | `single` elige una fecha; `range`, un desde y un hasta. |
| `name` | `string` | — | El nombre con el que la fecha viaja en un formulario, como `2026-09-27`. En `range` salen dos campos: `nombre-desde` y `nombre-hasta`. |
| `numberOfMonths` | `number` | — | Cuántos meses muestra el calendario, uno al lado del otro. Por defecto, 1. |
| `onOpenChange` | `(open: boolean) => void` | — | Avisa cuando se abre o se cierra. |
| `onValueChange` | `((value: Date \| null) => void) \| ((value: DateRange) => void)` | — | Avisa la fecha elegida. |
| `open` | `boolean` | — | El calendario abierto o cerrado, controlado. |
| `popupClassName` | `string` | — | Clases del panel del calendario. |
| `required` | `boolean` | — | Sin fecha no se puede enviar: dentro de un `Form`, el campo queda inválido y `Form` lo enfoca. En `range`, hacen falta las dos puntas. |
| `size` | `"sm" \| "md" \| "lg"` | — | Las mismas tres alturas que `Input`, para que un formulario mixto quede alineado. |
| `value` | `Date \| DateRange` | — | La fecha elegida, controlada. `null` es ninguna. |
| `weekStartsOn` | `0 \| 1` | — | Con qué día arranca la semana: `1` lunes (el default), `0` domingo. |
| `disabled` | `boolean` | — | **Heredada de Base UI.** Apaga la interacción y lo marca con `data-disabled`, que es el atributo del que cuelgan los estilos de apagado. |
| `className` | `string` | — | **Heredada de Base UI.** Se fusiona con las clases del componente vía `cn()` (tailwind-merge): lo que pongas gana. |

## Teclado

| Tecla | Qué hace |
|---|---|
| Enter · Espacio | Abre el calendario, con el foco en la fecha elegida. |
| Escape | Cierra y devuelve el foco al campo. |
| ← → ↑ ↓ | Se mueve por los días. Ver `Calendar`. |
| Escape (meses y años) | Vuelve a los días; el segundo Escape cierra. |

## Accesibilidad

- El campo es un botón: necesita `aria-label`, `aria-labelledby` o un `<Label htmlFor>` apuntando a su `id`.
- La fecha se escribe con `Intl` en el idioma que se le pasa (`locale`), no en el del navegador.
- Dentro de un `Field` se registra como su control: `FieldLabel` lo nombra junto con la fecha («Vencimiento 27 sept 2026»), `FieldDescription` y `FieldError` lo describen y `Form` lo enfoca si queda inválido al enviar.

## Reglas de uso

- En una pantalla angosta (< 640 px) el calendario se abre en la hoja de abajo, como el `Popover`: a todo el ancho, arrastrable y con la X. Con `numberOfMonths={2}` los meses se apilan.
- Idioma y semana para toda la app: `<LabelsProvider value={{ dates: { locale: "en-US", weekStartsOn: 0 } }}>` (también `format` para el campo de `DatePicker`). Lo leen `Calendar`, `DatePicker`, `DateTimePicker` y `CalendarView`; la prop de cada uno gana.
- Para fechas que se eligen mirando un calendario: un turno, un vencimiento cercano, un período.
- Una fecha lejana no se recorre mes por mes: el título del calendario abre la grilla de meses, y su año la de años. Si lo normal es tipearla —un vencimiento que se copia de un papel—, va un `Input`.
- `mode="range"` elige desde y hasta en el mismo calendario, y se cierra recién con el segundo clic.
- Con `name` (o el `name` del `Field`), la fecha viaja en el formulario como `2026-09-27`. En rango salen dos campos: `nombre-desde` y `nombre-hasta`. En `onFormSubmit` de `Form` llega el ISO (en rango, `{ from, to }`).
- `required`: sin fecha (en rango, sin las dos puntas) `Form` no envía, marca el campo inválido y lo enfoca.
- Con `clearable`, el pie del calendario ofrece «Limpiar» cuando hay fecha: para filtros y campos opcionales.

## Relacionados

[calendar](/docs/components/calendar.md) · [input](/docs/components/input.md) · [select](/docs/components/select.md) · [popover](/docs/components/popover.md)
