@softize/opus 12.11.0 → 13.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 (119) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/bin/lib/check.mjs +2 -7
  3. package/bin/lib/copy.mjs +1 -5
  4. package/docs/adr/0005-structural-surfaces-share-an-explicit-anatomy.md +93 -10
  5. package/docs/adr/0007-toast-actions-form-an-ordered-collection.md +63 -0
  6. package/docs/adr/0008-hierarchical-navigation-is-composed-at-the-consumer-boundary.md +71 -0
  7. package/docs/radius-scale.md +1 -1
  8. package/package.json +1 -1
  9. package/registry/skills/maintain-opus-docs/SKILL.md +83 -0
  10. package/registry/skills/maintain-opus-docs/agents/openai.yaml +4 -0
  11. package/registry/skills/maintain-opus-docs/references/editorial-standard.md +85 -0
  12. package/registry/skills/maintain-opus-docs/references/evaluations.md +34 -0
  13. package/registry/skills/maintain-opus-docs/scripts/audit-docs.mjs +81 -0
  14. package/src/ui/components/patterns/confirm.tsx +140 -40
  15. package/src/ui/components/patterns/list.tsx +35 -40
  16. package/src/ui/components/patterns/page-state.tsx +2 -2
  17. package/src/ui/components/patterns/sidebar.tsx +26 -26
  18. package/src/ui/components/patterns/trigger.tsx +25 -22
  19. package/src/ui/components/primitives/alert.tsx +3 -3
  20. package/src/ui/components/primitives/dialog.tsx +196 -39
  21. package/src/ui/components/primitives/drawer.tsx +8 -5
  22. package/src/ui/components/primitives/empty.tsx +3 -3
  23. package/src/ui/components/primitives/item.tsx +3 -3
  24. package/src/ui/components/primitives/sonner.tsx +187 -8
  25. package/src/ui/docs/DocBrowser.tsx +102 -23
  26. package/src/ui/docs/content/accordion.md +22 -16
  27. package/src/ui/docs/content/action-form-card.md +8 -8
  28. package/src/ui/docs/content/action-form-dialog.md +9 -9
  29. package/src/ui/docs/content/action-form.md +28 -34
  30. package/src/ui/docs/content/action-list-dialog.md +11 -6
  31. package/src/ui/docs/content/action-list.md +64 -39
  32. package/src/ui/docs/content/action-trigger.md +21 -14
  33. package/src/ui/docs/content/action-view.md +8 -8
  34. package/src/ui/docs/content/actions.md +9 -9
  35. package/src/ui/docs/content/ai.md +3 -3
  36. package/src/ui/docs/content/alert.md +14 -12
  37. package/src/ui/docs/content/aspect-ratio.md +4 -4
  38. package/src/ui/docs/content/audit.md +2 -2
  39. package/src/ui/docs/content/auth.md +3 -3
  40. package/src/ui/docs/content/avatar.md +34 -14
  41. package/src/ui/docs/content/badge.md +3 -3
  42. package/src/ui/docs/content/breadcrumb.md +13 -8
  43. package/src/ui/docs/content/button.md +81 -6
  44. package/src/ui/docs/content/calendar.md +5 -5
  45. package/src/ui/docs/content/card.md +1 -1
  46. package/src/ui/docs/content/carousel.md +16 -11
  47. package/src/ui/docs/content/chat.md +3 -3
  48. package/src/ui/docs/content/checkbox.md +7 -7
  49. package/src/ui/docs/content/cli.md +5 -5
  50. package/src/ui/docs/content/collapsible.md +8 -8
  51. package/src/ui/docs/content/command.md +16 -8
  52. package/src/ui/docs/content/composer.md +2 -2
  53. package/src/ui/docs/content/content.md +2 -2
  54. package/src/ui/docs/content/copyable.md +4 -3
  55. package/src/ui/docs/content/customization.md +5 -5
  56. package/src/ui/docs/content/cycle.md +3 -3
  57. package/src/ui/docs/content/data-state.md +11 -12
  58. package/src/ui/docs/content/data.md +26 -33
  59. package/src/ui/docs/content/detail.md +3 -3
  60. package/src/ui/docs/content/dialog.md +339 -31
  61. package/src/ui/docs/content/dictionary-value.md +8 -8
  62. package/src/ui/docs/content/dock.md +3 -3
  63. package/src/ui/docs/content/drawer.md +27 -14
  64. package/src/ui/docs/content/empty-value.md +2 -2
  65. package/src/ui/docs/content/empty.md +19 -12
  66. package/src/ui/docs/content/events.md +4 -4
  67. package/src/ui/docs/content/field.md +34 -12
  68. package/src/ui/docs/content/getting-started.md +1 -1
  69. package/src/ui/docs/content/icon-picker.md +8 -4
  70. package/src/ui/docs/content/input-otp.md +20 -12
  71. package/src/ui/docs/content/input.md +121 -9
  72. package/src/ui/docs/content/item.md +27 -13
  73. package/src/ui/docs/content/kbd.md +19 -11
  74. package/src/ui/docs/content/label.md +5 -3
  75. package/src/ui/docs/content/log.md +4 -4
  76. package/src/ui/docs/content/markdown.md +7 -6
  77. package/src/ui/docs/content/mcp.md +13 -15
  78. package/src/ui/docs/content/menu.md +34 -16
  79. package/src/ui/docs/content/observability.md +2 -2
  80. package/src/ui/docs/content/page.md +51 -6
  81. package/src/ui/docs/content/pagination.md +22 -17
  82. package/src/ui/docs/content/popover.md +16 -8
  83. package/src/ui/docs/content/progress.md +7 -5
  84. package/src/ui/docs/content/queue.md +5 -5
  85. package/src/ui/docs/content/radio-group.md +20 -12
  86. package/src/ui/docs/content/router.md +11 -6
  87. package/src/ui/docs/content/scheduler.md +4 -5
  88. package/src/ui/docs/content/scroll-area.md +12 -7
  89. package/src/ui/docs/content/select.md +42 -29
  90. package/src/ui/docs/content/separator.md +5 -5
  91. package/src/ui/docs/content/sidebar.md +323 -54
  92. package/src/ui/docs/content/skeleton.md +3 -2
  93. package/src/ui/docs/content/slider.md +8 -7
  94. package/src/ui/docs/content/spinner.md +8 -8
  95. package/src/ui/docs/content/split.md +8 -5
  96. package/src/ui/docs/content/storage.md +6 -8
  97. package/src/ui/docs/content/switch.md +8 -7
  98. package/src/ui/docs/content/table.md +13 -3
  99. package/src/ui/docs/content/tabs.md +28 -14
  100. package/src/ui/docs/content/testing.md +9 -11
  101. package/src/ui/docs/content/textarea.md +5 -4
  102. package/src/ui/docs/content/toast.md +47 -13
  103. package/src/ui/docs/content/toggle.md +75 -7
  104. package/src/ui/docs/content/tokens.md +3 -3
  105. package/src/ui/docs/content/tooltip.md +19 -11
  106. package/src/ui/docs/content/truncate.md +7 -8
  107. package/src/ui/docs/content/ui.md +10 -9
  108. package/src/ui/docs/content/upgrading.md +7 -8
  109. package/src/ui/docs/registry.tsx +20 -37
  110. package/src/ui/meta.ts +64 -94
  111. package/src/ui/react.tsx +15 -16
  112. package/src/ui/theme.css +50 -0
  113. package/src/ui/components/primitives/alert-dialog.tsx +0 -192
  114. package/src/ui/docs/content/alert-dialog.md +0 -73
  115. package/src/ui/docs/content/button-group.md +0 -71
  116. package/src/ui/docs/content/confirm.md +0 -120
  117. package/src/ui/docs/content/input-group.md +0 -79
  118. package/src/ui/docs/content/page-state.md +0 -45
  119. package/src/ui/docs/content/toggle-group.md +0 -81
@@ -1,6 +1,7 @@
1
- ## Básico
1
+ ## Escolha em lista
2
2
 
3
- Um componente para toda escolha em lista. As opções são DADO (`options`), não JSX: `{ value, label }` — o `label` é o que o campo mostra, o que a busca casa e o que o leitor de tela lê. O `id` pareia com o `htmlFor` do Label.
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.
4
5
 
5
6
  ```tsx preview col md
6
7
  const [role, setRole] = useState('')
@@ -23,9 +24,10 @@ render(
23
24
  )
24
25
  ```
25
26
 
26
- ## Com busca (searchable)
27
+ ## Lista pesquisável
27
28
 
28
- `searchable` transforma o próprio campo na busca: digitou, a lista filtra embaixo (fuzzy do cmdk, casando label, hint e value). É o que resolve a **achabilidade** — a régua é essa, não performance: 200 itens sem busca é embaçado, achar é rolar até cansar.
29
+ Use `searchable` quando a quantidade ou os rótulos dificultarem encontrar uma opção. O próprio
30
+ campo passa a filtrar `label`, `hint` e `value` com a busca do cmdk.
29
31
 
30
32
  ```tsx preview col md
31
33
  const [issue, setIssue] = useState('')
@@ -49,9 +51,11 @@ render(
49
51
  )
50
52
  ```
51
53
 
52
- ## Múltiplo (chips)
54
+ ## Seleção múltipla
53
55
 
54
- `multiple` muda o contrato (`value` e `onChange` viram `string[]`): os escolhidos viram chips removíveis e a lista fica aberta pra seleção em sequência. Com a busca vazia, o topo do dropdown oferece Selecionar tudo / Limpar seleção (só no modo client — com filtro ativo ou `onSearch`, "tudo" seria ambíguo).
56
+ Com `multiple`, `value` e `onChange` usam `string[]`. As opções escolhidas aparecem como chips
57
+ removíveis e a lista permanece aberta para escolhas sucessivas. “Selecionar tudo” fica disponível
58
+ somente quando todas as opções estão carregadas localmente e a busca está vazia.
55
59
 
56
60
  ```tsx preview col md
57
61
  const [skills, setSkills] = useState<string[]>(['clean-code'])
@@ -77,9 +81,10 @@ render(
77
81
  )
78
82
  ```
79
83
 
80
- ## Nativo (native)
84
+ ## Controle nativo
81
85
 
82
- `native` renderiza o `<select>` do sistema (só com a seta restilizada — a nativa cola na borda e ignora o tema). Ganha o picker do próprio SO: roda de scroll no iOS, teclado do Android, zero JS. Use em **lista curta e sabida** (2–8 itens que a pessoa já conhece: Sim/Não, prioridade, ambiente). Item rico, busca ou multi não existem aqui — pra isso, o modo default.
86
+ Use `native` para uma lista curta e conhecida, como prioridade ou ambiente. O sistema operacional
87
+ controla a interação do `<select>`. Busca, seleção múltipla e conteúdo rico exigem o modo padrão.
83
88
 
84
89
  ```tsx preview col md
85
90
  const [env, setEnv] = useState('preview')
@@ -104,7 +109,8 @@ render(
104
109
 
105
110
  ## Grupos
106
111
 
107
- `group` na opção agrupa a lista — bloco nomeado nos dois modos (`<optgroup>` no nativo, heading no custom). Pra listas com origens distintas (membros × agentes).
112
+ `group` reúne opções sob um título nos dois modos. Use grupos quando a lista combinar conjuntos com
113
+ origens ou papéis distintos.
108
114
 
109
115
  ```tsx preview col md
110
116
  const [reviewer, setReviewer] = useState('')
@@ -124,9 +130,10 @@ render(
124
130
  )
125
131
  ```
126
132
 
127
- ## Busca server-side
133
+ ## Busca no servidor
128
134
 
129
- Com `onSearch` o filtro do cmdk desliga: o pai busca (debounced, ao abrir e ao digitar) e devolve `options`; `loading` mostra Buscando… enquanto o fetch corre. Implica campo buscável (não precisa repetir `searchable`).
135
+ Com `onSearch`, o consumidor consulta as opções ao abrir e ao digitar, com debounce. Atualize
136
+ `options` com a resposta e use `loading` durante a consulta. Esse modo já habilita o campo de busca.
130
137
 
131
138
  ```tsx preview col md
132
139
  const ASSIGNEES = [
@@ -163,7 +170,8 @@ render(
163
170
 
164
171
  ## Ícone dentro do campo
165
172
 
166
- `icon` põe o ícone DENTRO do controle, antes do texto (e dos chips, no multi) — identifica o campo numa barra de filtros. Ícone AO LADO do campo é o antipadrão: desalinha na quebra de linha e some no responsivo. Vale nos dois modos.
173
+ `icon` posiciona um ícone decorativo antes do texto ou dos chips. Use-o para reforçar a identidade do
174
+ campo sem criar um elemento separado ao lado do controle.
167
175
 
168
176
  ```tsx preview col-start
169
177
  const [task, setTask] = useState('')
@@ -182,9 +190,10 @@ render(
182
190
  )
183
191
  ```
184
192
 
185
- ## Densidade (size)
193
+ ## Tamanho
186
194
 
187
- O controle segue a altura do design system: default é h-9 (a mesma do Input e do Button) e `size="sm"` h-8 pra barra de filtros/toolbar densa.
195
+ O tamanho padrão acompanha `Input` e `Button`. Use `size="sm"` em barras de filtros e outras
196
+ composições densas.
188
197
 
189
198
  ```tsx preview col-start
190
199
  const [a, setA] = useState('gra-2')
@@ -203,9 +212,10 @@ render(
203
212
  )
204
213
  ```
205
214
 
206
- ## Limpar (clearable)
215
+ ## Limpar a seleção
207
216
 
208
- `clearable` põe o X no controle quando há seleção — limpa num clique (single volta a vazio, multi a `[]`), sem abrir a lista. Bom pra filtro, onde "sem valor" é um estado de uso frequente; os filtros do ActionList já vêm assim.
217
+ `clearable` acrescenta uma ação que limpa o valor sem abrir a lista. No modo simples, o valor volta
218
+ a vazio; no múltiplo, volta a `[]`. Os filtros de `ActionList` já habilitam esse comportamento.
209
219
 
210
220
  ```tsx preview col md
211
221
  function Demo() {
@@ -229,9 +239,10 @@ function Demo() {
229
239
  render(<Demo />)
230
240
  ```
231
241
 
232
- ## Item rico (content)
242
+ ## Conteúdo rico nas opções
233
243
 
234
- `label` é sempre string o que o campo mostra e a busca casa); `content` é o render RICO do item na lista — ícone, avatar, duas linhas. Assim o item pode ser elaborado sem estragar busca nem a11y.
244
+ Mantenha `label` como string para busca e leitura assistiva. Use `content` para acrescentar ícone,
245
+ avatar ou texto complementar à opção apresentada na lista.
235
246
 
236
247
  ```tsx preview col md
237
248
  const [agent, setAgent] = useState('developer')
@@ -271,9 +282,10 @@ render(
271
282
  )
272
283
  ```
273
284
 
274
- ## Barra do composer (variant ghost)
285
+ ## Select na barra do Composer
275
286
 
276
- `variant="ghost"` tira moldura e padding: o seletor discreto que vive na barra de ações do Composer/Chat (app, task, agente), à esquerda do enviar. `triggerLabel` na opção encurta o gatilho quando o rótulo da lista é longo (o título inteiro fica só na lista).
287
+ Use `variant="ghost"` para integrar o seletor à barra de ações de `Composer`. `triggerLabel` pode
288
+ encurtar somente o texto do gatilho; a lista continua exibindo o `label` completo.
277
289
 
278
290
  ```tsx preview col-start
279
291
  const [task, setTask] = useState('GB-42')
@@ -292,9 +304,10 @@ render(
292
304
  )
293
305
  ```
294
306
 
295
- ## Ação no fim do campo (trailing)
307
+ ## Ação no fim do campo
296
308
 
297
- `trailing` põe uma ação custom DENTRO do controle, no fim (antes do chevron) — um botão que age sobre o valor escolhido, sem virar um irmão solto ao lado do campo. O clique no trailing **não** abre a lista (o slot para a propagação).
309
+ `trailing` posiciona uma ação relacionada ao valor antes do chevron. Interagir com essa região não
310
+ abre a lista.
298
311
 
299
312
  ```tsx preview col-start
300
313
  const [task, setTask] = useState('gra-2')
@@ -318,9 +331,9 @@ render(
318
331
  )
319
332
  ```
320
333
 
321
- ## Props
334
+ ## Propriedades de Select
322
335
 
323
- | Prop | Tipo | Default | Descrição |
336
+ | Propriedade | Tipo | Padrão | Descrição |
324
337
  |---|---|---|---|
325
338
  | `options` | `SelectOption[]` | | As opções: `{ value, label, hint?, content?, triggerLabel?, group?, disabled? }`. |
326
339
  | `value` | `string \| string[]` | | O selecionado: string no single, string[] no multiple. |
@@ -328,16 +341,16 @@ render(
328
341
  | `native` | `boolean` | `false` | Renderiza o `<select>` do sistema. Exclui busca, multi e ghost (o browser é quem desenha a lista). |
329
342
  | `searchable` | `boolean` | `false` | O campo vira busca: filtra a lista enquanto digita. |
330
343
  | `multiple` | `boolean` | `false` | Chips removíveis, lista que permanece aberta e Selecionar tudo. |
331
- | `variant` | `'default' \| 'ghost'` | `'default'` | `ghost` = sem moldura, pra barra do composer. |
344
+ | `variant` | `'default' \| 'ghost'` | `'default'` | `ghost` = sem moldura, para barra do composer. |
332
345
  | `placeholder` | `string` | `'Selecione…'` | Texto do campo vazio. |
333
- | `searchPlaceholder` | `string` | | Placeholder enquanto busca; cai pro `placeholder` se ausente. |
346
+ | `searchPlaceholder` | `string` | | Placeholder enquanto busca; cai para o `placeholder` se ausente. |
334
347
  | `emptyText` | `string` | `'Nada encontrado.'` | Mensagem quando a busca não acha nada. |
335
348
  | `onSearch` | `(query: string) => void` | | Busca server-side (debounced, ao abrir e ao digitar): desliga o filtro do cmdk — o pai atualiza `options`. |
336
349
  | `loading` | `boolean` | | Mostra Buscando… enquanto o fetch corre (use com `onSearch`). |
337
- | `clearable` | `boolean` | `false` | X no controle quando há seleção — limpa num clique. |
350
+ | `clearable` | `boolean` | `false` | X no controle quando há seleção — limpa em um clique. |
338
351
  | `icon` | `React.ReactNode` | | Ícone leading DENTRO do controle (decorativo) — herda `size-4` e o tom muted. |
339
352
  | `trailing` | `React.ReactNode` | | Ação custom no FIM do controle (antes do chevron) — o clique não abre a lista. |
340
- | `size` | `'default' \| 'sm'` | `'default'` | Altura: default (h-9, a do Input e do Button) ou sm (h-8) pra toolbar densa. |
353
+ | `size` | `'default' \| 'sm'` | `'default'` | Altura: default (h-9, a do Input e do Button) ou sm (h-8) para toolbar densa. |
341
354
  | `disabled` | `boolean` | `false` | Esmaece e trava o controle. |
342
- | `id` | `string` | | Vai pro campo — pra parear com o `htmlFor` do Label. |
355
+ | `id` | `string` | | Vai para o campo — para parear com o `htmlFor` do Label. |
343
356
  | `className` | `string` | | Classes da raiz do controle, incluindo campo, ícones e ações, em todos os modos. |
@@ -1,6 +1,6 @@
1
- ## Horizontal
1
+ ## Separar blocos empilhados
2
2
 
3
- Divide blocos empilhados ocupa a largura toda do contêiner.
3
+ Use a orientação horizontal entre blocos empilhados. A linha ocupa toda a largura disponível.
4
4
 
5
5
  ```tsx preview col
6
6
  <div>
@@ -13,7 +13,7 @@ Divide blocos empilhados — ocupa a largura toda do contêiner.
13
13
 
14
14
  ## Vertical
15
15
 
16
- Entre itens de uma linha. O vertical herda a altura do contêiner — dê altura à linha (ex.: h-4 no flex).
16
+ Use a orientação vertical entre itens lado a lado. A linha herda a altura do contêiner.
17
17
 
18
18
  ```tsx preview
19
19
  <div className="flex h-4 items-center gap-3 text-sm">
@@ -25,9 +25,9 @@ Entre itens de uma linha. O vertical herda a altura do contêiner — dê altura
25
25
  </div>
26
26
  ```
27
27
 
28
- ## Props
28
+ ## Propriedades de Separator
29
29
 
30
- | Prop | Tipo | Default | Descrição |
30
+ | Propriedade | Tipo | Padrão | Descrição |
31
31
  |---|---|---|---|
32
32
  | `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | Direção da linha. Vertical precisa de altura vinda do contêiner. |
33
33
  | `decorative` | `boolean` | `true` | Decorativa some da árvore de acessibilidade. Use false quando a divisão é semântica. |
@@ -1,79 +1,348 @@
1
- ## Sidebar
1
+ ## Navegação dentro de um layout
2
2
 
3
- `Sidebar` é uma coluna de chrome e navegação. Ela não escolhe a posição: entra em qualquer `Pane` de um `Split`. A mesma forma serve para a sidebar global do app e a navegação interna de Configurações.
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`.
6
+
7
+ O exemplo canônico mantém a largura inicial em `rem`, deixa a navegação rolar e preserva o
8
+ cabeçalho e o rodapé nas extremidades.
4
9
 
5
10
  ```tsx preview
6
11
  render(
7
- <div className="h-72 overflow-hidden rounded-lg border border-border">
8
- <Split resizable>
9
- <Pane initialSize={32} minSize={20} inset="none">
10
- <Sidebar divider={false} className="w-full">
11
- <PaneHeader className="px-4 py-3 text-sm font-semibold">Projeto</PaneHeader>
12
+ <div className="h-80 overflow-hidden rounded-lg border border-border">
13
+ <Split>
14
+ <Pane initialSize="16rem" inset="none">
15
+ <Sidebar className="w-full">
16
+ <PaneHeader className="px-4 py-3 text-sm font-semibold">
17
+ Projeto
18
+ </PaneHeader>
12
19
  <PaneBody>
13
20
  <SidebarNav
14
- groups={[{ label: 'Design', items: [{ id: 'preview', label: 'Preview' }, { id: 'code', label: 'Código' }] }]}
15
- activeId="preview"
21
+ groups={[
22
+ {
23
+ label: 'Workspace',
24
+ items: [
25
+ { id: 'overview', label: 'Visão geral', icon: <LayoutDashboard /> },
26
+ { id: 'sessions', label: 'Sessões', icon: <MessagesSquare /> },
27
+ ],
28
+ },
29
+ ]}
30
+ activeId="overview"
16
31
  onSelect={() => {}}
17
32
  />
18
33
  </PaneBody>
19
- <PaneFooter><span className="text-xs text-muted-foreground">Configurações</span></PaneFooter>
34
+ <PaneFooter>
35
+ <SidebarItem label="Configurações" icon={<Settings />} onClick={() => {}} />
36
+ </PaneFooter>
20
37
  </Sidebar>
21
38
  </Pane>
22
- <Pane grow inset="lg"><p className="text-sm text-muted-foreground">Conteúdo da tela.</p></Pane>
39
+ <Pane grow inset="lg">
40
+ <p className="text-sm text-muted-foreground">Conteúdo da página.</p>
41
+ </Pane>
23
42
  </Split>
24
43
  </div>,
25
44
  )
26
45
  ```
27
46
 
28
- ## Colapso
29
-
30
- `collapsed` pertence à própria `Sidebar`; `SidebarItem`, `SidebarNav` e `ShellNav` adaptam-se automaticamente para botões `size-9` centralizados, ícones e tooltips. `PaneHeader`, `PaneBody` e `PaneFooter` são os slots do pane: a aplicação mantém a identidade e ações que lhe pertencem sem atribuí-las artificialmente à sidebar.
31
-
32
- `SidebarItem` também é a linha de árvores de navegação. `actions` e o controle formado por
33
- `onToggle`/`expanded` são irmãos do botão principal, portanto menus e chevrons não criam
34
- controles interativos aninhados. No modo recolhido, a linha conserva somente o destino com
35
- ícone e tooltip. No modo aberto, o chevron ocupa o lugar do ícone da pasta durante o hover da própria linha ou
36
- foco, e as ações ficam sobrepostas à extremidade direita: controles invisíveis não reduzem o
37
- espaço disponível para o rótulo. O foco comum no destino não mantém o chevron aberto;
38
- somente o foco visível no próprio controle o revela para navegação por teclado.
39
- As ações aparecem no hover da própria linha, no foco visível do próprio controle ou enquanto o menu está
40
- aberto; focar o destino da linha não revela as reticências.
41
- O rótulo que não cabe na linha desaparece num gradiente até a borda, em vez de terminar em
42
- reticências; a linha mede o próprio transbordo, então o rótulo que cabe inteiro fica intacto.
43
- O foco visível usa o mesmo anel do `Button` (`ring-2 ring-ring/50`), na linha aberta, no botão do
44
- rail recolhido e no chevron sem ele a navegação por teclado cairia no anel padrão do navegador,
45
- que destoa do tema.
46
- Durante drag-and-drop, `dropPosition="before"` e `"after"` desenham uma linha sobreposta ao
47
- limite do item; `"inside"` realça a superfície da pasta. O indicador nunca reserva espaço.
48
-
49
- Árvores montadas por composição usam `SidebarGroupLabel` para os mesmos rótulos discretos
50
- que o `SidebarNav` desenha automaticamente. O heading permanece `text-sm`; hierarquia vem
51
- do peso médio e da cor atenuada, não de reduzir legibilidade.
47
+ Use `resizable` no `Split` somente quando ajustar a largura fizer parte da tarefa. Nesse caso, o
48
+ `Pane` também declara `minSize`, e `divider={false}` evita que `Sidebar` desenhe uma segunda
49
+ divisória ao lado do handle.
50
+
51
+ ## Anatomia e responsabilidades
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.
55
+
56
+ | Parte | Responsabilidade |
57
+ |---|---|
58
+ | `Split` | Define a relação espacial e, opcionalmente, o redimensionamento. |
59
+ | `Pane` | Define largura, limites e espaçamento interno da coluna. |
60
+ | `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
+ | `SidebarNav` ou `ShellNav` | Apresenta e controla os destinos de navegação. |
65
+
66
+ `PaneContent` permanece como alias temporário de `PaneBody` durante a versão 12. Código novo usa
67
+ `PaneBody`.
68
+
69
+ ## Escolher a navegação
70
+
71
+ Use `SidebarNav` para grupos planos de destinos. Ele recebe dados e produz `SidebarItem` com a mesma
72
+ semântica usada na composição manual.
73
+
74
+ ```tsx preview
75
+ <div className="h-72 w-64 overflow-hidden rounded-lg border border-border">
76
+ <Sidebar className="w-full">
77
+ <PaneBody>
78
+ <SidebarNav
79
+ groups={[
80
+ {
81
+ label: 'Configurações',
82
+ items: [
83
+ { id: 'users', label: 'Usuários', icon: <Users /> },
84
+ { id: 'roles', label: 'Papéis', icon: <ShieldCheck /> },
85
+ { id: 'imports', label: 'Importações', icon: <Database /> },
86
+ ],
87
+ },
88
+ ]}
89
+ activeId="users"
90
+ onSelect={() => {}}
91
+ />
92
+ </PaneBody>
93
+ </Sidebar>
94
+ </div>
95
+ ```
96
+
97
+ ## Compor uma árvore
98
+
99
+ Uma árvore não pertence ao contrato de `SidebarNav`: profundidade, carregamento e expansão variam
100
+ por produto. Componha cada nó com `SidebarItem`, envolva seus descendentes em `SidebarTreeGroup` e
101
+ mantenha o estado no consumidor. O grupo aplica o recuo e desenha a guia vertical; o destino
102
+ principal e o controle de expansão permanecem irmãos acessíveis.
103
+
104
+ ```tsx preview
105
+ const [open, setOpen] = useState(true)
106
+
107
+ render(
108
+ <div className="h-72 w-64 overflow-hidden rounded-lg border border-border">
109
+ <Sidebar className="w-full">
110
+ <PaneBody className="p-2">
111
+ <SidebarGroupLabel>Documentação</SidebarGroupLabel>
112
+ <SidebarItem
113
+ label="Componentes"
114
+ icon={<Folder />}
115
+ expanded={open}
116
+ onToggle={() => setOpen((current) => !current)}
117
+ onClick={() => setOpen((current) => !current)}
118
+ />
119
+ {open && (
120
+ <SidebarTreeGroup>
121
+ <SidebarItem label="Layout" icon={<Folder />} expanded onToggle={() => {}} onClick={() => {}} />
122
+ <SidebarTreeGroup>
123
+ <SidebarItem label="Sidebar" icon={<PanelLeft />} active onClick={() => {}} />
124
+ <SidebarItem label="Page" icon={<PanelsTopLeft />} onClick={() => {}} />
125
+ </SidebarTreeGroup>
126
+ </SidebarTreeGroup>
127
+ )}
128
+ </PaneBody>
129
+ </Sidebar>
130
+ </div>,
131
+ )
132
+ ```
133
+
134
+ Esse é o padrão usado pelo `DocBrowser`: seções são rótulos, grupos nomeados são nós expansíveis e
135
+ páginas são folhas. Um grupo começa aberto e volta a abrir quando contém a página ativa.
136
+
137
+ Use `ShellNav` para uma navegação plana que precisa de cabeçalho próprio e grupos ancorados no
138
+ rodapé. Ele gerencia a rolagem internamente e se adapta quando estiver dentro de uma `Sidebar`
139
+ recolhida.
52
140
 
53
141
  ```tsx
54
142
  <Sidebar collapsed={collapsed}>
55
- <PaneHeader><MySidebarHeader onToggle={() => setCollapsed(!collapsed)} /></PaneHeader>
56
- <PaneBody><SidebarNav groups={groups} activeId={active} onSelect={go} /></PaneBody>
57
- <PaneFooter><UserMenu /></PaneFooter>
143
+ <ShellNav
144
+ heading={<ShellNavHeading title="Relatórios" action={<CreateReportButton />} />}
145
+ groups={reportGroups}
146
+ footer={settingsGroups}
147
+ activeId={activeId}
148
+ onSelect={navigateToReport}
149
+ />
58
150
  </Sidebar>
59
151
  ```
60
152
 
61
- ## Navegação contextual
153
+ ## Recolher a coluna
62
154
 
63
- `SidebarNav` aceita grupos e subgrupos. Isso cobre tanto a navegação global quanto seções internas, como Configurações ou documentação, sem outro shell especializado.
155
+ `collapsed` pertence à `Sidebar`. Nesse estado, `SidebarItem`, `SidebarNav` e `ShellNav` mantêm
156
+ somente os ícones e expõem os rótulos em tooltips. Por isso, todo destino que aparece no modo
157
+ recolhido precisa de um ícone reconhecível e de um `label` completo.
64
158
 
65
- ```tsx
66
- <SidebarNav
67
- groups={[
68
- {
69
- label: 'Configurações',
70
- subgroups: [
71
- { label: 'Acesso', items: [{ id: 'users', label: 'Usuários' }] },
72
- { label: 'Dados', items: [{ id: 'imports', label: 'Importações' }] },
73
- ],
74
- },
75
- ]}
76
- activeId={active}
77
- onSelect={go}
78
- />
159
+ ```tsx preview
160
+ const [collapsed, setCollapsed] = useState(false)
161
+
162
+ render(
163
+ <div className="flex h-64 overflow-hidden rounded-lg border border-border">
164
+ <Sidebar collapsed={collapsed}>
165
+ <PaneHeader className="flex h-12 items-center justify-center">
166
+ <Button
167
+ variant="ghost"
168
+ size="icon-sm"
169
+ aria-label={collapsed ? 'Expandir navegação' : 'Recolher navegação'}
170
+ onClick={() => setCollapsed((current) => !current)}
171
+ >
172
+ <PanelLeftClose className={collapsed ? 'rotate-180' : ''} />
173
+ </Button>
174
+ </PaneHeader>
175
+ <PaneBody>
176
+ <SidebarNav
177
+ groups={[
178
+ {
179
+ items: [
180
+ { id: 'home', label: 'Início', icon: <House /> },
181
+ { id: 'reports', label: 'Relatórios', icon: <ChartNoAxesColumn /> },
182
+ ],
183
+ },
184
+ ]}
185
+ activeId="home"
186
+ onSelect={() => {}}
187
+ />
188
+ </PaneBody>
189
+ </Sidebar>
190
+ <div className="flex-1 p-4 text-sm text-muted-foreground">
191
+ O conteúdo ocupa o espaço liberado pela sidebar.
192
+ </div>
193
+ </div>,
194
+ )
195
+ ```
196
+
197
+ O consumidor controla o estado e decide onde colocar o gatilho. A transição de largura pertence à
198
+ `Sidebar`; a aplicação não precisa trocar a árvore de componentes.
199
+
200
+ ## Itens com expansão e ações
201
+
202
+ `SidebarItem` mantém o destino principal, a expansão e as ações como controles irmãos. Essa
203
+ estrutura evita botões aninhados e permite que cada controle receba foco de forma independente.
204
+
205
+ ```tsx preview
206
+ <div className="w-72 rounded-lg border border-border p-2">
207
+ <SidebarItem
208
+ label="Contratos"
209
+ icon={<Folder />}
210
+ expanded
211
+ onToggle={() => {}}
212
+ actions={
213
+ <Button variant="ghost" size="icon-xs" aria-label="Mais ações">
214
+ <Ellipsis />
215
+ </Button>
216
+ }
217
+ onClick={() => {}}
218
+ />
219
+ <SidebarItem
220
+ label="Criar workspace"
221
+ icon={<FilePlus2 />}
222
+ className="pl-6"
223
+ onClick={() => {}}
224
+ />
225
+ </div>
79
226
  ```
227
+
228
+ Quando o rótulo não cabe, ele termina em um gradiente. As ações aparecem ao passar o ponteiro, ao
229
+ receber foco visível ou enquanto um menu permanece aberto; como são sobrepostas, não reduzem o
230
+ espaço disponível para o texto.
231
+
232
+ Para árvores arrastáveis, `dropPosition` comunica o destino visual sem reservar espaço:
233
+ `before` e `after` desenham uma linha na fronteira, enquanto `inside` realça a superfície do item.
234
+ O driver de drag-and-drop continua externo e fornece seus atributos por `dragProps`.
235
+
236
+ ## Acessibilidade
237
+
238
+ - `activeId` resulta em `aria-current="page"` no destino atual.
239
+ - `navLabel` nomeia a landmark de navegação.
240
+ - O controle de expansão informa `aria-expanded` e recebe um rótulo baseado no item.
241
+ - Itens desabilitados continuam visíveis, mas não respondem à interação.
242
+ - O foco visível usa o anel semântico do tema no destino, nas ações e no controle de expansão.
243
+ - No modo recolhido, o tooltip preserva o nome que deixou de aparecer visualmente.
244
+
245
+ ## Propriedades de Sidebar
246
+
247
+ | Propriedade | Tipo | Padrão | Descrição |
248
+ |---|---|---|---|
249
+ | `collapsed` | `boolean` | `false` | Recolhe a coluna e publica esse estado para as navegações descendentes. |
250
+ | `divider` | `boolean` | `true` | Desenha a borda na lateral. Desative quando o `Split` já fornecer o handle. |
251
+ | `className` | `string` | | Ajusta a raiz. A largura externa deve continuar pertencendo ao `Pane`. |
252
+ | `children` | `ReactNode` | | Cabeçalho, corpo, rodapé ou uma navegação que gerencie a própria estrutura. |
253
+
254
+ ## Propriedades de PaneHeader
255
+
256
+ | Propriedade | Tipo | Padrão | Descrição |
257
+ |---|---|---|---|
258
+ | `className` | `string` | | Ajusta a faixa fixa e seu espaçamento. |
259
+ | `children` | `ReactNode` | | Identidade, contexto ou ações apresentadas no topo. |
260
+
261
+ ## Propriedades de PaneBody
262
+
263
+ | Propriedade | Tipo | Padrão | Descrição |
264
+ |---|---|---|---|
265
+ | `className` | `string` | | Ajusta a região flexível e rolável. |
266
+ | `children` | `ReactNode` | | Conteúdo que ocupa o espaço entre cabeçalho e rodapé. |
267
+
268
+ ## Propriedades de PaneFooter
269
+
270
+ | Propriedade | Tipo | Padrão | Descrição |
271
+ |---|---|---|---|
272
+ | `className` | `string` | | Ajusta a faixa fixa e seu espaçamento. |
273
+ | `children` | `ReactNode` | | Navegação ou ações que precisam permanecer acessíveis no rodapé. |
274
+
275
+ ## Propriedades de SidebarNav
276
+
277
+ | Propriedade | Tipo | Padrão | Descrição |
278
+ |---|---|---|---|
279
+ | `groups` | `SidebarNavGroup[]` | | Grupos e itens apresentados na ordem recebida. Grupos vazios são omitidos. |
280
+ | `activeId` | `string` | | Identificador do destino atual. |
281
+ | `onSelect` | `(id: string) => void` | | Recebe o identificador selecionado; roteamento permanece com o consumidor. |
282
+ | `navLabel` | `string` | `'Navegação'` | Nome acessível da landmark `nav`. |
283
+ | `className` | `string` | | Ajusta a raiz da navegação. |
284
+
285
+ ### Estrutura de SidebarNavGroup
286
+
287
+ | Campo | Tipo | Descrição |
288
+ |---|---|---|
289
+ | `label` | `string` | Rótulo opcional do grupo. Rótulos vazios não reservam espaço. |
290
+ | `items` | `SidebarNavItem[]` | Itens diretos do grupo. |
291
+
292
+ `SidebarNavItem` acrescenta `id` às propriedades de `SidebarItem`. Para mais níveis, componha
293
+ `SidebarItem` e `SidebarTreeGroup` diretamente.
294
+
295
+ ## Propriedades de SidebarItem
296
+
297
+ | Propriedade | Tipo | Padrão | Descrição |
298
+ |---|---|---|---|
299
+ | `label` | `string` | | Nome visível do destino e conteúdo do tooltip no modo recolhido. |
300
+ | `icon` | `ReactNode` | | Ícone apresentado antes do rótulo e preservado no modo recolhido. |
301
+ | `badge` | `ReactNode` | | Informação curta no fim da linha, como contagem ou estado. |
302
+ | `active` | `boolean` | `false` | Destaca o destino e aplica `aria-current="page"`. |
303
+ | `disabled` | `boolean` | `false` | Mantém o item visível sem permitir interação. |
304
+ | `actions` | `ReactNode` | | Controles relacionados exibidos no fim da linha. |
305
+ | `onToggle` | `() => void` | | Adiciona o controle independente de expansão. |
306
+ | `expanded` | `boolean` | `false` | Define o estado acessível e a rotação do controle de expansão. |
307
+ | `dropPosition` | `'before' \| 'inside' \| 'after'` | | Indica visualmente onde um item arrastado será solto. |
308
+ | `dragProps` | `HTMLAttributes<HTMLDivElement>` | | Encaminha atributos fornecidos pelo driver de drag-and-drop para a linha. |
309
+ | `tooltipHint` | `ReactNode` | | Acrescenta contexto ao tooltip do modo recolhido. |
310
+ | `className` | `string` | | Ajusta a linha nos estados aberto e recolhido. |
311
+ | `onClick` | `() => void` | | Executa a navegação principal do item. |
312
+
313
+ ## Propriedades de SidebarGroupLabel
314
+
315
+ | Propriedade | Tipo | Padrão | Descrição |
316
+ |---|---|---|---|
317
+ | `className` | `string` | | Ajusta o rótulo discreto do grupo. |
318
+ | `children` | `ReactNode` | | Nome do conjunto de destinos. |
319
+
320
+ ## Propriedades de SidebarTreeGroup
321
+
322
+ | Propriedade | Tipo | Padrão | Descrição |
323
+ |---|---|---|---|
324
+ | `className` | `string` | | Ajusta o grupo sem substituir o recuo, o espaçamento e a guia vertical padrão. |
325
+ | `children` | `ReactNode` | | Itens ou grupos que descendem do nó anterior. |
326
+
327
+ ## Propriedades de ShellNav
328
+
329
+ | Propriedade | Tipo | Padrão | Descrição |
330
+ |---|---|---|---|
331
+ | `groups` | `ShellNavGroup[]` | | Grupos planos exibidos na região rolável. Grupos vazios são omitidos. |
332
+ | `activeId` | `string` | | Identificador do destino atual. |
333
+ | `onSelect` | `(id: string) => void` | | Recebe o identificador selecionado. |
334
+ | `heading` | `ReactNode` | | Conteúdo fixo acima da navegação; fica oculto no modo recolhido. |
335
+ | `footer` | `ShellNavGroup[]` | | Grupos ancorados abaixo da região rolável. |
336
+ | `navLabel` | `string` | `'Navegação'` | Nome acessível da landmark `nav`. |
337
+ | `className` | `string` | | Ajusta a coluna da navegação. A largura vem do contêiner. |
338
+
339
+ Cada `ShellNavGroup` recebe `label` opcional e `items`. Um `ShellNavItem` declara `id`, `label`,
340
+ `icon`, `badge` e `disabled`.
341
+
342
+ ## Propriedades de ShellNavHeading
343
+
344
+ | Propriedade | Tipo | Padrão | Descrição |
345
+ |---|---|---|---|
346
+ | `title` | `ReactNode` | | Título da navegação. |
347
+ | `action` | `ReactNode` | | Ação relacionada apresentada no extremo oposto. |
348
+ | `className` | `string` | | Ajusta a faixa de cabeçalho. |
@@ -1,6 +1,7 @@
1
1
  ## Linha de lista com avatar
2
2
 
3
- O skeleton imita a forma do conteúdo: círculo pro avatar (rounded-full), barras com a largura aproximada do texto.
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
5
 
5
6
  ```tsx preview col
6
7
  <div className="flex items-center gap-3">
@@ -14,7 +15,7 @@ O skeleton imita a forma do conteúdo: círculo pro avatar (rounded-full), barra
14
15
 
15
16
  ## Card em carregamento
16
17
 
17
- O esqueleto reproduz o layout final do card — título, descrição, conteúdo e ações — pra tela não pular quando os dados chegarem.
18
+ O esqueleto reproduz o layout final do card — título, descrição, conteúdo e ações — para tela não pular quando os dados chegarem.
18
19
 
19
20
  ```tsx preview col
20
21
  <Card>