# EventCalendar

O calendário de compromissos: o que acontece, quando, e por quanto tempo.

O `Calendar` da casa responde "que dia?". O dado que ele carrega é uma data, ou
duas, e o desenho dele é um mês de números com um deles pintado. O
`EventCalendar` carrega uma lista de compromissos com começo e fim, e existe
para mostrar as duas coisas que uma lista de texto não mostra: **quanto tempo
cada um ocupa** e **quais se atropelam**.

```tsx
<EventCalendar
  defaultView="week"
  defaultDate={anchor}
  events={events}
  label="Agenda da equipe"
  onRangeChange={({ start, end }) => carregar(start, end)}
  onEventSelect={(event) => abrir(event.id)}
  onSlotSelect={({ start, end }) => novo(start, end)}
/>
```

Um compromisso é um objeto pequeno, e o vocabulário de cor é o fechado da casa:

```ts
type CalendarEvent = {
  id: string
  title: string
  start: Date
  end: Date
  allDay?: boolean
  tone?: 'neutral' | 'accent' | 'success' | 'warning' | 'danger' | 'info'
}
```

**`tone`, e não `color`.** É a primeira tentação de um calendário, um hexa por
evento, e ela não tem como dar certo aqui: cor literal fora de `src/tokens` é
reprovada pela guarda, e um hexa vindo do app não tem par de contraste medido.
Quem precisa de mais tem `renderEvent` e `classNames.event`, com as classes da
casa. E cor nunca é o dado: se vermelho significa "cancelado", alguma coisa
precisa escrever "cancelado", ou a informação não existe para metade das
pessoas.

## As quatro vistas, e os três motores

São quatro vistas e três motores de desenho, porque `day` é `week` com uma
coluna só.

- **`agenda`** é uma lista agrupada por dia. Funciona em qualquer largura, é a
  única que não pede geometria nenhuma, e é o que o leitor de tela ouve em
  todas as outras (ver "Acessibilidade").
- **`week`** é a razão de a peça existir. Sete colunas de tempo lado a lado é a
  única forma de ver que a quinta está lotada e a sexta está vazia.
- **`day`** é a mesma grade com uma coluna, e ganha valor próprio por ser a
  única grade de tempo que cabe em 390px.
- **`month`** é o mapa de densidade: quantas coisas em cada dia, e em que
  ordem. É a vista que responde "quando vence" melhor do que qualquer outra.

### O que a `month` não mostra, e é preciso saber antes de escolhê-la

Numa célula de mês **não cabe duração** e **não cabe sobreposição**. Todo
compromisso vira uma tarja da mesma altura: uma reunião de quinze minutos e um
treinamento de oito horas ficam idênticos. E dois eventos às 14h ficam um
embaixo do outro exatamente como dois eventos às 9h e às 17h, então a tela não
diz que eles batem.

**Quem precisa ver choque de horário usa `day` ou `week`.** A `month` serve
para a pergunta anterior a essa: em que dia isso cai, e esse dia já está cheio?
Dentro da célula a ordem é cronológica, o que passa de `maxLanes` (três, de
saída) vira um `+N mais`, e esse botão abre a lista daquele dia, que é a vista
`agenda` de um dia só.

## O que acontece a 390px

A largura útil da casa a 390px é 358px, e a partir daí a aritmética decide
sozinha:

- **`week`**: 358 menos 44 da calha das horas dá 314px, dividido por sete dá
  **44,8px por coluna**. A coluna atinge o alvo de toque e é só isso que ela
  atinge: em 44px cabem umas cinco letras, e "Reunião com o contador" vira
  "Reun…". Sete colunas dessas são uma tela sem informação nenhuma.
- **`day`**: 314px de coluna única, com texto legível e sobreposição visível.
- **`month`**: 358 dividido por sete dá **51px por célula**.

**A decisão:** abaixo de 640px a `week` some do seletor, e `view="week"` recebido
por prop resolve para `agenda`, calado, do mesmo jeito que o `Calendar` ignora
`numberOfMonths` no celular e que o `Dialog` vira `Sheet`. É precedente da casa
em três lugares.

**A `month` fica**, e o motivo é que ela não é a `week` com outro nome. Uma
coluna de `week` precisa mostrar *hora e duração* em 44px, e não mostra; uma
célula de `month` precisa mostrar *que existe alguma coisa, e mais ou menos o
quê*, em 51px, e isso sobrevive ao corte: a tarja continua legível pelas
primeiras palavras, a contagem do dia continua exata, e o `+N mais` abre a
lista completa numa folha de baixo. No celular, quem procura duração vai para a
`day`; quem procura "que dia" fica na `month`.

**O que não se faz, e é a saída que todo mundo tenta primeiro: rolagem
horizontal na semana.** A grade de tempo já rola na vertical, e somar rolagem
horizontal cria duas direções de gesto disputando o mesmo dedo. O dedo perde.

## O compromisso que atravessa dias

Um compromisso de 14/03 às 22h a 16/03 às 9h não é um retângulo, são três
pedaços em três colunas, e o dado é um só. **A peça não guarda evento partido:
ela o parte na apresentação.** Cada segmento carrega `continuesBefore` e
`continuesAfter`, e é isso que tira o arredondamento da beirada e que faz o
texto acessível dizer "continua do dia anterior". Sem esses dois sinalizadores,
um evento de três dias vira três compromissos idênticos e ninguém sabe se são
três reuniões ou uma.

São dois casos, e eles vão para lugares diferentes da tela:

1. **Dia inteiro e multi-dia** (`allDay`, ou duração maior que a janela de horas
   visível) vão para a **faixa de dia inteiro**, acima da grade, como barras que
   atravessam colunas. A faixa empilha em lanes (ordena por começo e, empatado,
   por duração decrescente, e põe cada barra na primeira lane livre), tem teto
   de `maxLanes`, e o resto vira `+N`.
2. **A noite que cruza a meia-noite** (22h às 9h) é partida em dois segmentos e
   **não** sobe para a faixa, porque a hora dela é informação: 22h é tarde da
   noite, e a faixa de cima diria só "quarta e quinta".

Onde o evento se parte depende do fuso do navegador. O mesmo compromisso visto
de outro fuso pode não cruzar a meia-noite, e então não se parte. É consequência
direta de a peça não conhecer fuso, e é comportamento, não defeito.

## O compromisso que se sobrepõe a outro

O algoritmo é o mesmo do Google Agenda, e vale escrever porque o erro é sempre o
mesmo: quem tenta resolver evento a evento produz larguras que não fecham.

1. **Agrupar em conjuntos.** Um conjunto é um grupo ligado por sobreposição
   transitiva: A bate em B, B bate em C, então A, B e C são um conjunto, mesmo
   que A e C não se toquem. A largura se divide por conjunto, nunca por par.
2. **Colunas dentro do conjunto.** Percorre em ordem de começo e põe cada evento
   na primeira coluna onde ele não bate em nada.
3. **Expandir para a direita.** Quem tem espaço livre à direita cresce até
   esbarrar. É o que impede a tela de virar quatro tirinhas finas quando só dois
   horários realmente colidem.

`maxColumns` (três, de saída) é o teto. O que passa dele vira um `+N mais` na
última coluna, que abre a lista do dia. A 314px de coluna única no celular, três
colunas dão 104px cada, que ainda têm texto; a quarta não teria.

## O piso de altura, e o que ele custa

A 48px por hora, um compromisso de quinze minutos tem 12px e um de cinco minutos
tem 4px. Nenhum dos dois é alvo de toque, e em nenhum dos dois cabe texto. A
tarja tem então um **piso de altura desenhada**: `--rc-control-md` na mesa (40px
confortável, 32px compacto, portanto sensível à densidade) e 44px no celular.

**O piso é só do desenho. O cálculo de sobreposição roda nos horários reais**, e
essa separação é a decisão inteira. A consequência precisa estar escrita, porque
ela aparece na tela: dois compromissos de dez minutos separados por quinze
minutos não se sobrepõem no dado e **se sobrepõem na tela**. Eles se desenham
empilhados, com sombra, e não roubam coluna um do outro.

É um defeito visual assumido, e a alternativa é pior: deixar o layout usar as
alturas pisadas faz a grade inteira mentir, porque aí a largura de todo mundo
passa a depender de um número que não é o horário. Um erro fica na beirada de
dois eventos curtos; o outro se espalha pelo dia.

A área que rola tem `maxHeight` (560px de saída) na `agenda`, na `day` e na
`week`; a `month` cresce com o número de linhas e rola junto com a página,
porque uma célula de mês só serve inteira.

Pelo mesmo motivo, quem cai fora da janela de `dayStart`/`dayEnd` não some:
encosta na beirada da calha, com o piso de altura, e continua na `agenda` e na
`month`. `dayStart`/`dayEnd` existem porque 24 horas a 48px são 1152px de
altura, e ninguém tem expediente à meia-noite: de 7h às 20h dá 624px, que cabe
numa tela.

**Horário de verão.** A peça desenha pelo relógio de parede: a linha das 14h
fica em `14 * hourHeight`, sempre. Num dia de virada, o bloco que atravessa a
mudança sai uma hora maior ou menor do que a duração real, e nenhuma linha de
hora se desloca. É a troca escolhida: o erro fica num bloco, e não na grade
inteira.

## Acessibilidade

Uma grade de calendário é duas coisas incompatíveis: uma tabela bidimensional
para quem navega por teclado, e uma lista cronológica para quem ouve.

**Para quem ouve, toda vista é a vista `agenda`.** O andaime visual (as linhas
de hora, a calha, os cabeçalhos de coluna, os nomes dos dias da semana no mês)
sai `aria-hidden`. O que se expõe é, por dia, um grupo com nome acessível
("Terça-feira, 17 de março, 3 compromissos") e, dentro dele, uma lista em ordem
cronológica com `aria-setsize` e `aria-posinset` por evento. É o mesmo par que a
`VirtualList` escolheu, e pelo mesmo motivo: a contagem tem que ser a real, e
não a desenhada, porque o que está escondido atrás de um `+N mais` continua
existindo.

A faixa de dia inteiro é um grupo à parte, chamado "Dia inteiro", com a própria
lista e a própria contagem. O nome do grupo de cada dia continua somando tudo o
que cai naquele dia, a faixa inclusive: é a resposta certa para "quão cheia está
a terça".

**O que não se faz:** `role="grid"` com uma célula por meia hora. Uma semana de
24 horas em blocos de trinta minutos dá 336 células, quase todas vazias, e cada
uma é uma parada. É o erro que a maioria das bibliotecas de calendário comete, e
é o mesmo erro do `Tracker` numa forma nova.

**Para o teclado, uma parada de tabulação para a peça inteira**, com foco
itinerante entre **eventos**, e não entre células. Precedente direto do
`Tracker`, que é uma parada só para 365 quadrados.

| Tecla | O que faz |
|---|---|
| `↑` `↓` | evento anterior e próximo dentro do dia, em ordem de hora |
| `↑` `↓` na `month` | no primeiro e no último do dia, pula para a mesma coluna da semana de cima ou de baixo |
| `↑` `↓` na `agenda` | anterior e próximo da lista, atravessando os dias |
| `←` `→` | o evento mais próximo em hora, no dia anterior ou seguinte |
| `Home` `End` | primeiro e último evento do período visível |
| `PageUp` `PageDown` | período anterior e seguinte |
| `Enter` `Espaço` | dispara `onEventSelect`, ou abre o `+N mais` |
| `Esc` | devolve o foco à barra de ferramentas |

`←` e `→` seguem a direção da escrita: em RTL, `←` anda para o dia seguinte. A
direção vem do `RivoProvider`, e isso entrou na v1 e não depois, porque espelhar
o desenho sem espelhar a conta já custou defeito real em quatro peças da casa.

Mais três coisas:

- **A troca de período se anuncia** numa região viva: "16 a 22 de março de 2026,
  7 compromissos". Desenho não chega a quem ouve, e a paginação é a única
  mudança grande que não move o foco.
- **`aria-current="date"` viaja com o grupo do dia**, e não com o cabeçalho da
  coluna. O cabeçalho é andaime e sai `aria-hidden`, então um `aria-current`
  nele não seria lido por ninguém; o grupo de hoje ainda diz "hoje" no nome.
- **A linha do agora é decorativa** (`aria-hidden`). Ela não é dado, é um
  relógio, e quem ouve tem o relógio do próprio aparelho.

## Os quatro finais, e onde o vazio aparece

Os mesmos do `DataTable` e da `VirtualList`, na mesma ordem e com os mesmos
nomes de prop: **erro vence carregando, e vazio só vale depois que a consulta
voltou**. `isLoading` e `events === undefined` são a mesma coisa. `errorTitle`,
`errorMessage`, `onRetry` e `retryLabel` são o conjunto do erro. A espera se
anuncia em voz alta na mesma região viva das irmãs.

O vazio tem uma diferença que é decisão desta peça: **na `agenda` ele ocupa o
lugar da lista; nas grades ele fica por cima, e a grade continua desenhada**.
Uma grade é também a superfície onde se clica para criar, e esconder a semana
quando a semana está livre é esconder exatamente o horário vago que a pessoa
procurava.

## O que sai quando o período muda

`onRangeChange` avisa que o período visível mudou, e é o gancho para o app
buscar. **O fim é exclusivo**: é a meia-noite do dia seguinte ao último dia
mostrado, o que faz a comparação no servidor ser `start <= x < end` sem
milissegundo de sobra. A `agenda` e a `month` compartilham o mês como período,
com uma diferença: a `month` desenha semanas inteiras, então ela mostra os dias
vizinhos que completam a primeira e a última linha, e o intervalo emitido inclui
esses dias.

## Partes

`classNames` veste cada parte sem ninguém alcançar o nó interno por `[&>div>div]`:

- **`toolbar`**: a barra com navegação, "ir para a data" e seletor de vista.
- **`body`**: a moldura que envolve a vista.
- **`header`**: a linha de cabeçalho de dia, na grade e no mês.
- **`gutter`**: a calha das horas.
- **`column`**: a coluna de um dia na grade de tempo.
- **`cell`**: a célula de um dia no mês.
- **`band`**: a faixa de dia inteiro.
- **`section`**: o bloco de um dia na agenda.
- **`event`**: a caixa de cada compromisso, nas três formas.

`renderEvent` troca só o miolo da tarja. A caixa, o foco, o posicionamento e o
rótulo acessível continuam da peça, porque são eles que sustentam a navegação
por teclado.

## O que ela não faz

Cada linha aqui é uma porta que alguém vai tentar abrir, e é o que impede a peça
de virar aplicação.

1. **Não busca dado.** Entram `events`, `isLoading`, `isError`, `onRetry` e
   `empty`; sai `onRangeChange`. Mesma divisão do `DataTable`, da `VirtualList` e
   do `QueryBoundary`.
2. **Não conhece fuso de servidor.** Tudo é `Date`, na hora local do navegador,
   como o `DatePicker` já decidiu. Um compromisso gravado em `America/Sao_Paulo`
   e visto de Lisboa aparece na hora de Lisboa; se não é isso que se quer,
   converta antes de entregar. Uma peça que conhecesse fuso precisaria de banco
   de fusos, e banco de fusos é o começo de uma biblioteca de datas.
3. **Não edita.** Sem arrastar para mover, sem esticar para redimensionar. Ela
   emite `onEventSelect` e `onSlotSelect` (o clique no vazio devolve o intervalo
   de meia hora que foi clicado, ou o dia inteiro no mês) e o app abre o
   `Dialog` que quiser. Arrastar num touch disputa com a rolagem, e é a linha
   exata onde um componente vira aplicação.
4. **Não expande recorrência.** Sem RRULE, sem exceção de série: o app entrega
   as instâncias já expandidas.
5. **Não faz recurso.** Colunas por sala ou por profissional, em vez de por dia,
   é o mesmo motor com outro eixo, e é necessidade real de clínica e de
   barbearia. Fica para depois, e a porta está aberta.
6. **Não imprime**, não exporta `.ics`, não sincroniza com nada.

## Duas escolhas de construção que se veem de fora

**A `agenda` não é virtualizada, de propósito.** A estrutura que a
acessibilidade exige aqui (um grupo por dia, com a lista dele dentro, e
`aria-setsize` por evento) é aninhada, e uma janela virtual é uma lista plana:
não dá para expressar as duas ao mesmo tempo sem mentir na contagem. Some-se que
o foco itinerante precisa que o elemento exista no DOM para receber foco, e o
período visível é no máximo um mês, o que limita a lista a algumas dezenas de
seções. Para um ano inteiro numa rolagem só, o caminho é a `VirtualList` com o
`renderItem` que você já tem.

**No mês, o compromisso de vários dias é desenhado por dia**, e não como uma
barra única atravessando a linha. Todos os pedaços ficam na mesma lane, então a
leitura continua sendo de uma faixa contínua, e as beiradas de continuação
perdem o arredondamento. O que se ganha com isso é o grupo do dia: cada célula
contém os compromissos daquele dia, que é o que o leitor de tela precisa ouvir.

## Quando não usar

**Se ninguém precisa ver duração nem choque de horário, use `DataTable`.** Esta
é a mais importante, e é a que mais se erra. Uma listagem de agendamentos com
coluna de cliente, hora, status, ordenação e filtro é uma tabela, e a tabela já
faz tudo isso melhor. O `EventCalendar` só se paga quando a resposta que se
procura é geométrica: "esse bloco é grande demais", "esses dois batem". Fora
disso ele é uma tabela cara e com menos recursos.

**Se a pergunta é "que dia?", use `Calendar`, `DatePicker` ou
`DateRangePicker`.** Escolher um vencimento não é ver uma agenda, e um
`EventCalendar` para isso é uma grade de 24 horas onde a pessoa queria sete
números.

**Se a pergunta é "o que aconteceu com esta coisa?", use `Timeline`.** Ela olha
para trás, é sobre um objeto só (uma nota fiscal, um contrato) e os eventos dela
são instantes sem duração. Um `EventCalendar` de uma nota fiscal desenharia
cinco carimbos de zero minuto espalhados por três meses de grade vazia.

**Se a pergunta é "quantos, por período?", use `Tracker`.** Ele conta
ocorrências discretas, não tem hora e cabe dentro de um `Stat`.

## No React Native

Na fila, e a fila e por DESENHO de gesto, nao por tempo. Tres das quatro vistas portam: a `agenda` vira `SectionList` (virtualizacao de fabrica, o mesmo argumento que tirou a `VirtualList` do catalogo nativo), a `day` e uma coluna unica de 314px, que e coluna de verdade, e a `month` sobrevive aos 51px por celula porque a celula so precisa mostrar que existe alguma coisa e mais ou menos o que.

**A `week` nao porta.** Sete colunas em 358px dao 44,8px cada, e a coluna de semana existe para mostrar hora e duracao. Em 44,8px ela mostra um retangulo colorido, que e o que a `month` ja faz melhor e mais barato. O web tomou a mesma decisao para a propria tela estreita: abaixo de `sm` a `week` some do seletor e `view="week"` resolve para `agenda`.

**A decisao foi tomada em 27/08/2026, e e nao.** Ela esteve em `FILA_DECLARADA` esperando decisao de gesto; o desenho foi escrito, medido, e a conta dele decidiu contra a peca. Esta em `docs/2026-08-27-event-calendar-nativo-desenho.md`, e continua valendo como registro do que foi medido.

O custo nao se distribui por igual entre as vistas, e e isso que decide. A `agenda` e o `month` sao baratos: um e lista, o outro e grade de mes, e os dois ja tem resposta no pacote. A `day` e a `week` sao a peca inteira - o desenhador de tempo, o alvo de 44 pontos sobre tarja de 12, o conflito entre deslizar para trocar de periodo e arrastar para ler, e a maior parte das mil e duzentas linhas. Elas custam de 15 a 18% do pacote, compiladas pelo metro no aplicativo de quem importa um `Button`, porque o nativo publica FONTE.

E o que elas comprariam nao cabe na tela: sete colunas em 358px dao 44,8px cada, onde a coluna de semana existe para mostrar hora e duracao. Grade de tempo e idioma de mesa - ela responde "o que choca com o que", e essa pergunta se faz com o olho passeando, e nao com o dedo cobrindo o que ele toca.

**No telefone, a resposta e outra peca.** Compromisso por dia e lista, e a lista se monta com o que ja existe. Data com valor - vencimento, prazo, entrega - e o `Calendar`, que no nativo ja pinta por dia pelo `DayPaint`. Quem precisa de grade de tempo no celular esta pedindo a tela de mesa num aparelho que nao a comporta.

## Importação

```tsx
import { EventCalendar } from '@rivocode/ui'
```

## Exemplos

### Semana

```tsx
import { Badge, Button, EventCalendar, type CalendarEvent } from '@rivocode/ui'
import { useState } from 'react'

const ANCHOR = new Date(2026, 2, 17)

function at(day: number, hour: number, minute = 0) {
  return new Date(2026, 2, day, hour, minute)
}

const WEEK: CalendarEvent[] = [
  { id: '1', title: 'Reunião com o contador', start: at(16, 9), end: at(16, 10), tone: 'accent' },
  { id: '2', title: 'Fechamento da folha', start: at(16, 14), end: at(16, 16), tone: 'warning' },
  { id: '3', title: 'Diária da equipe', start: at(17, 9), end: at(17, 9, 30) },
  { id: '4', title: 'Almoço com o cliente', start: at(17, 12), end: at(17, 13, 30) },
  { id: '5', title: 'Revisão do orçamento', start: at(18, 10), end: at(18, 11, 30), tone: 'info' },
  { id: '6', title: 'Entrega da apuração', start: at(19, 15), end: at(19, 17), tone: 'danger' },
  { id: '7', title: 'Retrospectiva', start: at(20, 16), end: at(20, 17), tone: 'success' },
]

const MONTH: CalendarEvent[] = [
  ...WEEK,
  { id: '8', title: 'Vencimento do DAS', start: at(20, 0), end: at(21, 0), allDay: true, tone: 'danger' },
  { id: '9', title: 'Feira do setor', start: at(24, 0), end: at(27, 0), allDay: true, tone: 'info' },
  { id: '10', title: 'Consultoria', start: at(9, 14), end: at(9, 16) },
  { id: '11', title: 'Auditoria interna', start: at(10, 9), end: at(10, 18), tone: 'warning' },
  { id: '12', title: 'Treinamento', start: at(25, 9), end: at(25, 12) },
  { id: '13', title: 'Fechamento do mês', start: at(31, 8), end: at(31, 18), tone: 'accent' },
]

/** Agenda */
export function Agenda() {
  return (
    <EventCalendar defaultView="agenda" defaultDate={ANCHOR} events={MONTH} label="Próximos compromissos" />
  )
}

export function Week() {
  return <EventCalendar defaultView="week" defaultDate={ANCHOR} events={WEEK} label="Agenda da equipe" />
}
```

### Dia

```tsx
import { Badge, Button, EventCalendar, type CalendarEvent } from '@rivocode/ui'
import { useState } from 'react'

const ANCHOR = new Date(2026, 2, 17)

function at(day: number, hour: number, minute = 0) {
  return new Date(2026, 2, day, hour, minute)
}

const WEEK: CalendarEvent[] = [
  { id: '1', title: 'Reunião com o contador', start: at(16, 9), end: at(16, 10), tone: 'accent' },
  { id: '2', title: 'Fechamento da folha', start: at(16, 14), end: at(16, 16), tone: 'warning' },
  { id: '3', title: 'Diária da equipe', start: at(17, 9), end: at(17, 9, 30) },
  { id: '4', title: 'Almoço com o cliente', start: at(17, 12), end: at(17, 13, 30) },
  { id: '5', title: 'Revisão do orçamento', start: at(18, 10), end: at(18, 11, 30), tone: 'info' },
  { id: '6', title: 'Entrega da apuração', start: at(19, 15), end: at(19, 17), tone: 'danger' },
  { id: '7', title: 'Retrospectiva', start: at(20, 16), end: at(20, 17), tone: 'success' },
]

const MONTH: CalendarEvent[] = [
  ...WEEK,
  { id: '8', title: 'Vencimento do DAS', start: at(20, 0), end: at(21, 0), allDay: true, tone: 'danger' },
  { id: '9', title: 'Feira do setor', start: at(24, 0), end: at(27, 0), allDay: true, tone: 'info' },
  { id: '10', title: 'Consultoria', start: at(9, 14), end: at(9, 16) },
  { id: '11', title: 'Auditoria interna', start: at(10, 9), end: at(10, 18), tone: 'warning' },
  { id: '12', title: 'Treinamento', start: at(25, 9), end: at(25, 12) },
  { id: '13', title: 'Fechamento do mês', start: at(31, 8), end: at(31, 18), tone: 'accent' },
]

/** Agenda */
export function Agenda() {
  return (
    <EventCalendar defaultView="agenda" defaultDate={ANCHOR} events={MONTH} label="Próximos compromissos" />
  )
}

export function Day() {
  return (
    <EventCalendar
      defaultView="day"
      defaultDate={ANCHOR}
      events={WEEK}
      dayStart={8}
      dayEnd={19}
      label="Agenda do dia"
    />
  )
}

const MONTH: CalendarEvent[] = [
  ...WEEK,
  { id: '8', title: 'Vencimento do DAS', start: at(20, 0), end: at(21, 0), allDay: true, tone: 'danger' },
  { id: '9', title: 'Feira do setor', start: at(24, 0), end: at(27, 0), allDay: true, tone: 'info' },
  { id: '10', title: 'Consultoria', start: at(9, 14), end: at(9, 16) },
  { id: '11', title: 'Auditoria interna', start: at(10, 9), end: at(10, 18), tone: 'warning' },
  { id: '12', title: 'Treinamento', start: at(25, 9), end: at(25, 12) },
  { id: '13', title: 'Fechamento do mês', start: at(31, 8), end: at(31, 18), tone: 'accent' },
]
```

### Mês

```tsx
import { Badge, Button, EventCalendar, type CalendarEvent } from '@rivocode/ui'
import { useState } from 'react'

const ANCHOR = new Date(2026, 2, 17)

function at(day: number, hour: number, minute = 0) {
  return new Date(2026, 2, day, hour, minute)
}

const WEEK: CalendarEvent[] = [
  { id: '1', title: 'Reunião com o contador', start: at(16, 9), end: at(16, 10), tone: 'accent' },
  { id: '2', title: 'Fechamento da folha', start: at(16, 14), end: at(16, 16), tone: 'warning' },
  { id: '3', title: 'Diária da equipe', start: at(17, 9), end: at(17, 9, 30) },
  { id: '4', title: 'Almoço com o cliente', start: at(17, 12), end: at(17, 13, 30) },
  { id: '5', title: 'Revisão do orçamento', start: at(18, 10), end: at(18, 11, 30), tone: 'info' },
  { id: '6', title: 'Entrega da apuração', start: at(19, 15), end: at(19, 17), tone: 'danger' },
  { id: '7', title: 'Retrospectiva', start: at(20, 16), end: at(20, 17), tone: 'success' },
]

const MONTH: CalendarEvent[] = [
  ...WEEK,
  { id: '8', title: 'Vencimento do DAS', start: at(20, 0), end: at(21, 0), allDay: true, tone: 'danger' },
  { id: '9', title: 'Feira do setor', start: at(24, 0), end: at(27, 0), allDay: true, tone: 'info' },
  { id: '10', title: 'Consultoria', start: at(9, 14), end: at(9, 16) },
  { id: '11', title: 'Auditoria interna', start: at(10, 9), end: at(10, 18), tone: 'warning' },
  { id: '12', title: 'Treinamento', start: at(25, 9), end: at(25, 12) },
  { id: '13', title: 'Fechamento do mês', start: at(31, 8), end: at(31, 18), tone: 'accent' },
]

export function Month() {
  return <EventCalendar defaultView="month" defaultDate={ANCHOR} events={MONTH} label="Mês da equipe" />
}
```

### Agenda

```tsx
import { Badge, Button, EventCalendar, type CalendarEvent } from '@rivocode/ui'
import { useState } from 'react'

const ANCHOR = new Date(2026, 2, 17)

function at(day: number, hour: number, minute = 0) {
  return new Date(2026, 2, day, hour, minute)
}

const WEEK: CalendarEvent[] = [
  { id: '1', title: 'Reunião com o contador', start: at(16, 9), end: at(16, 10), tone: 'accent' },
  { id: '2', title: 'Fechamento da folha', start: at(16, 14), end: at(16, 16), tone: 'warning' },
  { id: '3', title: 'Diária da equipe', start: at(17, 9), end: at(17, 9, 30) },
  { id: '4', title: 'Almoço com o cliente', start: at(17, 12), end: at(17, 13, 30) },
  { id: '5', title: 'Revisão do orçamento', start: at(18, 10), end: at(18, 11, 30), tone: 'info' },
  { id: '6', title: 'Entrega da apuração', start: at(19, 15), end: at(19, 17), tone: 'danger' },
  { id: '7', title: 'Retrospectiva', start: at(20, 16), end: at(20, 17), tone: 'success' },
]

const MONTH: CalendarEvent[] = [
  ...WEEK,
  { id: '8', title: 'Vencimento do DAS', start: at(20, 0), end: at(21, 0), allDay: true, tone: 'danger' },
  { id: '9', title: 'Feira do setor', start: at(24, 0), end: at(27, 0), allDay: true, tone: 'info' },
  { id: '10', title: 'Consultoria', start: at(9, 14), end: at(9, 16) },
  { id: '11', title: 'Auditoria interna', start: at(10, 9), end: at(10, 18), tone: 'warning' },
  { id: '12', title: 'Treinamento', start: at(25, 9), end: at(25, 12) },
  { id: '13', title: 'Fechamento do mês', start: at(31, 8), end: at(31, 18), tone: 'accent' },
]

const CLASH: CalendarEvent[] = [
  { id: 'a', title: 'Consulta de Ana Prado', start: at(17, 9), end: at(17, 12), tone: 'accent' },
  { id: 'b', title: 'Consulta de Bruno Lima', start: at(17, 9), end: at(17, 10) },
  { id: 'c', title: 'Retorno de Célia Dias', start: at(17, 9), end: at(17, 9, 30), tone: 'info' },
  { id: 'd', title: 'Encaixe de Davi Rocha', start: at(17, 9), end: at(17, 10, 30), tone: 'warning' },
  { id: 'e', title: 'Consulta de Eva Nunes', start: at(17, 10, 30), end: at(17, 11), tone: 'success' },
]

export function Agenda() {
  return (
    <EventCalendar defaultView="agenda" defaultDate={ANCHOR} events={MONTH} label="Próximos compromissos" />
  )
}

const CLASH: CalendarEvent[] = [
  { id: 'a', title: 'Consulta de Ana Prado', start: at(17, 9), end: at(17, 12), tone: 'accent' },
  { id: 'b', title: 'Consulta de Bruno Lima', start: at(17, 9), end: at(17, 10) },
  { id: 'c', title: 'Retorno de Célia Dias', start: at(17, 9), end: at(17, 9, 30), tone: 'info' },
  { id: 'd', title: 'Encaixe de Davi Rocha', start: at(17, 9), end: at(17, 10, 30), tone: 'warning' },
  { id: 'e', title: 'Consulta de Eva Nunes', start: at(17, 10, 30), end: at(17, 11), tone: 'success' },
]
```

### Choque de horário

```tsx
import { Badge, Button, EventCalendar, type CalendarEvent } from '@rivocode/ui'
import { useState } from 'react'

const ANCHOR = new Date(2026, 2, 17)

function at(day: number, hour: number, minute = 0) {
  return new Date(2026, 2, day, hour, minute)
}

const WEEK: CalendarEvent[] = [
  { id: '1', title: 'Reunião com o contador', start: at(16, 9), end: at(16, 10), tone: 'accent' },
  { id: '2', title: 'Fechamento da folha', start: at(16, 14), end: at(16, 16), tone: 'warning' },
  { id: '3', title: 'Diária da equipe', start: at(17, 9), end: at(17, 9, 30) },
  { id: '4', title: 'Almoço com o cliente', start: at(17, 12), end: at(17, 13, 30) },
  { id: '5', title: 'Revisão do orçamento', start: at(18, 10), end: at(18, 11, 30), tone: 'info' },
  { id: '6', title: 'Entrega da apuração', start: at(19, 15), end: at(19, 17), tone: 'danger' },
  { id: '7', title: 'Retrospectiva', start: at(20, 16), end: at(20, 17), tone: 'success' },
]

const MONTH: CalendarEvent[] = [
  ...WEEK,
  { id: '8', title: 'Vencimento do DAS', start: at(20, 0), end: at(21, 0), allDay: true, tone: 'danger' },
  { id: '9', title: 'Feira do setor', start: at(24, 0), end: at(27, 0), allDay: true, tone: 'info' },
  { id: '10', title: 'Consultoria', start: at(9, 14), end: at(9, 16) },
  { id: '11', title: 'Auditoria interna', start: at(10, 9), end: at(10, 18), tone: 'warning' },
  { id: '12', title: 'Treinamento', start: at(25, 9), end: at(25, 12) },
  { id: '13', title: 'Fechamento do mês', start: at(31, 8), end: at(31, 18), tone: 'accent' },
]

/** Agenda */
export function Agenda() {
  return (
    <EventCalendar defaultView="agenda" defaultDate={ANCHOR} events={MONTH} label="Próximos compromissos" />
  )
}

const CLASH: CalendarEvent[] = [
  { id: 'a', title: 'Consulta de Ana Prado', start: at(17, 9), end: at(17, 12), tone: 'accent' },
  { id: 'b', title: 'Consulta de Bruno Lima', start: at(17, 9), end: at(17, 10) },
  { id: 'c', title: 'Retorno de Célia Dias', start: at(17, 9), end: at(17, 9, 30), tone: 'info' },
  { id: 'd', title: 'Encaixe de Davi Rocha', start: at(17, 9), end: at(17, 10, 30), tone: 'warning' },
  { id: 'e', title: 'Consulta de Eva Nunes', start: at(17, 10, 30), end: at(17, 11), tone: 'success' },
]

const ACROSS: CalendarEvent[] = [
  { id: 'trip', title: 'Viagem a Recife', start: at(16, 0), end: at(19, 0), allDay: true, tone: 'info' },
  { id: 'holiday', title: 'Feriado municipal', start: at(18, 0), end: at(19, 0), allDay: true },
  { id: 'night', title: 'Plantão da virada', start: at(17, 22), end: at(18, 9), tone: 'warning' },
]

export function Overlap() {
  return (
    <EventCalendar
      defaultView="day"
      defaultDate={ANCHOR}
      events={CLASH}
      dayStart={8}
      dayEnd={13}
      maxColumns={3}
      label="Agenda do consultório"
    />
  )
}

const ACROSS: CalendarEvent[] = [
  { id: 'trip', title: 'Viagem a Recife', start: at(16, 0), end: at(19, 0), allDay: true, tone: 'info' },
  { id: 'holiday', title: 'Feriado municipal', start: at(18, 0), end: at(19, 0), allDay: true },
  { id: 'night', title: 'Plantão da virada', start: at(17, 22), end: at(18, 9), tone: 'warning' },
]
```

### Dia inteiro e a noite que atravessa

```tsx
import { Badge, Button, EventCalendar, type CalendarEvent } from '@rivocode/ui'
import { useState } from 'react'

const ANCHOR = new Date(2026, 2, 17)

function at(day: number, hour: number, minute = 0) {
  return new Date(2026, 2, day, hour, minute)
}

const WEEK: CalendarEvent[] = [
  { id: '1', title: 'Reunião com o contador', start: at(16, 9), end: at(16, 10), tone: 'accent' },
  { id: '2', title: 'Fechamento da folha', start: at(16, 14), end: at(16, 16), tone: 'warning' },
  { id: '3', title: 'Diária da equipe', start: at(17, 9), end: at(17, 9, 30) },
  { id: '4', title: 'Almoço com o cliente', start: at(17, 12), end: at(17, 13, 30) },
  { id: '5', title: 'Revisão do orçamento', start: at(18, 10), end: at(18, 11, 30), tone: 'info' },
  { id: '6', title: 'Entrega da apuração', start: at(19, 15), end: at(19, 17), tone: 'danger' },
  { id: '7', title: 'Retrospectiva', start: at(20, 16), end: at(20, 17), tone: 'success' },
]

const MONTH: CalendarEvent[] = [
  ...WEEK,
  { id: '8', title: 'Vencimento do DAS', start: at(20, 0), end: at(21, 0), allDay: true, tone: 'danger' },
  { id: '9', title: 'Feira do setor', start: at(24, 0), end: at(27, 0), allDay: true, tone: 'info' },
  { id: '10', title: 'Consultoria', start: at(9, 14), end: at(9, 16) },
  { id: '11', title: 'Auditoria interna', start: at(10, 9), end: at(10, 18), tone: 'warning' },
  { id: '12', title: 'Treinamento', start: at(25, 9), end: at(25, 12) },
  { id: '13', title: 'Fechamento do mês', start: at(31, 8), end: at(31, 18), tone: 'accent' },
]

/** Agenda */
export function Agenda() {
  return (
    <EventCalendar defaultView="agenda" defaultDate={ANCHOR} events={MONTH} label="Próximos compromissos" />
  )
}

const ACROSS: CalendarEvent[] = [
  { id: 'trip', title: 'Viagem a Recife', start: at(16, 0), end: at(19, 0), allDay: true, tone: 'info' },
  { id: 'holiday', title: 'Feriado municipal', start: at(18, 0), end: at(19, 0), allDay: true },
  { id: 'night', title: 'Plantão da virada', start: at(17, 22), end: at(18, 9), tone: 'warning' },
]

export function AcrossDays() {
  return (
    <EventCalendar
      defaultView="week"
      defaultDate={ANCHOR}
      events={ACROSS}
      dayStart={7}
      dayEnd={23}
      label="Agenda da equipe"
    />
  )
}
```

### Escolher e criar

```tsx
import { Badge, Button, EventCalendar, type CalendarEvent } from '@rivocode/ui'
import { useState } from 'react'

const ANCHOR = new Date(2026, 2, 17)

function at(day: number, hour: number, minute = 0) {
  return new Date(2026, 2, day, hour, minute)
}

const WEEK: CalendarEvent[] = [
  { id: '1', title: 'Reunião com o contador', start: at(16, 9), end: at(16, 10), tone: 'accent' },
  { id: '2', title: 'Fechamento da folha', start: at(16, 14), end: at(16, 16), tone: 'warning' },
  { id: '3', title: 'Diária da equipe', start: at(17, 9), end: at(17, 9, 30) },
  { id: '4', title: 'Almoço com o cliente', start: at(17, 12), end: at(17, 13, 30) },
  { id: '5', title: 'Revisão do orçamento', start: at(18, 10), end: at(18, 11, 30), tone: 'info' },
  { id: '6', title: 'Entrega da apuração', start: at(19, 15), end: at(19, 17), tone: 'danger' },
  { id: '7', title: 'Retrospectiva', start: at(20, 16), end: at(20, 17), tone: 'success' },
]

const MONTH: CalendarEvent[] = [
  ...WEEK,
  { id: '8', title: 'Vencimento do DAS', start: at(20, 0), end: at(21, 0), allDay: true, tone: 'danger' },
  { id: '9', title: 'Feira do setor', start: at(24, 0), end: at(27, 0), allDay: true, tone: 'info' },
  { id: '10', title: 'Consultoria', start: at(9, 14), end: at(9, 16) },
  { id: '11', title: 'Auditoria interna', start: at(10, 9), end: at(10, 18), tone: 'warning' },
  { id: '12', title: 'Treinamento', start: at(25, 9), end: at(25, 12) },
  { id: '13', title: 'Fechamento do mês', start: at(31, 8), end: at(31, 18), tone: 'accent' },
]

/** Agenda */
export function Agenda() {
  return (
    <EventCalendar defaultView="agenda" defaultDate={ANCHOR} events={MONTH} label="Próximos compromissos" />
  )
}

export function Interactive() {
  const [picked, setPicked] = useState<string>('Nada escolhido ainda.')

  return (
    <div className="flex w-full flex-col gap-3">
      <p className="text-sm text-fg-muted">{picked}</p>
      <EventCalendar
        defaultView="week"
        defaultDate={ANCHOR}
        events={WEEK}
        label="Agenda da equipe"
        onEventSelect={(event) => setPicked(`Aberto: ${event.title}`)}
        onSlotSelect={(range) =>
          setPicked(`Novo compromisso às ${range.start.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' })}`)
        }
      />
    </div>
  )
}
```

### Tarja com desenho próprio

```tsx
import { Badge, Button, EventCalendar, type CalendarEvent } from '@rivocode/ui'
import { useState } from 'react'

const ANCHOR = new Date(2026, 2, 17)

function at(day: number, hour: number, minute = 0) {
  return new Date(2026, 2, day, hour, minute)
}

const WEEK: CalendarEvent[] = [
  { id: '1', title: 'Reunião com o contador', start: at(16, 9), end: at(16, 10), tone: 'accent' },
  { id: '2', title: 'Fechamento da folha', start: at(16, 14), end: at(16, 16), tone: 'warning' },
  { id: '3', title: 'Diária da equipe', start: at(17, 9), end: at(17, 9, 30) },
  { id: '4', title: 'Almoço com o cliente', start: at(17, 12), end: at(17, 13, 30) },
  { id: '5', title: 'Revisão do orçamento', start: at(18, 10), end: at(18, 11, 30), tone: 'info' },
  { id: '6', title: 'Entrega da apuração', start: at(19, 15), end: at(19, 17), tone: 'danger' },
  { id: '7', title: 'Retrospectiva', start: at(20, 16), end: at(20, 17), tone: 'success' },
]

const MONTH: CalendarEvent[] = [
  ...WEEK,
  { id: '8', title: 'Vencimento do DAS', start: at(20, 0), end: at(21, 0), allDay: true, tone: 'danger' },
  { id: '9', title: 'Feira do setor', start: at(24, 0), end: at(27, 0), allDay: true, tone: 'info' },
  { id: '10', title: 'Consultoria', start: at(9, 14), end: at(9, 16) },
  { id: '11', title: 'Auditoria interna', start: at(10, 9), end: at(10, 18), tone: 'warning' },
  { id: '12', title: 'Treinamento', start: at(25, 9), end: at(25, 12) },
  { id: '13', title: 'Fechamento do mês', start: at(31, 8), end: at(31, 18), tone: 'accent' },
]

/** Agenda */
export function Agenda() {
  return (
    <EventCalendar defaultView="agenda" defaultDate={ANCHOR} events={MONTH} label="Próximos compromissos" />
  )
}

const CLASH: CalendarEvent[] = [
  { id: 'a', title: 'Consulta de Ana Prado', start: at(17, 9), end: at(17, 12), tone: 'accent' },
  { id: 'b', title: 'Consulta de Bruno Lima', start: at(17, 9), end: at(17, 10) },
  { id: 'c', title: 'Retorno de Célia Dias', start: at(17, 9), end: at(17, 9, 30), tone: 'info' },
  { id: 'd', title: 'Encaixe de Davi Rocha', start: at(17, 9), end: at(17, 10, 30), tone: 'warning' },
  { id: 'e', title: 'Consulta de Eva Nunes', start: at(17, 10, 30), end: at(17, 11), tone: 'success' },
]

export function CustomEvent() {
  return (
    <EventCalendar
      defaultView="day"
      defaultDate={ANCHOR}
      events={CLASH}
      dayStart={8}
      dayEnd={13}
      label="Agenda do consultório"
      renderEvent={(event, info) => (
        <div className="flex min-w-0 flex-col gap-0.5">
          <span className="truncate font-medium">{event.title}</span>
          {info.shape === 'block' && <Badge size="sm">Confirmado</Badge>}
        </div>
      )}
    />
  )
}
```

### Carregando

```tsx
import { Badge, Button, EventCalendar, type CalendarEvent } from '@rivocode/ui'
import { useState } from 'react'

const ANCHOR = new Date(2026, 2, 17)

function at(day: number, hour: number, minute = 0) {
  return new Date(2026, 2, day, hour, minute)
}

const WEEK: CalendarEvent[] = [
  { id: '1', title: 'Reunião com o contador', start: at(16, 9), end: at(16, 10), tone: 'accent' },
  { id: '2', title: 'Fechamento da folha', start: at(16, 14), end: at(16, 16), tone: 'warning' },
  { id: '3', title: 'Diária da equipe', start: at(17, 9), end: at(17, 9, 30) },
  { id: '4', title: 'Almoço com o cliente', start: at(17, 12), end: at(17, 13, 30) },
  { id: '5', title: 'Revisão do orçamento', start: at(18, 10), end: at(18, 11, 30), tone: 'info' },
  { id: '6', title: 'Entrega da apuração', start: at(19, 15), end: at(19, 17), tone: 'danger' },
  { id: '7', title: 'Retrospectiva', start: at(20, 16), end: at(20, 17), tone: 'success' },
]

const MONTH: CalendarEvent[] = [
  ...WEEK,
  { id: '8', title: 'Vencimento do DAS', start: at(20, 0), end: at(21, 0), allDay: true, tone: 'danger' },
  { id: '9', title: 'Feira do setor', start: at(24, 0), end: at(27, 0), allDay: true, tone: 'info' },
  { id: '10', title: 'Consultoria', start: at(9, 14), end: at(9, 16) },
  { id: '11', title: 'Auditoria interna', start: at(10, 9), end: at(10, 18), tone: 'warning' },
  { id: '12', title: 'Treinamento', start: at(25, 9), end: at(25, 12) },
  { id: '13', title: 'Fechamento do mês', start: at(31, 8), end: at(31, 18), tone: 'accent' },
]

/** Agenda */
export function Agenda() {
  return (
    <EventCalendar defaultView="agenda" defaultDate={ANCHOR} events={MONTH} label="Próximos compromissos" />
  )
}

export function Loading() {
  return <EventCalendar defaultView="week" defaultDate={ANCHOR} isLoading label="Agenda da equipe" />
}
```

### Erro

```tsx
import { Badge, Button, EventCalendar, type CalendarEvent } from '@rivocode/ui'
import { useState } from 'react'

const ANCHOR = new Date(2026, 2, 17)

function at(day: number, hour: number, minute = 0) {
  return new Date(2026, 2, day, hour, minute)
}

const WEEK: CalendarEvent[] = [
  { id: '1', title: 'Reunião com o contador', start: at(16, 9), end: at(16, 10), tone: 'accent' },
  { id: '2', title: 'Fechamento da folha', start: at(16, 14), end: at(16, 16), tone: 'warning' },
  { id: '3', title: 'Diária da equipe', start: at(17, 9), end: at(17, 9, 30) },
  { id: '4', title: 'Almoço com o cliente', start: at(17, 12), end: at(17, 13, 30) },
  { id: '5', title: 'Revisão do orçamento', start: at(18, 10), end: at(18, 11, 30), tone: 'info' },
  { id: '6', title: 'Entrega da apuração', start: at(19, 15), end: at(19, 17), tone: 'danger' },
  { id: '7', title: 'Retrospectiva', start: at(20, 16), end: at(20, 17), tone: 'success' },
]

const MONTH: CalendarEvent[] = [
  ...WEEK,
  { id: '8', title: 'Vencimento do DAS', start: at(20, 0), end: at(21, 0), allDay: true, tone: 'danger' },
  { id: '9', title: 'Feira do setor', start: at(24, 0), end: at(27, 0), allDay: true, tone: 'info' },
  { id: '10', title: 'Consultoria', start: at(9, 14), end: at(9, 16) },
  { id: '11', title: 'Auditoria interna', start: at(10, 9), end: at(10, 18), tone: 'warning' },
  { id: '12', title: 'Treinamento', start: at(25, 9), end: at(25, 12) },
  { id: '13', title: 'Fechamento do mês', start: at(31, 8), end: at(31, 18), tone: 'accent' },
]

/** Agenda */
export function Agenda() {
  return (
    <EventCalendar defaultView="agenda" defaultDate={ANCHOR} events={MONTH} label="Próximos compromissos" />
  )
}

export function Error() {
  return (
    <EventCalendar
      defaultView="week"
      defaultDate={ANCHOR}
      isError
      onRetry={() => {}}
      errorTitle="Não foi possível carregar a agenda"
      errorMessage="O servidor não respondeu. Tente de novo em alguns minutos."
      label="Agenda da equipe"
    />
  )
}
```

### Vazio

```tsx
import { Badge, Button, EventCalendar, type CalendarEvent } from '@rivocode/ui'
import { useState } from 'react'

const ANCHOR = new Date(2026, 2, 17)

function at(day: number, hour: number, minute = 0) {
  return new Date(2026, 2, day, hour, minute)
}

const WEEK: CalendarEvent[] = [
  { id: '1', title: 'Reunião com o contador', start: at(16, 9), end: at(16, 10), tone: 'accent' },
  { id: '2', title: 'Fechamento da folha', start: at(16, 14), end: at(16, 16), tone: 'warning' },
  { id: '3', title: 'Diária da equipe', start: at(17, 9), end: at(17, 9, 30) },
  { id: '4', title: 'Almoço com o cliente', start: at(17, 12), end: at(17, 13, 30) },
  { id: '5', title: 'Revisão do orçamento', start: at(18, 10), end: at(18, 11, 30), tone: 'info' },
  { id: '6', title: 'Entrega da apuração', start: at(19, 15), end: at(19, 17), tone: 'danger' },
  { id: '7', title: 'Retrospectiva', start: at(20, 16), end: at(20, 17), tone: 'success' },
]

const MONTH: CalendarEvent[] = [
  ...WEEK,
  { id: '8', title: 'Vencimento do DAS', start: at(20, 0), end: at(21, 0), allDay: true, tone: 'danger' },
  { id: '9', title: 'Feira do setor', start: at(24, 0), end: at(27, 0), allDay: true, tone: 'info' },
  { id: '10', title: 'Consultoria', start: at(9, 14), end: at(9, 16) },
  { id: '11', title: 'Auditoria interna', start: at(10, 9), end: at(10, 18), tone: 'warning' },
  { id: '12', title: 'Treinamento', start: at(25, 9), end: at(25, 12) },
  { id: '13', title: 'Fechamento do mês', start: at(31, 8), end: at(31, 18), tone: 'accent' },
]

/** Agenda */
export function Agenda() {
  return (
    <EventCalendar defaultView="agenda" defaultDate={ANCHOR} events={MONTH} label="Próximos compromissos" />
  )
}

export function Empty() {
  return (
    <EventCalendar
      defaultView="week"
      defaultDate={ANCHOR}
      events={[]}
      label="Agenda da equipe"
      empty={{
        title: 'Semana livre',
        description: 'Nenhum compromisso marcado entre 16 e 22 de março.',
        action: <Button size="sm">Marcar compromisso</Button>,
      }}
    />
  )
}
```

## Props

| Prop | Tipo | Obrigatória | Desde | O que faz |
| --- | --- | --- | --- | --- |
| `classNames` | `Partial<Record<"band" \| "body" \| "cell" \| "column" \| "event" \| "gutter" \| "header" \| "section" \| "toolbar", string>>` |  | - | Classe por parte: `toolbar`, `body`, `header`, `gutter`, `column`, `event`, `band`, `cell`, `section`. |
| `date` | `Date` |  | - | A data ancora do periodo visivel, quando quem usa controla o estado. |
| `dayEnd` | `number` |  | - | A ultima hora desenhada. |
| `dayStart` | `number` |  | - | A primeira hora desenhada na grade de tempo. |
| `defaultDate` | `Date` |  | - | A data ancora inicial. |
| `defaultView` | `EventCalendarView` |  | - | A vista inicial, quando o componente controla o proprio estado. |
| `empty` | `{ title: ReactNode; description: ReactNode; action?: ReactNode; icon?: ReactNode; }` |  | - | O que aparece quando a consulta volta sem nada no periodo. |
| `errorMessage` | `ReactNode` |  | - |  |
| `errorTitle` | `ReactNode` |  | - | O titulo do aviso de erro. |
| `events` | `CalendarEvent[]` |  | - | Os compromissos ja expandidos: a peca nao expande recorrencia. |
| `hourHeight` | `number` |  | - | Quantos pixels vale uma hora. |
| `isError` | `boolean` |  | - |  |
| `isLoading` | `boolean` |  | - |  |
| `label` | `string` |  | - | O nome do calendario para o leitor de tela. |
| `maxColumns` | `number` |  | - | Quantas colunas um choque de horario pode abrir antes de virar "+N". |
| `maxHeight` | `string \| number` |  | - | Altura maxima da area que rola. |
| `maxLanes` | `number` |  | - | Quantas faixas cabem na barra de dia inteiro e na celula do mes. |
| `onDateChange` | `((date: Date) => void)` |  | - |  |
| `onEventSelect` | `((event: CalendarEvent) => void)` |  | - |  |
| `onRangeChange` | `((range: EventCalendarRange) => void)` |  | - | Avisa que o periodo visivel mudou, para o app buscar. |
| `onRetry` | `(() => void)` |  | - | Sem isto, o erro nao oferece nova tentativa. |
| `onSlotSelect` | `((range: EventCalendarRange) => void)` |  | - | Clique no vazio da grade, arredondado em meia hora. |
| `onViewChange` | `((view: EventCalendarView) => void)` |  | - |  |
| `renderEvent` | `((event: CalendarEvent, info: CalendarEventInfo) => ReactNode)` |  | - | Troca so o miolo da tarja; a caixa, o foco e o rotulo continuam da peca. |
| `retryLabel` | `ReactNode` |  | - | O nome do botao que executa o `onRetry`. |
| `view` | `EventCalendarView` |  | - | A vista mostrada, quando quem usa controla o estado. |
| `views` | `EventCalendarView[]` |  | - | Quais vistas aparecem no seletor. |
| `weekStartsOn` | `WeekStart` |  | - | 0 domingo, 1 segunda. |

Além dessas: repassa `className`, `style`, `id` e os demais atributos do elemento raiz.

## Ver também

- [Code](/componentes/code.md)
- [RelativeTime](/componentes/relative-time.md)
- [Timeline](/componentes/timeline.md)
- [Tracker](/componentes/tracker.md)
- [Convenções da biblioteca](/convencoes.md): Provider, tokens e as regras que valem para toda peça
- [Índice completo](/llms.txt)
