@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,26 +1,28 @@
1
1
  ## Lista de altura fixa
2
2
 
3
- Dê a altura ao ScrollArea (h-48) e ponha o conteúdo dentro — a barra vertical já vem por padrão, sem precisar de ScrollBar.
3
+ Use `ScrollArea` quando um bloco precisa manter a própria altura e rolar o conteúdo que passar dela.
4
+ Dê a altura ao `ScrollArea`, como `h-48`, e coloque o conteúdo dentro. A barra vertical já vem
5
+ incluída; não é preciso acrescentar `ScrollBar`.
4
6
 
5
7
  ```tsx preview col
6
- const sessions = [
7
- { id: 'empresa-x-1842', title: 'Migrar billing para o novo schema', agent: 'developer' },
8
- { id: 'empresa-x-1839', title: 'Revisar handoff do checkout', agent: 'reviewer' },
9
- { id: 'empresa-x-1835', title: 'Redesenhar o painel de rotas', agent: 'designer' },
10
- { id: 'empresa-x-1830', title: 'Corrigir flaky no teste de webhook', agent: 'developer' },
11
- { id: 'empresa-x-1827', title: 'Vincular skill clean-code ao agente', agent: 'reviewer' },
12
- { id: 'empresa-x-1821', title: 'Subir preview do worktree', agent: 'developer' },
13
- { id: 'empresa-x-1818', title: 'Ajustar copy do empty state', agent: 'designer' },
14
- { id: 'empresa-x-1814', title: 'Rodar opus check no monorepo', agent: 'reviewer' },
8
+ const orders = [
9
+ { id: 'OS-1842', title: 'Revisão dos 20.000 km', vehicle: 'ABC-1D23' },
10
+ { id: 'OS-1839', title: 'Troca das pastilhas de freio', vehicle: 'BRA-2E19' },
11
+ { id: 'OS-1835', title: 'Alinhamento e balanceamento', vehicle: 'RIO-4F56' },
12
+ { id: 'OS-1830', title: 'Diagnóstico de ruído na suspensão', vehicle: 'SPO-7G88' },
13
+ { id: 'OS-1827', title: 'Troca de óleo e filtro', vehicle: 'ABC-1D23' },
14
+ { id: 'OS-1821', title: 'Substituição da bateria', vehicle: 'CWB-3H45' },
15
+ { id: 'OS-1818', title: 'Higienização do ar-condicionado', vehicle: 'BRA-2E19' },
16
+ { id: 'OS-1814', title: 'Revisão dos 40.000 km', vehicle: 'POA-9J12' },
15
17
  ]
16
18
 
17
19
  render(
18
20
  <ScrollArea className="h-48 w-full rounded-md border border-border">
19
21
  <div className="p-3">
20
- {sessions.map((s) => (
21
- <div key={s.id} className="rounded-md px-2 py-1.5 text-sm hover:bg-muted">
22
- <p className="font-medium">{s.title}</p>
23
- <p className="text-xs text-muted-foreground">{s.id} · {s.agent}</p>
22
+ {orders.map((order) => (
23
+ <div key={order.id} className="rounded-md px-2 py-1.5 text-sm hover:bg-muted">
24
+ <p className="font-medium">{order.title}</p>
25
+ <p className="text-xs text-muted-foreground">{order.id} · {order.vehicle}</p>
24
26
  </div>
25
27
  ))}
26
28
  </div>
@@ -30,17 +32,18 @@ render(
30
32
 
31
33
  ## Trilho horizontal
32
34
 
33
- Para rolar na horizontal, acrescente <ScrollBar orientation="horizontal" /> como filho e deixe o conteúdo em uma linha que não quebra (flex + w-max).
35
+ Para rolar na horizontal, acrescente `<ScrollBar orientation="horizontal" />` como filho e mantenha
36
+ o conteúdo em uma linha que não quebra, com `flex` e `w-max`.
34
37
 
35
38
  ```tsx preview col
36
- const skills = ['implement-opus-change', 'create-opus-action', 'test-opus-action', 'build-opus-ui', 'upgrade-opus']
39
+ const services = ['Troca de óleo', 'Filtro de ar', 'Alinhamento', 'Balanceamento', 'Pastilhas de freio', 'Velas de ignição']
37
40
 
38
41
  render(
39
42
  <ScrollArea className="w-full rounded-md border border-border whitespace-nowrap">
40
43
  <div className="flex w-max gap-2 p-3">
41
- {skills.map((skill) => (
42
- <span key={skill} className="rounded-md border border-border bg-muted px-3 py-1.5 text-sm">
43
- {skill}
44
+ {services.map((service) => (
45
+ <span key={service} className="rounded-md border border-border bg-muted px-3 py-1.5 text-sm">
46
+ {service}
44
47
  </span>
45
48
  ))}
46
49
  </div>
@@ -49,28 +52,30 @@ render(
49
52
  )
50
53
  ```
51
54
 
52
- ## Painel com divisórias
55
+ ## Histórico com divisórias
53
56
 
54
- O conteúdo é livre: aqui o log do agente, com Separator entre as linhas. A barra só aparece quando o conteúdo passa da altura.
57
+ O conteúdo interno é livre. Neste exemplo, o histórico de uma ordem de serviço usa `Separator`
58
+ entre as linhas. A barra só aparece quando o conteúdo passa da altura e o ponteiro está sobre a
59
+ área; para mantê-la sempre visível, passe `type="always"`.
55
60
 
56
61
  ```tsx preview col
57
- const log = [
58
- '12:04 developer clonou o worktree de empresa-x-api',
59
- '12:05 developer rodou pnpm install',
60
- '12:07 developer abriu a sessão empresa-x-1842',
61
- '12:11 developer aplicou o diff no schema de billing',
62
- '12:14 reviewer foi acionado no handoff',
63
- '12:16 reviewer apontou 2 ajustes de microcopy',
64
- '12:19 developer corrigiu a microcopy',
65
- '12:22 opus check passou em todos os pacotes',
66
- '12:24 reviewer aprovou o handoff',
62
+ const history = [
63
+ '08:02 Veículo recebido na oficina',
64
+ '08:15 Checklist de entrada concluído',
65
+ '08:40 Diagnóstico iniciado',
66
+ '09:10 Orçamento enviado ao cliente',
67
+ '09:32 Orçamento aprovado',
68
+ '10:05 Peças separadas no estoque',
69
+ '11:20 Serviço concluído',
70
+ '11:45 Teste de rodagem aprovado',
71
+ '12:00 Veículo liberado para retirada',
67
72
  ]
68
73
 
69
74
  render(
70
75
  <ScrollArea className="h-40 w-full rounded-md border border-border">
71
76
  <div className="p-3 text-sm">
72
- {log.map((line, i) => (
73
- <div key={i}>
77
+ {history.map((line, i) => (
78
+ <div key={line}>
74
79
  {i > 0 && <Separator className="my-1.5" />}
75
80
  <p className="text-muted-foreground">{line}</p>
76
81
  </div>
@@ -84,11 +89,13 @@ render(
84
89
 
85
90
  | Propriedade | Tipo | Padrão | Descrição |
86
91
  |---|---|---|---|
87
- | `className` | `string` | | Classes que definem as dimensões da janela rolável. Sem uma dimensão limitada, não há conteúdo a recortar. |
88
- | `children` | `React.ReactNode` | | Conteúdo da janela, incluindo uma `ScrollBar` horizontal quando necessária. |
92
+ | `className` | `string` | | Define as dimensões da janela rolável. Sem uma dimensão limitada, não há conteúdo a recortar. |
93
+ | `type` | `'hover' \| 'scroll' \| 'auto' \| 'always'` | `'hover'` | Quando a barra aparece: sob o ponteiro, durante a rolagem, sempre que houver excedente ou sempre. |
94
+ | `children` | `ReactNode` | | Conteúdo da janela, incluindo uma `ScrollBar` horizontal quando necessária. |
89
95
 
90
96
  ## Propriedades de ScrollBar
91
97
 
92
98
  | Propriedade | Tipo | Padrão | Descrição |
93
99
  |---|---|---|---|
94
100
  | `orientation` | `'vertical' \| 'horizontal'` | `'vertical'` | Direção da barra. A barra vertical já faz parte de `ScrollArea`. |
101
+ | `className` | `string` | | Ajusta o trilho da barra. |
@@ -1,7 +1,9 @@
1
1
  ## Escolha em lista
2
2
 
3
- Use `Select` para escolhas em lista. Declare as opções como dados em `options`; `label` é usado no
4
- campo, na busca e na leitura assistiva. Associe `id` ao `htmlFor` de `Label` em formulários.
3
+ Use `Select` quando a pessoa precisar escolher um valor em uma lista que não cabe, inteira, na tela.
4
+ Para poucas opções que devem ficar visíveis ao mesmo tempo, use [`RadioGroup`](/ui/radio-group).
5
+ Declare as opções como dados em `options`; `label` é usado no campo, na busca e na leitura
6
+ assistiva. Associe `id` ao `htmlFor` de `Label` em formulários.
5
7
 
6
8
  ```tsx preview col md
7
9
  const [role, setRole] = useState('')
@@ -16,7 +18,7 @@ render(
16
18
  placeholder="Selecione o papel"
17
19
  options={[
18
20
  { value: 'designer', label: 'Designer' },
19
- { value: 'developer', label: 'Developer' },
21
+ { value: 'developer', label: 'Desenvolvedor' },
20
22
  { value: 'reviewer', label: 'Revisor' },
21
23
  ]}
22
24
  />
@@ -24,9 +26,10 @@ render(
24
26
  )
25
27
  ```
26
28
 
27
- O gatilho respeita a largura definida pelo contexto. Ao abrir, a lista usa essa largura como mínimo
28
- e pode crescer até `32rem` para preservar rótulos maiores sem alterar o layout do formulário ou da
29
- toolbar. Rótulos que ultrapassarem esse limite continuam truncados como proteção de layout.
29
+ O gatilho respeita a largura definida pelo contexto. Na variante padrão, a lista aberta usa essa
30
+ largura como mínimo e pode crescer até `32rem` para mostrar rótulos maiores sem alterar o layout do
31
+ formulário ou da toolbar. Nas variantes `outline` e `ghost`, a lista tem largura própria, entre
32
+ `14rem` e `20rem`. Rótulos que ultrapassarem o limite são truncados.
30
33
 
31
34
  ## Lista pesquisável
32
35
 
@@ -67,7 +70,7 @@ const [skills, setSkills] = useState<string[]>(['clean-code'])
67
70
 
68
71
  render(
69
72
  <div className="grid gap-2">
70
- <Label htmlFor="agent-skills">Skills do agente</Label>
73
+ <Label htmlFor="agent-skills">Habilidades do agente</Label>
71
74
  <Select
72
75
  multiple
73
76
  searchable
@@ -128,7 +131,7 @@ render(
128
131
  options={[
129
132
  { value: 'joao', label: 'João Campos', group: 'Membros' },
130
133
  { value: 'rafa', label: 'Rafa', group: 'Membros' },
131
- { value: 'developer', label: 'Developer', group: 'Agentes' },
134
+ { value: 'developer', label: 'Desenvolvedor', group: 'Agentes' },
132
135
  { value: 'reviewer', label: 'Revisor', group: 'Agentes' },
133
136
  ]}
134
137
  />,
@@ -144,7 +147,7 @@ Com `onSearch`, o consumidor consulta as opções ao abrir e ao digitar, com deb
144
147
  const ASSIGNEES = [
145
148
  { value: 'joao', label: 'João Campos', hint: 'humano' },
146
149
  { value: 'designer', label: 'Designer', hint: 'IA' },
147
- { value: 'developer', label: 'Developer', hint: 'IA' },
150
+ { value: 'developer', label: 'Desenvolvedor', hint: 'IA' },
148
151
  { value: 'reviewer', label: 'Revisor', hint: 'IA' },
149
152
  ]
150
153
 
@@ -188,7 +191,7 @@ render(
188
191
  onChange={setTask}
189
192
  placeholder="Vincular tarefa"
190
193
  options={[
191
- { value: 'gra-2', label: 'Nova tela anotações', hint: 'GRA-2' },
194
+ { value: 'gra-2', label: 'Nova tela de anotações', hint: 'GRA-2' },
192
195
  { value: 'gra-7', label: 'Filtro por filial', hint: 'GRA-7' },
193
196
  ]}
194
197
  />,
@@ -210,7 +213,7 @@ render(
210
213
  icon={<Ticket />}
211
214
  value={a}
212
215
  onChange={setA}
213
- options={[{ value: 'gra-2', label: 'Nova tela anotações', hint: 'GRA-2' }]}
216
+ options={[{ value: 'gra-2', label: 'Nova tela de anotações', hint: 'GRA-2' }]}
214
217
  />
215
218
  <Button size="sm" variant="outline">Filtrar</Button>
216
219
  </div>,
@@ -220,7 +223,8 @@ render(
220
223
  ## Limpar a seleção
221
224
 
222
225
  `clearable` acrescenta uma ação que limpa o valor sem abrir a lista. No modo simples, o valor volta
223
- a vazio; no múltiplo, volta a `[]`. Os filtros de `ActionList` já habilitam esse comportamento.
226
+ a vazio; no múltiplo, volta a `[]`. Os filtros de [`ActionList`](/ui/action-list) já habilitam
227
+ esse comportamento.
224
228
 
225
229
  ```tsx preview col md
226
230
  function Demo() {
@@ -260,12 +264,12 @@ render(
260
264
  options={[
261
265
  {
262
266
  value: 'developer',
263
- label: 'Developer',
267
+ label: 'Desenvolvedor',
264
268
  content: (
265
269
  <span className="flex items-center gap-2">
266
270
  <Bot className="size-4 text-muted-foreground" />
267
271
  <span>
268
- Developer <span className="text-muted-foreground">· implementa</span>
272
+ Desenvolvedor <span className="text-muted-foreground">· implementa</span>
269
273
  </span>
270
274
  </span>
271
275
  ),
@@ -289,21 +293,22 @@ render(
289
293
 
290
294
  ## Select na barra do Composer
291
295
 
292
- Use `variant="ghost"` para integrar o seletor à barra de ações de `Composer`. `triggerLabel` pode
293
- encurtar somente o texto do gatilho; a lista continua exibindo o `label` completo.
296
+ Use `variant="ghost"` para integrar o seletor à barra de ações de [`Composer`](/ui/composer).
297
+ `triggerLabel` pode encurtar somente o texto do gatilho; a lista continua exibindo o `label`
298
+ completo.
294
299
 
295
300
  ```tsx preview col-start
296
- const [task, setTask] = useState('GB-42')
301
+ const [order, setOrder] = useState('OS-1842')
297
302
 
298
303
  render(
299
304
  <Select
300
305
  variant="ghost"
301
- value={task}
302
- onChange={setTask}
306
+ value={order}
307
+ onChange={setOrder}
303
308
  options={[
304
- { value: '', label: 'Sem task' },
305
- { value: 'GB-42', label: 'Trocar o FAB por botão no header', hint: 'GB-42', triggerLabel: 'GB-42' },
306
- { value: 'GB-51', label: 'Anexo no composer de criação', hint: 'GB-51', triggerLabel: 'GB-51' },
309
+ { value: '', label: 'Sem ordem de serviço' },
310
+ { value: 'OS-1842', label: 'Revisão dos 20.000 km', hint: 'OS-1842', triggerLabel: 'OS-1842' },
311
+ { value: 'OS-1839', label: 'Troca das pastilhas de freio', hint: 'OS-1839', triggerLabel: 'OS-1839' },
307
312
  ]}
308
313
  />,
309
314
  )
@@ -324,7 +329,7 @@ render(
324
329
  onChange={setTask}
325
330
  className="w-72"
326
331
  options={[
327
- { value: 'gra-2', label: 'Nova tela anotações', hint: 'GRA-2' },
332
+ { value: 'gra-2', label: 'Nova tela de anotações', hint: 'GRA-2' },
328
333
  { value: 'gra-7', label: 'Filtro por filial', hint: 'GRA-7' },
329
334
  ]}
330
335
  trailing={
@@ -336,26 +341,66 @@ render(
336
341
  )
337
342
  ```
338
343
 
344
+ ## Estados
345
+
346
+ `disabled` esmaece o controle e impede a abertura da lista. `aria-invalid` aplica o contexto de
347
+ erro; associe a mensagem por `aria-describedby`.
348
+
349
+ ```tsx preview col md
350
+ const [unit, setUnit] = useState('')
351
+
352
+ render(
353
+ <div className="grid gap-4">
354
+ <Field data-invalid="true">
355
+ <FieldLabel htmlFor="unit">Unidade</FieldLabel>
356
+ <Select
357
+ id="unit"
358
+ value={unit}
359
+ onChange={setUnit}
360
+ placeholder="Selecione a unidade"
361
+ aria-invalid
362
+ aria-describedby="unit-error"
363
+ options={[
364
+ { value: 'centro', label: 'Centro' },
365
+ { value: 'norte', label: 'Zona Norte' },
366
+ ]}
367
+ />
368
+ <FieldError id="unit-error">Escolha a unidade responsável.</FieldError>
369
+ </Field>
370
+ <Field data-disabled="true">
371
+ <FieldLabel htmlFor="region">Região</FieldLabel>
372
+ <Select
373
+ id="region"
374
+ value="sudeste"
375
+ onChange={() => undefined}
376
+ disabled
377
+ options={[{ value: 'sudeste', label: 'Sudeste' }]}
378
+ />
379
+ </Field>
380
+ </div>,
381
+ )
382
+ ```
383
+
339
384
  ## Propriedades de Select
340
385
 
341
386
  | Propriedade | Tipo | Padrão | Descrição |
342
387
  |---|---|---|---|
343
388
  | `options` | `SelectOption[]` | | As opções: `{ value, label, hint?, content?, triggerLabel?, group?, disabled? }`. |
344
389
  | `value` | `string \| string[]` | | O selecionado: string no single, string[] no multiple. |
345
- | `onChange` | `(value: string) => void \| (value: string[]) => void` | | Chamado ao escolher (e ao remover chip, no multiple) — a assinatura segue o modo. |
390
+ | `onChange` | `((value: string) => void) \| ((value: string[]) => void)` | | Chamado ao escolher (e ao remover chip, no multiple) — a assinatura segue o modo. |
346
391
  | `native` | `boolean` | `false` | Renderiza o `<select>` do sistema. Exclui busca, seleção múltipla e variantes visuais (o browser é quem desenha a lista). |
347
392
  | `searchable` | `boolean` | `false` | Acrescenta uma linha de busca no topo da lista, que filtra as opções enquanto a pessoa digita. O gatilho continua exibindo a escolha. |
348
- | `multiple` | `boolean` | `false` | Chips removíveis, lista que permanece aberta e Selecionar tudo. |
349
- | `variant` | `'default' \| 'outline' \| 'ghost'` | `'default'` | `outline` = compacto com borda; `ghost` = compacto sem borda nem fundo, para barra do composer. |
350
- | `placeholder` | `string` | `'Selecione…'` | Texto do campo vazio. |
393
+ | `multiple` | `boolean` | `false` | Seleção de vários valores: chips removíveis, lista que permanece aberta e a opção “Selecionar tudo”. |
394
+ | `variant` | `'default' \| 'outline' \| 'ghost'` | `'default'` | `outline` é compacto e mantém a borda; `ghost` é compacto, sem borda nem fundo, para a barra do `Composer`. |
395
+ | `placeholder` | `string` | `'Selecione…'`; sem padrão com `native` | Texto do campo vazio. Com `native`, só existe quando declarado e vira uma opção desabilitada no início da lista. |
351
396
  | `searchPlaceholder` | `string` | `'Buscar…'` | Placeholder da linha de busca dentro da lista. |
352
397
  | `emptyText` | `string` | `'Nada encontrado.'` | Mensagem quando a busca não acha nada. |
353
- | `onSearch` | `(query: string) => void` | | Busca server-side (debounced, ao abrir e ao digitar): desliga o filtro do cmdk — o pai atualiza `options`. |
354
- | `loading` | `boolean` | | Mostra Buscando… enquanto o fetch corre (use com `onSearch`). |
355
- | `clearable` | `boolean` | `false` | X no controle quando há seleção — limpa em um clique. |
356
- | `icon` | `React.ReactNode` | | Ícone leading DENTRO do controle (decorativo) — herda `size-4` e o tom muted. |
357
- | `trailing` | `React.ReactNode` | | Ação custom no FIM do controle (antes do chevron) — o clique não abre a lista. |
358
- | `size` | `'default' \| 'sm'` | `'default'` | Altura: default (h-9, a do Input e do Button) ou sm (h-8) para toolbar densa. |
398
+ | `onSearch` | `(query: string) => void` | | Busca no servidor, chamada ao abrir a lista e depois de uma breve pausa na digitação. Desliga o filtro local; o consumidor atualiza `options` com a resposta. |
399
+ | `loading` | `boolean` | | Mostra “Buscando…” enquanto a consulta de `onSearch` está em andamento. |
400
+ | `clearable` | `boolean` | `false` | Mostra, quando há seleção, uma ação que limpa o valor sem abrir a lista. |
401
+ | `icon` | `React.ReactNode` | | Ícone decorativo no início do controle, em `1rem` e no tom atenuado. |
402
+ | `trailing` | `React.ReactNode` | | Ação própria no fim do controle, antes do chevron; interagir com ela não abre a lista. |
403
+ | `size` | `'default' \| 'sm'` | `'default'` | Altura na escala dos controles: `default` (`2.25rem`, a de `Input` e `Button`) ou `sm` (`2rem`) para toolbars densas. |
359
404
  | `shape` | `'default' \| 'pill'` | `'default'` | Geometria do controle; `pill` arredonda as extremidades e preserva a variante visual. |
360
405
  | `disabled` | `boolean` | `false` | Esmaece e trava o controle. |
361
406
  | `id` | `string` | | Vai para o campo — para parear com o `htmlFor` do Label. |
@@ -41,10 +41,29 @@ aceita o subconjunto coerente com seu papel.
41
41
  </div>
42
42
  ```
43
43
 
44
+ ## Controles sem contexto
45
+
46
+ Alguns controles também têm `variant`, mas não têm `context`. Neles, `variant` não está ligado a um
47
+ significado: descreve o formato do próprio controle.
48
+
49
+ O par `context` + `variant` vale para componentes que comunicam significado, como
50
+ [`Button`](/ui/button), [`Badge`](/ui/badge), [`Alert`](/ui/alert), [`Dot`](/ui/dot) e
51
+ [`IconTile`](/ui/icon-tile). Os controles abaixo não aceitam `context`:
52
+
53
+ | Componente | Valores | O que muda |
54
+ | --- | --- | --- |
55
+ | [`Select`](/ui/select) | `default`, `outline`, `ghost` | `default` é o campo completo; `outline` é compacto com borda, e `ghost`, compacto sem borda nem fundo. |
56
+ | [`Toggle`](/ui/toggle) | `default`, `outline` | `default` não tem borda nem fundo em repouso; o fundo aparece no hover e quando ativo. `outline` acrescenta a borda. |
57
+ | [`TabsList`](/ui/tabs) | `default`, `line` | `default` agrupa as abas em uma superfície preenchida; `line` usa uma linha-base e marca nela a aba ativa. |
58
+
59
+ O mesmo nome pode aparecer nos dois grupos. Num `Badge`, `outline` é um tratamento do significado
60
+ declarado em `context`; num `Select`, é apenas o formato compacto com borda.
61
+
44
62
  ## Dicionários
45
63
 
46
- Status e estágios declaram `context` na entrada. A tela usa `DictionaryValue` ou `ActionList` e não
47
- escolhe a aparência novamente.
64
+ Status e estágios declaram `context` na entrada. A tela usa
65
+ [`DictionaryValue`](/ui/dictionary-value) ou [`ActionList`](/ui/action-list) e não escolhe a
66
+ aparência novamente.
48
67
 
49
68
  ```ts
50
69
  const status = t.dict(
@@ -1,27 +1,32 @@
1
1
  ## Separar blocos empilhados
2
2
 
3
- Use a orientação horizontal entre blocos empilhados. A linha ocupa toda a largura disponível.
3
+ Use `Separator` para marcar a fronteira entre blocos de conteúdo que não pertencem a uma superfície
4
+ com regiões. A orientação horizontal fica entre blocos empilhados e ocupa toda a largura disponível.
4
5
 
5
6
  ```tsx preview col
6
7
  <div>
7
- <h4 className="text-sm font-medium">Empresa X</h4>
8
- <p className="text-sm text-muted-foreground">Workspace do cliente Empresa X.</p>
8
+ <h4 className="text-sm font-medium">Oficina Centro</h4>
9
+ <p className="text-sm text-muted-foreground">Rua das Flores, 120.</p>
9
10
  </div>
10
11
  <Separator />
11
- <p className="text-sm text-muted-foreground">Última sessão há 2 horas.</p>
12
+ <p className="text-sm text-muted-foreground">Próximo horário livre hoje, às 14h.</p>
12
13
  ```
13
14
 
15
+ Não use `Separator` entre cabeçalho, corpo e rodapé de [`Card`](/ui/card), [`Dialog`](/ui/dialog) ou
16
+ [`Drawer`](/ui/drawer). Nessas superfícies, os divisores são derivados das regiões presentes: use
17
+ `layout="sectioned"` no componente raiz.
18
+
14
19
  ## Vertical
15
20
 
16
21
  Use a orientação vertical entre itens lado a lado. A linha herda a altura do contêiner.
17
22
 
18
23
  ```tsx preview
19
24
  <div className="flex h-4 items-center gap-3 text-sm">
20
- <span>2 repositórios</span>
25
+ <span>4 boxes</span>
21
26
  <Separator orientation="vertical" />
22
- <span>3 agentes</span>
27
+ <span>2 consultores</span>
23
28
  <Separator orientation="vertical" />
24
- <span>6 skills</span>
29
+ <span>6 revisões hoje</span>
25
30
  </div>
26
31
  ```
27
32
 
@@ -29,5 +34,6 @@ Use a orientação vertical entre itens lado a lado. A linha herda a altura do c
29
34
 
30
35
  | Propriedade | Tipo | Padrão | Descrição |
31
36
  |---|---|---|---|
32
- | `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | Direção da linha. Vertical precisa de altura vinda do contêiner. |
33
- | `decorative` | `boolean` | `true` | Decorativa some da árvore de acessibilidade. Use false quando a divisão é semântica. |
37
+ | `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | Direção da linha. A vertical precisa de altura vinda do contêiner. |
38
+ | `decorative` | `boolean` | `true` | Uma linha decorativa fica fora da árvore de acessibilidade. Use `false` quando a divisão tiver significado e precisar ser anunciada. |
39
+ | `className` | `string` | | Ajusta a linha, como o espaçamento ao redor. |
@@ -1,8 +1,8 @@
1
1
  ## Navegação dentro de um layout
2
2
 
3
3
  Use `Sidebar` para apresentar navegação global ou contextual em uma coluna lateral. O componente
4
- organiza a superfície e compartilha o estado de colapso; posição e largura pertencem a um `Pane`
5
- dentro de `Split`.
4
+ organiza a superfície e compartilha o estado de colapso; posição e largura pertencem a um
5
+ [`Pane`](/ui/pane) dentro de [`Split`](/ui/split).
6
6
 
7
7
  O exemplo canônico mantém a largura inicial em `rem`, deixa a navegação rolar e preserva o
8
8
  cabeçalho e o rodapé nas extremidades.
@@ -13,8 +13,8 @@ render(
13
13
  <Split>
14
14
  <Pane initialSize="16rem" inset="none">
15
15
  <Sidebar className="w-full">
16
- <PaneHeader className="px-4 py-3 text-sm font-semibold">
17
- Projeto
16
+ <PaneHeader>
17
+ <PaneTitle>Projeto</PaneTitle>
18
18
  </PaneHeader>
19
19
  <PaneBody>
20
20
  <SidebarNav
@@ -50,22 +50,17 @@ divisória ao lado do handle.
50
50
 
51
51
  ## Anatomia e responsabilidades
52
52
 
53
- Cada parte possui uma responsabilidade única. Essa separação permite reutilizar os mesmos slots em
54
- uma sidebar fixa, redimensionável ou recolhida.
53
+ A `Sidebar` não tem regiões próprias: o cabeçalho, o corpo rolável e o rodapé são os slots do
54
+ `Pane` que a hospeda, com a anatomia e as propriedades descritas na
55
+ [página da família](/ui/pane).
55
56
 
56
57
  | Parte | Responsabilidade |
57
58
  |---|---|
58
- | `Split` | Define a relação espacial e, opcionalmente, o redimensionamento. |
59
- | `Pane` | Define largura, limites e espaçamento interno da coluna. |
59
+ | [`Split`](/ui/split) | Define a relação espacial e, opcionalmente, o redimensionamento. |
60
+ | [`Pane`](/ui/pane) | Define largura e limites da coluna e estrutura suas regiões. |
60
61
  | `Sidebar` | Desenha a superfície lateral e fornece o estado de colapso. |
61
- | `PaneHeader` | Mantém identidade, contexto ou ações no topo. |
62
- | `PaneBody` | Ocupa o espaço restante e concentra a rolagem vertical. |
63
- | `PaneFooter` | Mantém ações persistentes no rodapé. |
64
62
  | `SidebarNav` | Apresenta e controla os destinos de navegação. |
65
63
 
66
- `PaneContent` continua exportado como alias depreciado de `PaneBody`. Código novo usa
67
- `PaneBody`.
68
-
69
64
  ## Escolher a navegação
70
65
 
71
66
  Use `SidebarNav` para grupos planos de destinos. Ele recebe dados e produz `SidebarItem` com a mesma
@@ -151,15 +146,17 @@ const [collapsed, setCollapsed] = useState(false)
151
146
  render(
152
147
  <div className="flex h-64 overflow-hidden rounded-lg border border-border">
153
148
  <Sidebar collapsed={collapsed}>
154
- <PaneHeader className="flex h-12 items-center justify-center">
155
- <Button
156
- variant="ghost"
157
- size="icon-sm"
158
- aria-label={collapsed ? 'Expandir navegação' : 'Recolher navegação'}
159
- onClick={() => setCollapsed((current) => !current)}
160
- >
161
- <PanelLeftClose className={collapsed ? 'rotate-180' : ''} />
162
- </Button>
149
+ <PaneHeader>
150
+ <PaneActions>
151
+ <Button
152
+ variant="ghost"
153
+ size="icon-sm"
154
+ aria-label={collapsed ? 'Expandir navegação' : 'Recolher navegação'}
155
+ onClick={() => setCollapsed((current) => !current)}
156
+ >
157
+ <PanelLeftClose className={collapsed ? 'rotate-180' : ''} />
158
+ </Button>
159
+ </PaneActions>
163
160
  </PaneHeader>
164
161
  <PaneBody>
165
162
  <SidebarNav
@@ -241,26 +238,8 @@ O driver de drag-and-drop continua externo e fornece seus atributos por `dragPro
241
238
  | `className` | `string` | | Ajusta a raiz. A largura externa deve continuar pertencendo ao `Pane`. |
242
239
  | `children` | `ReactNode` | | Cabeçalho, corpo, rodapé ou uma navegação que gerencie a própria estrutura. |
243
240
 
244
- ## Propriedades de PaneHeader
245
-
246
- | Propriedade | Tipo | Padrão | Descrição |
247
- |---|---|---|---|
248
- | `className` | `string` | | Ajusta a faixa fixa e seu espaçamento. |
249
- | `children` | `ReactNode` | | Identidade, contexto ou ações apresentadas no topo. |
250
-
251
- ## Propriedades de PaneBody
252
-
253
- | Propriedade | Tipo | Padrão | Descrição |
254
- |---|---|---|---|
255
- | `className` | `string` | | Ajusta a região flexível e rolável. |
256
- | `children` | `ReactNode` | | Conteúdo que ocupa o espaço entre cabeçalho e rodapé. |
257
-
258
- ## Propriedades de PaneFooter
259
-
260
- | Propriedade | Tipo | Padrão | Descrição |
261
- |---|---|---|---|
262
- | `className` | `string` | | Ajusta a faixa fixa e seu espaçamento. |
263
- | `children` | `ReactNode` | | Navegação ou ações que precisam permanecer acessíveis no rodapé. |
241
+ `PaneHeader`, `PaneBody` e `PaneFooter` são os slots da família `Pane`; a referência deles está na
242
+ [página do Pane](/ui/pane).
264
243
 
265
244
  ## Propriedades de SidebarNav
266
245
 
@@ -279,8 +258,9 @@ O driver de drag-and-drop continua externo e fornece seus atributos por `dragPro
279
258
  | `label` | `string` | Rótulo opcional do grupo. Rótulos vazios não reservam espaço. |
280
259
  | `items` | `SidebarNavItem[]` | Itens diretos do grupo. |
281
260
 
282
- `SidebarNavItem` acrescenta `id` às propriedades de `SidebarItem`. Para mais níveis, componha
283
- `SidebarItem` e `SidebarTreeGroup` diretamente.
261
+ `SidebarNavItem` recebe as propriedades de `SidebarItem`, exceto `active` e `onClick`, e acrescenta
262
+ `id`. O destaque vem de `activeId` e a seleção chega por `onSelect`, ambos no `SidebarNav`. Para mais
263
+ níveis, componha `SidebarItem` e `SidebarTreeGroup` diretamente.
284
264
 
285
265
  ## Propriedades de SidebarItem
286
266
 
@@ -1,7 +1,12 @@
1
1
  ## Linha de lista com avatar
2
2
 
3
3
  Use `Skeleton` para preservar a forma do conteúdo durante o carregamento. Combine círculos para
4
- avatares e barras com dimensões próximas às linhas de texto esperadas.
4
+ avatares e barras com dimensões próximas às linhas de texto esperadas. Quando a forma do conteúdo
5
+ não é conhecida, use [`Spinner`](/ui/spinner); para os estados completos de uma consulta, incluindo
6
+ erro e vazio, use [`DataState`](/ui/data-state).
7
+
8
+ O `Skeleton` é só visual e não anuncia a espera. Quando a região inteira estiver carregando, marque
9
+ o contêiner com `role="status"` e um nome, como `aria-label="Carregando sessões"`.
5
10
 
6
11
  ```tsx preview col
7
12
  <div className="flex items-center gap-3">
@@ -15,15 +20,19 @@ avatares e barras com dimensões próximas às linhas de texto esperadas.
15
20
 
16
21
  ## Card em carregamento
17
22
 
18
- O esqueleto reproduz o layout final do card — título, descrição, conteúdo e ações — para a tela não pular quando os dados chegarem.
23
+ O esqueleto reproduz o layout final do card — título, conteúdo e ações — para a tela não pular
24
+ quando os dados chegarem. O título entra pelo slot do cabeçalho com `asChild`, porque o
25
+ `CardHeader` só aceita os próprios slots.
19
26
 
20
27
  ```tsx preview col
21
28
  <Card>
22
29
  <CardHeader>
23
- <Skeleton className="h-5 w-40" />
24
- <Skeleton className="h-4 w-56" />
30
+ <CardTitle asChild>
31
+ <Skeleton className="h-5 w-40" />
32
+ </CardTitle>
25
33
  </CardHeader>
26
34
  <CardBody className="space-y-2">
35
+ <Skeleton className="h-4 w-56" />
27
36
  <Skeleton className="h-4 w-full" />
28
37
  <Skeleton className="h-4 w-3/4" />
29
38
  </CardBody>