# Calendar

> Un mes en una grilla —o varios, uno al lado del otro—, para elegir una fecha o un rango. Es el calendario de `DatePicker`, suelto.

```tsx
import { Calendar } from "sebs7n-ui/calendar"
```

## Ejemplos

### Una fecha

No dibuja superficie: acá el vidrio lo pone la `Card`. Recorrelo con las flechas; `Re Pág` y `Av Pág` cambian de mes.

```tsx
import { Calendar } from "sebs7n-ui/calendar"
import { Card, CardContent } from "sebs7n-ui/card"
import { useState } from "react"

function Basico() {
  const [fecha, setFecha] = useState<Date | null>(new Date(2026, 8, 27))
  return (
    <Card size="sm">
      <CardContent>
        <Calendar onValueChange={setFecha} value={fecha} />
      </CardContent>
    </Card>
  )
}
```

### Un rango

El primer clic es el desde y el segundo el hasta. Mientras se elige, el día bajo el puntero muestra cómo quedaría.

```tsx
import { Calendar } from "sebs7n-ui/calendar"
import { Card, CardContent } from "sebs7n-ui/card"
import { DateRange } from "sebs7n-ui/lib/dates"
import { useState } from "react"

function Rango() {
  const [rango, setRango] = useState<DateRange>({ from: new Date(2026, 8, 14), to: new Date(2026, 8, 18) })
  return (
    <Card size="sm">
      <CardContent>
        <Calendar mode="range" onValueChange={setRango} value={rango} />
      </CardContent>
    </Card>
  )
}
```

### Dos meses

Para un rango que cruza de un mes al otro. Cada fecha aparece una sola vez: los huecos de un mes quedan vacíos, porque esos días están en el de al lado.

```tsx
import { Calendar } from "sebs7n-ui/calendar"
import { Card, CardContent } from "sebs7n-ui/card"
import { DateRange } from "sebs7n-ui/lib/dates"
import { useState } from "react"

function DosMeses() {
  const [rango, setRango] = useState<DateRange>({ from: new Date(2026, 8, 28), to: new Date(2026, 9, 9) })
  return (
    <Card size="sm">
      <CardContent>
        <Calendar defaultMonth={new Date(2026, 8, 1)} mode="range" numberOfMonths={2} onValueChange={setRango} value={rango} />
      </CardContent>
    </Card>
  )
}
```

### Con límites y días apagados

`min` y `max` apagan lo que queda afuera y los botones de mes; `isDateDisabled`, fechas sueltas. Acá no hay turnos los fines de semana.

```tsx
import { Calendar } from "sebs7n-ui/calendar"
import { Card, CardContent } from "sebs7n-ui/card"

function Limites() {
  return (
    <Card size="sm">
      <CardContent>
        <Calendar
          defaultValue={new Date(2026, 8, 15)}
          isDateDisabled={(fecha) => fecha.getDay() === 0 || fecha.getDay() === 6}
          max={new Date(2026, 9, 16)}
          min={new Date(2026, 8, 7)}
        />
      </CardContent>
    </Card>
  )
}
```

### Una fecha lejana

El título abre una grilla de meses, y su año una de doce años: el alta de un cliente de hace veinte años está a cinco clics. `max` apaga los meses y años que todavía no llegaron.

```tsx
import { Calendar } from "sebs7n-ui/calendar"
import { Card, CardContent } from "sebs7n-ui/card"
import { useState } from "react"

function FechaLejana() {
  const [alta, setAlta] = useState<Date | null>(new Date(2006, 3, 12))
  return (
    <Card size="sm">
      <CardContent>
        <Calendar aria-label="Fecha de alta del cliente" max={new Date(2026, 8, 29)} onValueChange={setAlta} value={alta} />
      </CardContent>
    </Card>
  )
}
```

## Props

### Calendar

| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
| `defaultMonth` | `Date` | — | El mes con el que arranca. Por defecto, el de la fecha elegida, y si no hay, el actual. |
| `defaultValue` | `Date \| DateRange` | — | La fecha elegida al montar, sin controlar. |
| `isDateDisabled` | `(date: Date) => boolean` | — | Apaga fechas sueltas: feriados, fines de semana, días sin turno. |
| `labels` | `Partial<CalendarLabels>` | — | Los nombres de los botones de mes anterior y siguiente. |
| `locale` | `string` | — | El idioma de los nombres de mes y de día, 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. Las anteriores se ven apagadas. |
| `mode` | `"single" \| "range"` | — | `single` elige una fecha; `range`, un desde y un hasta en el mismo calendario. |
| `month` | `Date` | — | El mes a la vista, controlado. Cualquier día de ese mes sirve. |
| `numberOfMonths` | `number` | — | Cuántos meses se ven a la vez, uno al lado del otro. Por defecto, 1. `month` es el primero. Con más de uno, los días de los meses vecinos no se dibujan: ya están en el mes de al lado. |
| `onMonthChange` | `(month: Date) => void` | — | Avisa cuando cambia el mes a la vista, con su primer día. |
| `onValueChange` | `((value: Date \| null) => void) \| ((value: DateRange) => void)` | — | Avisa la fecha elegida. |
| `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. |
| `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 |
|---|---|
| ← → ↑ ↓ | Un día o una semana. Al salir de los meses a la vista, la vista se corre. |
| Inicio · Fin | Primer y último día de la semana. |
| Re Pág · Av Pág | Un mes atrás o adelante. Con Shift, un año. |
| Enter · Espacio | Elige el día enfocado. En el título, abre la grilla de meses; en su año, la de años. |
| ← → ↑ ↓ (meses y años) | Una celda o una fila de tres. Al pasar el borde, cambia el año o la página de doce años. |
| Re Pág · Av Pág (meses y años) | Un año, o doce, atrás o adelante. |
| Escape (meses y años) | Vuelve a los días sin cambiar nada, con el foco en el título. Adentro de un `DatePicker` no cierra el panel. |
| Tab | Entra a la grilla y sale: adentro es una sola parada. El título es una parada propia. |

## Accesibilidad

- Sigue el patrón de grilla de fechas de WAI-ARIA: `role="grid"`, una sola parada de tabulación y flechas adentro.
- Cada día se llama por su fecha entera —«domingo, 27 de septiembre de 2026»— y los encabezados de columna llevan el nombre del día en `abbr`.
- Hoy lleva `aria-current="date"`; el título del mes, `aria-live`, así cambiar de mes con los botones se anuncia.
- El título es un botón con `aria-expanded` y se llama «septiembre de 2026, Elegir mes y año». La grilla de meses y la de años son `role="grid"` de 4 filas por 3, con una sola parada; el mes de hoy lleva `aria-current` y el que está a la vista, `aria-selected`.
- Una fecha apagada usa `aria-disabled` y no `disabled`: sigue siendo enfocable, que es lo que deja pasar por arriba con las flechas.
- Los días de los meses vecinos completan las semanas pero están fuera del árbol de accesibilidad.
- Con varios meses hay una grilla por mes, cada una con su nombre, y siguen siendo una sola parada de tabulación entre todas.

## Reglas de uso

- 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.
- No dibuja superficie: va adentro de un `Popover`, de una `Card` o suelto. El fondo lo pone quien lo contiene.
- Para un campo de formulario, usá `DatePicker`, que ya lo trae adentro.
- Siempre son seis semanas, aunque el mes entre en cinco: con un alto fijo, lo que está debajo no se mueve al cambiar de mes.
- `numberOfMonths={2}` para un rango que suele cruzar de un mes al otro: una estadía, un alquiler. Cada fecha aparece una sola vez; los huecos de un mes quedan vacíos.
- En un teléfono va un solo mes: dos no entran a lo ancho y quedan uno debajo del otro.
- Para una fecha lejana —el alta de un cliente de hace años—, el título abre una grilla de meses y su año una de doce años: veinte años atrás son cinco clics, no 240. Con varios meses, solo el título del primero la abre, porque la vista se mueve entera. La grilla tapa a los días sin cambiar el tamaño.

## Relacionados

[date-picker](/docs/components/date-picker.md) · [popover](/docs/components/popover.md) · [input](/docs/components/input.md)
