@softize/opus 18.5.0 → 20.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/CHANGELOG.md +275 -0
  2. package/bin/cli.mjs +5 -0
  3. package/bin/lib/check.mjs +198 -10
  4. package/bin/lib/copy.mjs +29 -0
  5. package/bin/lib/mcp.mjs +1 -1
  6. package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +298 -0
  7. package/docs/adr/0024-fill-height-propagates-by-context.md +118 -0
  8. package/docs/data-products.md +15 -9
  9. package/docs/protocol.md +3 -4
  10. package/docs/releasing.md +2 -2
  11. package/package.json +2 -2
  12. package/registry/skills/create-opus-action/scripts/scaffold.mjs +2 -1
  13. package/registry/templates/app/package.json +1 -1
  14. package/src/core/runtime.ts +96 -8
  15. package/src/core/types.ts +5 -2
  16. package/src/ui/components/patterns/action-form-card.tsx +11 -1
  17. package/src/ui/components/patterns/content-header.tsx +16 -12
  18. package/src/ui/components/patterns/fill-region.ts +150 -0
  19. package/src/ui/components/patterns/heading-level.ts +16 -0
  20. package/src/ui/components/patterns/list.tsx +103 -73
  21. package/src/ui/components/patterns/page.tsx +20 -4
  22. package/src/ui/components/patterns/pane.tsx +294 -0
  23. package/src/ui/components/patterns/presentation.tsx +81 -54
  24. package/src/ui/components/patterns/sidebar.tsx +13 -72
  25. package/src/ui/components/patterns/split.tsx +51 -38
  26. package/src/ui/components/patterns/surface-anatomy.ts +246 -0
  27. package/src/ui/components/patterns/surface-header.tsx +51 -6
  28. package/src/ui/components/patterns/surface-media.tsx +69 -0
  29. package/src/ui/components/patterns/trigger.tsx +12 -2
  30. package/src/ui/components/primitives/alert.tsx +9 -1
  31. package/src/ui/components/primitives/avatar.tsx +7 -3
  32. package/src/ui/components/primitives/card.tsx +91 -29
  33. package/src/ui/components/primitives/carousel.tsx +34 -5
  34. package/src/ui/components/primitives/checkbox.tsx +11 -3
  35. package/src/ui/components/primitives/command.tsx +14 -5
  36. package/src/ui/components/primitives/copyable.tsx +120 -10
  37. package/src/ui/components/primitives/detail.tsx +10 -13
  38. package/src/ui/components/primitives/dialog.tsx +179 -45
  39. package/src/ui/components/primitives/drawer.tsx +131 -24
  40. package/src/ui/components/primitives/empty.tsx +1 -1
  41. package/src/ui/components/primitives/icon-picker.tsx +6 -2
  42. package/src/ui/components/primitives/icon-tile.tsx +7 -0
  43. package/src/ui/components/primitives/input-group.tsx +9 -2
  44. package/src/ui/components/primitives/input-otp.tsx +1 -1
  45. package/src/ui/components/primitives/kbd.tsx +1 -1
  46. package/src/ui/components/primitives/label.tsx +3 -0
  47. package/src/ui/components/primitives/menu.tsx +5 -0
  48. package/src/ui/components/primitives/metric-card.tsx +98 -39
  49. package/src/ui/components/primitives/popover.tsx +1 -1
  50. package/src/ui/components/primitives/progress.tsx +9 -1
  51. package/src/ui/components/primitives/slider.tsx +62 -11
  52. package/src/ui/components/primitives/truncate.tsx +10 -1
  53. package/src/ui/docs/content/accordion.md +74 -27
  54. package/src/ui/docs/content/action-form-card.md +24 -15
  55. package/src/ui/docs/content/action-form-dialog.md +17 -7
  56. package/src/ui/docs/content/action-form.md +92 -77
  57. package/src/ui/docs/content/action-list-dialog.md +75 -38
  58. package/src/ui/docs/content/action-list.md +134 -52
  59. package/src/ui/docs/content/action-trigger.md +27 -10
  60. package/src/ui/docs/content/action-view.md +14 -14
  61. package/src/ui/docs/content/actions.md +161 -66
  62. package/src/ui/docs/content/ai.md +43 -44
  63. package/src/ui/docs/content/alert.md +83 -29
  64. package/src/ui/docs/content/ask.md +51 -26
  65. package/src/ui/docs/content/aspect-ratio.md +22 -24
  66. package/src/ui/docs/content/audit.md +23 -19
  67. package/src/ui/docs/content/auth.md +38 -19
  68. package/src/ui/docs/content/avatar.md +54 -5
  69. package/src/ui/docs/content/badge.md +29 -9
  70. package/src/ui/docs/content/breadcrumb.md +21 -8
  71. package/src/ui/docs/content/button.md +73 -43
  72. package/src/ui/docs/content/calendar.md +134 -26
  73. package/src/ui/docs/content/card.md +252 -35
  74. package/src/ui/docs/content/carousel.md +37 -6
  75. package/src/ui/docs/content/chat.md +118 -37
  76. package/src/ui/docs/content/checkbox.md +73 -7
  77. package/src/ui/docs/content/cli.md +94 -55
  78. package/src/ui/docs/content/collapsible.md +43 -19
  79. package/src/ui/docs/content/command.md +30 -8
  80. package/src/ui/docs/content/composer.md +23 -8
  81. package/src/ui/docs/content/content.md +37 -7
  82. package/src/ui/docs/content/copyable.md +39 -8
  83. package/src/ui/docs/content/customization.md +55 -46
  84. package/src/ui/docs/content/cycle.md +33 -18
  85. package/src/ui/docs/content/data-products.md +115 -0
  86. package/src/ui/docs/content/data-state.md +20 -6
  87. package/src/ui/docs/content/data.md +20 -19
  88. package/src/ui/docs/content/detail.md +12 -5
  89. package/src/ui/docs/content/dialog.md +219 -28
  90. package/src/ui/docs/content/dictionary-value.md +4 -4
  91. package/src/ui/docs/content/dock.md +54 -19
  92. package/src/ui/docs/content/dot.md +35 -5
  93. package/src/ui/docs/content/drawer.md +193 -22
  94. package/src/ui/docs/content/empty-value.md +20 -6
  95. package/src/ui/docs/content/empty.md +24 -3
  96. package/src/ui/docs/content/events.md +76 -16
  97. package/src/ui/docs/content/executions.md +9 -9
  98. package/src/ui/docs/content/field.md +21 -6
  99. package/src/ui/docs/content/getting-started.md +44 -34
  100. package/src/ui/docs/content/icon-picker.md +19 -13
  101. package/src/ui/docs/content/icon-tile.md +28 -5
  102. package/src/ui/docs/content/input-otp.md +58 -7
  103. package/src/ui/docs/content/input.md +22 -3
  104. package/src/ui/docs/content/item.md +53 -6
  105. package/src/ui/docs/content/kbd.md +5 -1
  106. package/src/ui/docs/content/label.md +13 -4
  107. package/src/ui/docs/content/log.md +5 -5
  108. package/src/ui/docs/content/markdown.md +5 -4
  109. package/src/ui/docs/content/mcp.md +49 -23
  110. package/src/ui/docs/content/menu.md +95 -10
  111. package/src/ui/docs/content/metric-card.md +68 -21
  112. package/src/ui/docs/content/observability.md +10 -9
  113. package/src/ui/docs/content/page.md +125 -11
  114. package/src/ui/docs/content/pagination.md +13 -5
  115. package/src/ui/docs/content/pane.md +300 -0
  116. package/src/ui/docs/content/popover.md +42 -5
  117. package/src/ui/docs/content/presentation.md +91 -47
  118. package/src/ui/docs/content/progress.md +27 -14
  119. package/src/ui/docs/content/queue.md +39 -21
  120. package/src/ui/docs/content/radio-group.md +26 -4
  121. package/src/ui/docs/content/router.md +29 -10
  122. package/src/ui/docs/content/runtime.md +90 -54
  123. package/src/ui/docs/content/scheduler.md +53 -21
  124. package/src/ui/docs/content/scroll-area.md +42 -35
  125. package/src/ui/docs/content/select.md +78 -33
  126. package/src/ui/docs/content/semantic-context.md +21 -2
  127. package/src/ui/docs/content/separator.md +15 -9
  128. package/src/ui/docs/content/sidebar.md +25 -45
  129. package/src/ui/docs/content/skeleton.md +13 -4
  130. package/src/ui/docs/content/slider.md +37 -9
  131. package/src/ui/docs/content/spinner.md +4 -3
  132. package/src/ui/docs/content/split.md +34 -20
  133. package/src/ui/docs/content/storage.md +8 -9
  134. package/src/ui/docs/content/switch.md +10 -7
  135. package/src/ui/docs/content/table.md +46 -8
  136. package/src/ui/docs/content/tabs.md +14 -4
  137. package/src/ui/docs/content/testing.md +76 -31
  138. package/src/ui/docs/content/textarea.md +5 -4
  139. package/src/ui/docs/content/toast.md +48 -7
  140. package/src/ui/docs/content/toggle.md +3 -3
  141. package/src/ui/docs/content/tokens.md +15 -26
  142. package/src/ui/docs/content/tooltip.md +18 -2
  143. package/src/ui/docs/content/truncate.md +36 -6
  144. package/src/ui/docs/content/ui.md +74 -22
  145. package/src/ui/docs/content/upgrading.md +45 -36
  146. package/src/ui/docs/plugin.ts +36 -2
  147. package/src/ui/docs/registry.tsx +14 -2
  148. package/src/ui/drivers/react.tsx +16 -8
  149. package/src/ui/meta.ts +38 -32
  150. package/src/ui/react.tsx +14 -0
  151. package/src/vite/design.ts +16 -4
@@ -1,10 +1,11 @@
1
- O calendário fala pt-BR por padrão: nomes de mês e de dia vêm do `locale` (`ptBR` de
2
- `date-fns/locale`) e os controles de navegação têm rótulos acessíveis em português. Passe outro
3
- `locale` para trocar o idioma; `labels` sobrescreve rótulos individualmente.
4
-
5
1
  ## Dia único
6
2
 
7
- mode=single guarda uma Date — controle por selected/onSelect. defaultMonth abre o calendário no mês certo sem mexer na seleção.
3
+ Use `Calendar` quando a pessoa precisar escolher datas vendo o mês inteiro, como a data de uma
4
+ entrega ou a janela de um relatório. Para abrir o calendário a partir de um campo, ancore-o em um
5
+ [`Popover`](/ui/popover).
6
+
7
+ Com `mode="single"`, a seleção é uma `Date`, controlada por `selected` e `onSelect`. `defaultMonth`
8
+ abre o calendário no mês desejado sem alterar a seleção.
8
9
 
9
10
  ```tsx preview
10
11
  const [day, setDay] = useState<Date | undefined>(new Date(2026, 5, 22))
@@ -19,9 +20,14 @@ render(
19
20
  )
20
21
  ```
21
22
 
23
+ O calendário usa pt-BR por padrão: os nomes de mês e de dia vêm do `locale` (`ptBR`, de
24
+ `date-fns/locale`) e os controles de navegação têm nomes acessíveis em português. Passe outro
25
+ `locale` para trocar o idioma; `labels` substitui nomes acessíveis individualmente.
26
+
22
27
  ## Intervalo
23
28
 
24
- mode=range guarda { from, to } — o padrão para filtrar sessões por janela de datas. O primeiro clique fixa o início; o segundo, o fim.
29
+ Com `mode="range"`, a seleção é um intervalo `{ from, to }`, o formato comum para filtrar registros
30
+ por período. O primeiro clique define o início e o segundo, o fim.
25
31
 
26
32
  ```tsx preview
27
33
  const [week, setWeek] = useState<DateRange | undefined>({
@@ -39,9 +45,73 @@ render(
39
45
  )
40
46
  ```
41
47
 
42
- ## Navegação por dropdown
48
+ Para um intervalo que atravessa meses, `numberOfMonths` mostra meses lado a lado.
49
+
50
+ ```tsx preview
51
+ const [range, setRange] = useState<DateRange | undefined>({
52
+ from: new Date(2026, 5, 24),
53
+ to: new Date(2026, 6, 8),
54
+ })
55
+
56
+ render(
57
+ <Calendar
58
+ mode="range"
59
+ numberOfMonths={2}
60
+ selected={range}
61
+ onSelect={setRange}
62
+ defaultMonth={new Date(2026, 5, 1)}
63
+ />,
64
+ )
65
+ ```
66
+
67
+ ## Vários dias
43
68
 
44
- captionLayout=dropdown troca o título do mês por seletores de mês e ano — bom para pular para um período distante (ex.: histórico de um repositório) sem clicar mês a mês.
69
+ Com `mode="multiple"`, a seleção é uma lista de datas, e cada clique marca ou desmarca um dia. Use
70
+ esse modo quando os dias escolhidos não formarem um intervalo contínuo, como os plantões de uma
71
+ equipe.
72
+
73
+ ```tsx preview
74
+ const [days, setDays] = useState<Date[] | undefined>([
75
+ new Date(2026, 5, 3),
76
+ new Date(2026, 5, 10),
77
+ new Date(2026, 5, 17),
78
+ ])
79
+
80
+ render(
81
+ <Calendar
82
+ mode="multiple"
83
+ selected={days}
84
+ onSelect={setDays}
85
+ defaultMonth={new Date(2026, 5, 1)}
86
+ />,
87
+ )
88
+ ```
89
+
90
+ ## Datas indisponíveis
91
+
92
+ `disabled` impede a escolha de datas específicas. Ele aceita uma data, uma lista, um intervalo
93
+ `{ from, to }`, um predicado ou combinações como `{ before: data }` e `{ dayOfWeek: [0, 6] }`. No
94
+ exemplo, dias anteriores a 10 de junho e fins de semana ficam indisponíveis.
95
+
96
+ ```tsx preview
97
+ const [day, setDay] = useState<Date | undefined>()
98
+
99
+ render(
100
+ <Calendar
101
+ mode="single"
102
+ selected={day}
103
+ onSelect={setDay}
104
+ defaultMonth={new Date(2026, 5, 1)}
105
+ disabled={[{ before: new Date(2026, 5, 10) }, { dayOfWeek: [0, 6] }]}
106
+ />,
107
+ )
108
+ ```
109
+
110
+ ## Navegação por seletores
111
+
112
+ `captionLayout="dropdown"` troca o título do mês por seletores de mês e ano. Use essa forma quando a
113
+ pessoa precisar chegar a um período distante, como o histórico de anos anteriores, sem avançar mês
114
+ a mês.
45
115
 
46
116
  ```tsx preview
47
117
  <Calendar
@@ -51,25 +121,63 @@ captionLayout=dropdown troca o título do mês por seletores de mês e ano — b
51
121
  />
52
122
  ```
53
123
 
124
+ ## Personalizar o dia
125
+
126
+ Cada dia é um `CalendarDayButton`: um `Button` `ghost` quadrado que recebe os modificadores do dia,
127
+ como `selected`, `range_start` e `today`, e cuida do foco e dos estados de seleção. Para decorar o
128
+ dia, como marcar datas com evento, substitua o componente por `components={{ DayButton: … }}` e
129
+ renderize `CalendarDayButton` por dentro, em vez de reimplementar esse comportamento.
130
+
131
+ ```tsx preview
132
+ const eventDays = [5, 12, 26]
133
+
134
+ render(
135
+ <Calendar
136
+ mode="single"
137
+ defaultMonth={new Date(2026, 5, 1)}
138
+ components={{
139
+ DayButton: ({ children, ...props }) => (
140
+ <CalendarDayButton {...props}>
141
+ {children}
142
+ {eventDays.includes(props.day.date.getDate()) &&
143
+ props.day.date.getMonth() === 5 && (
144
+ <span aria-hidden="true" className="size-1 rounded-full bg-primary" />
145
+ )}
146
+ </CalendarDayButton>
147
+ ),
148
+ }}
149
+ />,
150
+ )
151
+ ```
152
+
153
+ O marcador do exemplo é decorativo. Quando a informação importar para a decisão, inclua-a também
154
+ no nome acessível do dia por `labels.labelDayButton`.
155
+
54
156
  ## Propriedades de Calendar
55
157
 
56
158
  | Propriedade | Tipo | Padrão | Descrição |
57
159
  |---|---|---|---|
58
- | `mode` | `'single' \| 'multiple' \| 'range'` | | O tipo de seleção — define o formato de selected/onSelect (Date, Date[] ou { from, to }). |
59
- | `selected` | `Date \| Date[] \| DateRange` | | A seleção atual no modo controlado — o formato segue o mode. Pareie com onSelect. |
60
- | `onSelect` | `(selected) => void` | | Chamado quando o usuário escolhe uma data. O argumento segue o mode. |
61
- | `defaultMonth` | `Date` | | O mês exibido ao montar, sem afetar a seleção. |
62
- | `captionLayout` | `'label' \| 'dropdown' \| 'dropdown-months' \| 'dropdown-years'` | `'label'` | Como o título do mês aparece — label é texto fixo; dropdown vira seletores de mês e ano. |
63
- | `numberOfMonths` | `number` | `1` | Quantos meses mostrar lado a lado — útil para escolher um intervalo longo. |
64
- | `disabled` | `Matcher` | | Datas não selecionáveis — uma Date, um array, um { from, to } ou um predicado (date) => boolean. |
65
- | `buttonVariant` | `Button['variant']` | `'ghost'` | A variante dos botões de navegação (anterior/próximo). |
66
- | `showOutsideDays` | `boolean` | `true` | Mostra os dias do mês vizinho que completam a primeira e a última semana. |
67
- | `locale` | `Locale` | `ptBR` | Idioma dos nomes de mês e de dia (um locale do date-fns). |
68
- | `labels` | `Partial<Labels>` | rótulos em pt-BR | Rótulos acessíveis da navegação e dos seletores; mescla sobre o padrão. |
69
-
70
- ## Personalizar o dia com CalendarDayButton
71
-
72
- Cada dia é um `CalendarDayButton` — um `Button` ghost quadrado que recebe os modificadores do
73
- dia (`selected`, `range-start`, `today`…). Use `components={{ DayButton: … }}` para
74
- decorar o dia (um marcador de evento, por exemplo) partindo dele em vez de reimplementar o
75
- foco e a seleção.
160
+ | `mode` | `'single' \| 'multiple' \| 'range'` | | Tipo de seleção. Define o formato de `selected` e `onSelect`: `Date`, `Date[]` ou `{ from, to }`. |
161
+ | `selected` | `Date \| Date[] \| DateRange` | | Seleção atual no modo controlado, no formato do `mode`. Use com `onSelect`. |
162
+ | `onSelect` | `(selected) => void` | | Chamado quando a pessoa escolhe uma data. O argumento segue o formato do `mode`. |
163
+ | `defaultMonth` | `Date` | | Mês exibido ao montar, sem afetar a seleção. |
164
+ | `captionLayout` | `'label' \| 'dropdown' \| 'dropdown-months' \| 'dropdown-years'` | `'label'` | Apresentação do título do mês: texto fixo ou seletores de mês, de ano ou de ambos. |
165
+ | `numberOfMonths` | `number` | `1` | Quantidade de meses exibidos lado a lado. |
166
+ | `disabled` | `Matcher \| Matcher[]` | | Datas que não podem ser escolhidas: uma data, uma lista, um intervalo, um predicado ou regras como `{ before }` e `{ dayOfWeek }`. |
167
+ | `buttonVariant` | `ButtonVariant` | `'ghost'` | Variante dos botões de mês anterior e próximo mês. |
168
+ | `showOutsideDays` | `boolean` | `true` | Mostra os dias dos meses vizinhos que completam a primeira e a última semana. |
169
+ | `locale` | `Locale` | `ptBR` | Idioma dos nomes de mês e de dia, como um locale do date-fns. |
170
+ | `labels` | `Partial<Labels>` | nomes em pt-BR | Nomes acessíveis da navegação, dos seletores e dos dias; mesclados sobre o padrão. |
171
+ | `components` | `Partial<CustomComponents>` | | Substitui partes da grade, como `DayButton`. |
172
+
173
+ `Calendar` repassa as demais propriedades ao `DayPicker` do react-day-picker.
174
+
175
+ ## Propriedades de CalendarDayButton
176
+
177
+ | Propriedade | Tipo | Padrão | Descrição |
178
+ |---|---|---|---|
179
+ | `day` | `CalendarDay` | | O dia renderizado; `day.date` é a data. Recebido do `Calendar`. |
180
+ | `modifiers` | `Modifiers` | | Estados do dia, como `selected`, `today`, `disabled`, `range_start` e `range_end`. Recebido do `Calendar`. |
181
+ | `className` | `string` | | Classes somadas ao botão do dia. |
182
+
183
+ `CalendarDayButton` aceita também as props nativas de `<button>`, repassadas pelo `Calendar`.
@@ -1,75 +1,292 @@
1
- ## Box simples
1
+ ## Caixa simples
2
2
 
3
- O uso mais comum: a superfície de conteúdo em repouso (`rounded-xl` + `border`), flat por padrão, com o padding pelo `className`. O Card não força flex/gap/padding — você compõe o miolo (sem brigar com `space-y`/`flex` seu). Quando a hierarquia realmente pedir elevação, declare a intensidade explicitamente, por exemplo `className="shadow-sm"`.
3
+ O uso mais comum é a superfície em repouso: raio, borda e fundo, sem sombra. O `Card` não impõe
4
+ flex, gap nem padding, então o miolo continua seu — inclusive o `space-y` ou o grid que a tela já
5
+ usa. Nesse formato o recuo vem do `className`.
4
6
 
5
7
  ```tsx preview col
6
8
  <Card className="p-4">
7
- <p className="text-sm text-muted-foreground">3 sessões ativas neste momento.</p>
9
+ <p className="text-sm text-muted-foreground">3 veículos aguardando retirada.</p>
8
10
  </Card>
9
11
  ```
10
12
 
11
- ## Estruturado (header / conteúdo / rodapé)
13
+ Quando a hierarquia realmente pedir elevação, declare a intensidade, por exemplo
14
+ `className="shadow-sm"`.
12
15
 
13
- Para painel com estrutura: cada slot é dono do próprio padding (como o Dialog). CardTitle/CardDescription no header; CardFooter alinha as ações.
16
+ ## Cabeçalho, corpo e rodapé
17
+
18
+ Quando o card tem estrutura, cada região vira um slot e traz o próprio recuo. `CardHeader`
19
+ reconhece quatro slots — `CardMedia`, `CardTitle`, `CardDescription` e `CardAction` — e monta a
20
+ linha sozinho: mídia, título e descrição de um lado, ações no extremo oposto. A ordem em que os
21
+ slots são escritos não altera a posição.
14
22
 
15
23
  ```tsx preview col
16
24
  <Card>
17
25
  <CardHeader>
18
- <CardTitle>Empresa X</CardTitle>
19
- <CardDescription>2 repositórios · 3 agentes vinculados.</CardDescription>
26
+ <CardTitle>Oficina Centro</CardTitle>
27
+ <CardDescription>4 boxes · 2 consultores de plantão.</CardDescription>
20
28
  </CardHeader>
21
29
  <CardBody>
22
- <p className="text-sm text-muted-foreground">Última sessão concluída há 2 horas.</p>
30
+ <p className="text-sm text-muted-foreground">Próximo horário livre hoje, às 14h.</p>
23
31
  </CardBody>
24
- <CardFooter className="gap-2">
25
- <Button size="sm">Abrir sessão</Button>
26
- <Button size="sm" variant="outline">Ver repositórios</Button>
32
+ <CardFooter>
33
+ <Button size="sm" variant="outline">Ver agenda</Button>
34
+ <Button size="sm">Agendar revisão</Button>
27
35
  </CardFooter>
28
36
  </Card>
29
37
  ```
30
38
 
31
- ## Com divisor
39
+ O rodapé alinha as ações no fim da linha. Um nó que não seja um desses quatro slots dentro do
40
+ `CardHeader` interrompe a renderização com um erro: a linha do cabeçalho pertence à família, e um
41
+ `div` solto ali devolveria à tela a decisão de posicionamento. Conteúdo livre vai para `CardBody`.
42
+
43
+ ## Escolher entre contínuo e seccionado
44
+
45
+ `layout` declara a anatomia da superfície inteira e fica no root. Não é uma borda ligada ou
46
+ desligada: cada valor é um pacote de recuo, ritmo vertical, escala do título e divisores.
32
47
 
33
- `border-b` no header / `border-t` no footer dão o divisor quando a estrutura pede separação visível. A aresta externa e os divisores internos usam o mesmo vocabulário de `border`.
48
+ | Aspecto | `continuous` (padrão) | `sectioned` |
49
+ | --- | --- | --- |
50
+ | Divisores | nenhum | entre as regiões vizinhas |
51
+ | Ritmo vertical | o cabeçalho se aproxima do corpo; o espaço separa | regiões simétricas; o divisor separa |
52
+ | Recuo lateral | mais folgado, para leitura | o mesmo das demais superfícies de operação |
53
+ | Escala do título | um degrau acima | um degrau mais contida |
54
+
55
+ Use `continuous` no card de leitura, que apresenta um assunto e termina. Use `sectioned` quando o
56
+ card for uma superfície de operação — regiões com pesos diferentes, lista dentro do corpo, rodapé
57
+ com ações — ou quando ele precisar conviver com um [`Dialog`](/ui/dialog), um
58
+ [`Drawer`](/ui/drawer) ou um [`Pane`](/ui/pane) sem degrau visível entre eles.
34
59
 
35
60
  ```tsx preview col
36
- <Card>
37
- <CardHeader className="border-b">
38
- <CardTitle>Skill code-style</CardTitle>
39
- <CardDescription>Padrão softize de código e microcopy.</CardDescription>
61
+ render(
62
+ <div className="grid grid-cols-2 gap-4">
63
+ <Card>
64
+ <CardHeader>
65
+ <CardTitle>Contínuo</CardTitle>
66
+ <CardDescription>O espaço separa as regiões.</CardDescription>
67
+ </CardHeader>
68
+ <CardBody>
69
+ <p className="text-sm text-muted-foreground">Um assunto, lido de cima a baixo.</p>
70
+ </CardBody>
71
+ <CardFooter>
72
+ <Button size="sm" variant="outline">Abrir</Button>
73
+ </CardFooter>
74
+ </Card>
75
+ <Card layout="sectioned">
76
+ <CardHeader>
77
+ <CardTitle>Seccionado</CardTitle>
78
+ <CardDescription>O divisor separa as regiões.</CardDescription>
79
+ </CardHeader>
80
+ <CardBody>
81
+ <p className="text-sm text-muted-foreground">Regiões com pesos diferentes.</p>
82
+ </CardBody>
83
+ <CardFooter>
84
+ <Button size="sm" variant="outline">Abrir</Button>
85
+ </CardFooter>
86
+ </Card>
87
+ </div>,
88
+ );
89
+ ```
90
+
91
+ ### Os divisores são derivados
92
+
93
+ Em `sectioned`, cada região desenha a própria linha quando existe uma região vizinha do outro lado.
94
+ Um cabeçalho sozinho não fecha nada, um rodapé que é a única região também não, e um cabeçalho
95
+ seguido direto de um rodapé produz uma linha só, em vez de duas encostadas.
96
+
97
+ Por isso, não escreva `border-b` no cabeçalho nem `border-t` no rodapé: a regra depende da presença
98
+ das regiões irmãs, que o componente conhece e a tela não. Uma borda escrita à mão ignora quais
99
+ regiões estão presentes: `border-b` num cabeçalho sozinho desenha uma linha encostada na borda do
100
+ card, e `border-t` no corpo duplica a linha que o cabeçalho já desenha. Da mesma forma,
101
+ `border-b-0` não apaga a linha derivada. Para um card sem divisores, use `layout="continuous"`.
102
+
103
+ ## Mídia e ações no cabeçalho
104
+
105
+ `CardMedia` abre a linha com a moldura de ícone do Opus — o mesmo [`IconTile`](/ui/icon-tile), na
106
+ escala `sm`, com `context` e `variant` para declarar o significado da cor. `CardAction` fica no
107
+ extremo oposto ao título, sem precisar de classe de alinhamento.
108
+
109
+ ```tsx preview col
110
+ <Card layout="sectioned">
111
+ <CardHeader>
112
+ <CardMedia context="primary">
113
+ <CarFront />
114
+ </CardMedia>
115
+ <CardTitle>Revisão dos 20.000 km</CardTitle>
116
+ <CardDescription>Agendada para 12 de maio, às 9h.</CardDescription>
117
+ <CardAction>
118
+ <Button size="sm" variant="outline">Reagendar</Button>
119
+ </CardAction>
40
120
  </CardHeader>
41
121
  <CardBody>
42
- <p className="text-sm text-muted-foreground">Vinculada a 4 agentes neste workspace.</p>
122
+ <p className="text-sm text-muted-foreground">Consultor responsável: Marina Alves.</p>
43
123
  </CardBody>
44
- <CardFooter className="justify-between border-t">
45
- <Badge context="success">Publicada</Badge>
46
- <Button size="sm" variant="outline">Editar skill</Button>
47
- </CardFooter>
48
124
  </Card>
49
125
  ```
50
126
 
51
- ## Ação no cabeçalho
127
+ ## Hierarquia do título
52
128
 
53
- `CardAction` é o slot de ação do header (um botão ou menu). Quem compõe posiciona — em geral um
54
- `CardHeader` em `flex-row` com o título de um lado e a ação do outro.
129
+ `CardTitle` é um heading. `level` escolhe a hierarquia do documento, de `1` a `6`, com `3` como
130
+ padrão — um card costuma ser uma seção dentro de uma página que já tem o `h1`. O tamanho visual não
131
+ vem daí: ele é decidido pela família e pelo `layout`, então corrigir o nível para acertar a
132
+ estrutura do documento não muda a aparência.
55
133
 
56
134
  ```tsx preview col
57
135
  <Card>
58
- <CardHeader className="flex-row items-start justify-between">
59
- <div>
60
- <CardTitle>Empresa X</CardTitle>
61
- <CardDescription>3 agentes vinculados.</CardDescription>
62
- </div>
63
- <CardAction>
64
- <Button size="sm" variant="outline">Editar</Button>
65
- </CardAction>
136
+ <CardHeader>
137
+ <CardTitle level={2}>Qualidade da base</CardTitle>
138
+ <CardDescription>O nível muda a semântica; a escala continua a do layout.</CardDescription>
139
+ </CardHeader>
140
+ </Card>
141
+ ```
142
+
143
+ Quando o título leva a outra tela, coloque o link dentro dele. O heading continua sendo o
144
+ `CardTitle`, e o link fica no seu conteúdo:
145
+
146
+ ```tsx preview col
147
+ <Card>
148
+ <CardHeader>
149
+ <CardTitle>
150
+ <a href="#" className="hover:underline">Revisão dos 20.000 km</a>
151
+ </CardTitle>
152
+ <CardDescription>Agendada para 12 de maio, às 9h.</CardDescription>
66
153
  </CardHeader>
67
154
  </Card>
68
155
  ```
69
156
 
157
+ Use `asChild` só quando outro componente já renderiza o próprio heading e precisa receber a escala do
158
+ título. Com `asChild`, o elemento é o filho: `level` deixa de valer, e um link passado ali substitui
159
+ o heading em vez de ficar dentro dele.
160
+
161
+ ## A matriz de slots
162
+
163
+ As regiões são independentes: o cabeçalho existe sem corpo, o corpo existe sem cabeçalho e o rodapé
164
+ entra apenas quando há ações persistentes. Os divisores acompanham as regiões presentes, e o corpo
165
+ reassume o respiro de cima ou de baixo quando é a primeira ou a última região. No layout contínuo, o
166
+ cabeçalho só se aproxima da região seguinte quando ela existe: sozinho, ele fecha o card com o mesmo
167
+ respiro que tem em cima.
168
+
169
+ ```tsx preview col
170
+ render(
171
+ <div className="grid grid-cols-2 gap-4">
172
+ <Card>
173
+ <CardHeader>
174
+ <CardMedia>
175
+ <UserRound />
176
+ </CardMedia>
177
+ <CardTitle>Contínuo com mídia</CardTitle>
178
+ </CardHeader>
179
+ <CardBody>
180
+ <p className="text-sm text-muted-foreground">
181
+ Sem rodapé, o corpo reassume o respiro de baixo.
182
+ </p>
183
+ </CardBody>
184
+ </Card>
185
+ <Card>
186
+ <CardBody>
187
+ <p className="text-sm text-muted-foreground">
188
+ Só corpo: o card vira uma caixa com o recuo da família.
189
+ </p>
190
+ </CardBody>
191
+ </Card>
192
+ <Card layout="sectioned">
193
+ <CardHeader>
194
+ <CardTitle>Seccionado sem corpo</CardTitle>
195
+ <CardAction>
196
+ <Button size="sm" variant="ghost">Editar</Button>
197
+ </CardAction>
198
+ </CardHeader>
199
+ <CardFooter>
200
+ <Button size="sm">Salvar</Button>
201
+ </CardFooter>
202
+ </Card>
203
+ <Card layout="sectioned">
204
+ <CardHeader>
205
+ <CardMedia context="success">
206
+ <ShieldCheck />
207
+ </CardMedia>
208
+ <CardTitle>Seccionado completo</CardTitle>
209
+ <CardDescription>Cabeçalho, corpo e rodapé.</CardDescription>
210
+ </CardHeader>
211
+ <CardBody>
212
+ <p className="text-sm text-muted-foreground">Cada fronteira ganha uma linha.</p>
213
+ </CardBody>
214
+ <CardFooter>
215
+ <Button size="sm" variant="outline">Descartar</Button>
216
+ <Button size="sm">Publicar</Button>
217
+ </CardFooter>
218
+ </Card>
219
+ </div>,
220
+ );
221
+ ```
222
+
223
+ No layout contínuo, o respiro de baixo de um cabeçalho sem corpo nem rodapé segue o de cima. Essa
224
+ regra só vale quando o cabeçalho é a última região e tem precedência sobre o `className`, então um
225
+ `pb-*` no `CardHeader` não altera esse recuo. Para um recuo diferente, escolha o `layout`: no
226
+ `sectioned`, as regiões usam o recuo simétrico e mais contido das superfícies de operação.
227
+
70
228
  ## Propriedades de Card
71
229
 
72
230
  | Propriedade | Tipo | Padrão | Descrição |
73
231
  |---|---|---|---|
74
- | `asChild` | `boolean` | `false` | Renderiza o filho com a superfície do Card (ex.: um `<button>` clicável inteiro). |
75
- | `className` | `string` | | Compõe sobre a superfície; os slots (`CardHeader`, `CardBody`, `CardFooter`, `CardAction`) são donos do próprio padding. `CardContent` é alias depreciado de `CardBody`. |
232
+ | `layout` | `'continuous' \| 'sectioned'` | `'continuous'` | Anatomia da superfície inteira; os slots leem esse valor e não o recebem. |
233
+ | `asChild` | `boolean` | `false` | Renderiza o filho com a superfície do Card, por exemplo um `<button>` clicável inteiro. |
234
+ | `className` | `string` | | Compõe sobre a superfície. Sem slots, é aqui que entra o recuo da caixa. |
235
+
236
+ As demais propriedades são as propriedades nativas de `div`.
237
+
238
+ ## Propriedades de CardHeader
239
+
240
+ | Propriedade | Tipo | Padrão | Descrição |
241
+ |---|---|---|---|
242
+ | `children` | `ReactNode` | | Somente `CardMedia`, `CardTitle`, `CardDescription` e `CardAction`, no máximo um de cada. |
243
+ | `className` | `string` | | Ajusta a faixa superior sobre o recuo do layout. |
244
+
245
+ ## Propriedades de CardMedia
246
+
247
+ | Propriedade | Tipo | Padrão | Descrição |
248
+ |---|---|---|---|
249
+ | `size` | `'xs' \| 'sm' \| 'default' \| 'lg'` | `'sm'` | Tamanho da moldura e do glifo na escala compartilhada dos controles. |
250
+ | `context` | `'neutral' \| 'primary' \| 'info' \| 'success' \| 'warning' \| 'danger'` | `'neutral'` | Declara o significado da cor. |
251
+ | `variant` | `'subtle' \| 'outline'` | `'subtle'` | Usa preenchimento tonal ou acrescenta borda. |
252
+ | `className` | `string` | | Ajusta a moldura sem substituir a escala. |
253
+ | `asChild` | `boolean` | `false` | Cede o elemento para uma marca com visual próprio — um orbe, um logotipo — que ocupa o slot sem ganhar a moldura por cima. |
254
+
255
+ ## Propriedades de CardTitle
256
+
257
+ | Propriedade | Tipo | Padrão | Descrição |
258
+ |---|---|---|---|
259
+ | `level` | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | `3` | Hierarquia do documento. O tamanho vem do `layout`, nunca do nível. |
260
+ | `asChild` | `boolean` | `false` | Usa o filho como elemento do título, com a escala do layout. Reserve para um componente que já renderiza um heading; com `asChild`, `level` não se aplica. |
261
+ | `className` | `string` | | Ajusta o título. |
262
+
263
+ ## Propriedades de CardDescription
264
+
265
+ | Propriedade | Tipo | Padrão | Descrição |
266
+ |---|---|---|---|
267
+ | `children` | `ReactNode` | | Frase de apoio sob o título, dentro do cabeçalho. |
268
+ | `className` | `string` | | Ajusta a descrição. |
269
+
270
+ ## Propriedades de CardAction
271
+
272
+ | Propriedade | Tipo | Padrão | Descrição |
273
+ |---|---|---|---|
274
+ | `children` | `ReactNode` | | Controles no extremo oposto ao título, como um botão ou um menu. |
275
+ | `className` | `string` | | Ajusta o agrupamento das ações. |
276
+
277
+ ## Propriedades de CardBody
278
+
279
+ | Propriedade | Tipo | Padrão | Descrição |
280
+ |---|---|---|---|
281
+ | `children` | `ReactNode` | | Conteúdo principal do card. |
282
+ | `className` | `string` | | Ajusta a região sobre o recuo do layout. |
283
+
284
+ `CardContent` continua exportado como alias depreciado de `CardBody` e sai em uma próxima versão
285
+ maior. Código novo usa `CardBody`.
286
+
287
+ ## Propriedades de CardFooter
288
+
289
+ | Propriedade | Tipo | Padrão | Descrição |
290
+ |---|---|---|---|
291
+ | `children` | `ReactNode` | | Ações persistentes, alinhadas no fim da linha. |
292
+ | `className` | `string` | | Ajusta o rodapé, inclusive a distribuição das ações. |
@@ -1,9 +1,16 @@
1
1
  ## Um slide por vez
2
2
 
3
- Compõe Carousel>CarouselContent>CarouselItem, com CarouselPrevious/CarouselNext para navegar. As setas ficam fora do trilho (-left-12/-right-12), então reserve a margem lateral no entorno.
3
+ Use `Carousel` para percorrer uma sequência de itens do mesmo tipo em um espaço limitado, um ou
4
+ alguns de cada vez. Para consultar e comparar muitos itens, prefira uma lista, como
5
+ [`ActionList`](/ui/action-list), que mostra tudo de uma vez.
6
+
7
+ Componha `Carousel` > `CarouselContent` > `CarouselItem` e acrescente `CarouselPrevious` e
8
+ `CarouselNext` para navegar. As setas ficam `3rem` fora da área dos slides, então reserve essa
9
+ margem lateral no entorno, como o `mx-12` do exemplo. Dê um nome ao carrossel com `aria-label`:
10
+ ele é anunciado como uma região.
4
11
 
5
12
  ```tsx preview
6
- <Carousel className="mx-12 w-full max-w-xs">
13
+ <Carousel className="mx-12 w-full max-w-xs" aria-label="Sessões recentes">
7
14
  <CarouselContent>
8
15
  <CarouselItem>
9
16
  <div className="flex h-32 items-center justify-center rounded-lg border bg-card text-sm">
@@ -28,10 +35,11 @@ Compõe Carousel>CarouselContent>CarouselItem, com CarouselPrevious/CarouselNext
28
35
 
29
36
  ## Vários por vista
30
37
 
31
- O basis do CarouselItem decide quantos cabem na vista — basis-1/3 mostra três slides por vez. Bom para galeria de workspaces ou repositórios.
38
+ A largura de cada `CarouselItem` decide quantos slides cabem na área visível: `basis-1/3` mostra
39
+ três por vez. `opts={{ align: 'start' }}` alinha o primeiro slide à borda inicial.
32
40
 
33
41
  ```tsx preview
34
- <Carousel className="mx-12 w-full max-w-sm" opts={{ align: 'start' }}>
42
+ <Carousel className="mx-12 w-full max-w-sm" opts={{ align: 'start' }} aria-label="Sessões da semana">
35
43
  <CarouselContent>
36
44
  {['Sessão #128', 'Sessão #131', 'Sessão #134', 'Sessão #137', 'Sessão #140'].map((session) => (
37
45
  <CarouselItem key={session} className="basis-1/3">
@@ -48,10 +56,11 @@ O basis do CarouselItem decide quantos cabem na vista — basis-1/3 mostra três
48
56
 
49
57
  ## Vertical
50
58
 
51
- orientation=vertical empilha os slides; as setas migram para cima e para baixo (-top-12/-bottom-12). Dê uma altura ao CarouselContent para delimitar a vista.
59
+ `orientation="vertical"` empilha os slides, e as setas passam para cima e para baixo, também
60
+ `3rem` fora da área dos slides. Dê uma altura ao `CarouselContent` para delimitar a área visível.
52
61
 
53
62
  ```tsx preview
54
- <Carousel className="w-full max-w-xs" orientation="vertical">
63
+ <Carousel className="my-12 w-full max-w-xs" orientation="vertical" aria-label="Agentes">
55
64
  <CarouselContent className="h-48">
56
65
  <CarouselItem>
57
66
  <div className="flex h-24 items-center justify-center rounded-lg border bg-card text-sm">
@@ -74,6 +83,20 @@ orientation=vertical empilha os slides; as setas migram para cima e para baixo (
74
83
  </Carousel>
75
84
  ```
76
85
 
86
+ ## Teclado
87
+
88
+ Com o foco dentro do carrossel, as setas do teclado mostram o slide anterior e o próximo. Na
89
+ orientação horizontal, use as setas esquerda e direita; na vertical, as setas para cima e para
90
+ baixo. Os botões de navegação ficam desabilitados nas pontas, a menos que `opts` declare
91
+ `{ loop: true }`.
92
+
93
+ Em um campo editável dentro de um slide, como `input`, `textarea`, `select` ou um elemento
94
+ `contenteditable`, as setas continuam movendo o cursor ou mudando a opção, sem trocar de slide. O
95
+ mesmo vale para controles que navegam pelas setas, como [`Slider`](/ui/slider),
96
+ [`RadioGroup`](/ui/radio-group), [`Tabs`](/ui/tabs), [`Toggle`](/ui/toggle) em grupo,
97
+ [`Accordion`](/ui/accordion), listas e menus. Um carrossel colocado dentro de uma barra de
98
+ ferramentas ou de uma grade continua respondendo às setas.
99
+
77
100
  ## Propriedades de Carousel
78
101
 
79
102
  | Propriedade | Tipo | Padrão | Descrição |
@@ -83,6 +106,14 @@ orientation=vertical empilha os slides; as setas migram para cima e para baixo (
83
106
  | `setApi` | `(api: EmblaCarouselType \| undefined) => void` | | Recebe a instância do Embla para controle externo, como navegar com `scrollTo` ou ler o slide ativo. |
84
107
  | `plugins` | `EmblaPluginType[]` | | Plugins do Embla associados ao carrossel, como autoplay. |
85
108
 
109
+ Os demais atributos, como `aria-label` e `className`, vão para o elemento raiz.
110
+
111
+ ## Propriedades de CarouselContent
112
+
113
+ `CarouselContent` delimita a área visível e recorta o que passa dela. Não tem props próprias;
114
+ `className` e os demais atributos de `div` vão para a faixa interna que se desloca e contém os
115
+ slides. Na orientação vertical, é nela que entra a altura, como `h-48`.
116
+
86
117
  ## Propriedades de CarouselItem
87
118
 
88
119
  | Propriedade | Tipo | Padrão | Descrição |