@softize/opus 26.0.0 → 26.2.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 (35) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/bin/lib/check.mjs +9 -32
  3. package/bin/lib/copy.mjs +7 -4
  4. package/docs/adr/0014-structural-headers-do-not-carry-description.md +1 -1
  5. package/docs/adr/0018-modal-header-only-names-the-surface.md +1 -1
  6. package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +4 -0
  7. package/docs/adr/0057-doc-browser-permite-busca-no-chrome-do-consumidor.md +27 -0
  8. package/docs/adr/0059-subheadings-orient-without-carrying-business-rules.md +51 -0
  9. package/docs/adr/README.md +2 -0
  10. package/package.json +1 -1
  11. package/registry/skills/build-opus-ui/references/evaluations.md +8 -0
  12. package/registry/skills/build-opus-ui/references/ui-patterns.md +14 -4
  13. package/src/core/types.ts +3 -2
  14. package/src/ui/components/patterns/action-list-dialog.tsx +14 -7
  15. package/src/ui/components/patterns/form-dialog.tsx +18 -7
  16. package/src/ui/components/patterns/form.tsx +36 -8
  17. package/src/ui/components/patterns/page.tsx +22 -0
  18. package/src/ui/components/patterns/sidebar.tsx +17 -3
  19. package/src/ui/components/patterns/surface-anatomy.ts +1 -1
  20. package/src/ui/components/primitives/dialog.tsx +28 -1
  21. package/src/ui/components/primitives/drawer.tsx +30 -2
  22. package/src/ui/docs/DocBrowser.tsx +128 -81
  23. package/src/ui/docs/content/action-form-card.md +1 -1
  24. package/src/ui/docs/content/action-form-dialog.md +8 -0
  25. package/src/ui/docs/content/action-form.md +18 -5
  26. package/src/ui/docs/content/action-list-dialog.md +8 -0
  27. package/src/ui/docs/content/dialog.md +24 -2
  28. package/src/ui/docs/content/documentation-portal.md +45 -2
  29. package/src/ui/docs/content/drawer.md +24 -2
  30. package/src/ui/docs/content/page.md +23 -1
  31. package/src/ui/docs/content/sidebar.md +23 -0
  32. package/src/ui/docs/content/surface.md +8 -2
  33. package/src/ui/docs/types.ts +2 -0
  34. package/src/ui/lib/modal-layers.ts +27 -3
  35. package/src/ui/react.tsx +3 -0
@@ -84,12 +84,21 @@ do mesmo peso visual. Nesse caso, o cancelamento muda para `outline`, salvo esco
84
84
  `cancelVariant`. Para apresentar o formulário como uma seção delimitada da página, use
85
85
  [`ActionFormCard`](/ui/action-form-card). As propriedades de cada wrapper estão na página dele.
86
86
 
87
- ## Ajuda no rótulo
87
+ ## Hint visível ou ícone de help
88
88
 
89
- `FieldSpec.help` aparece como um ícone junto ao rótulo, com o texto em um tooltip. Escreva ali o
90
- critério, o efeito ou a limitação que o rótulo não diz; ajuda que só repete o rótulo deve ser omitida.
91
- Não existe texto auxiliar sob o campo: quem precisa de uma frase permanente abaixo do controle usa
92
- `FieldDescription` na composição própria.
89
+ Use `FieldSpec.hint` abaixo do controle quando a pessoa precisar da informação para preencher
90
+ corretamente ou prever o efeito da escolha. Exemplo: “Sem áreas selecionadas, a pasta aparece em
91
+ todas as áreas.” O texto permanece visível e participa do `aria-describedby` do controle, junto
92
+ do erro quando houver. Em uma composição própria, use `FieldDescription` com a mesma associação.
93
+
94
+ Use `FieldSpec.help` no ícone junto à label para detalhes complementares, definições e exemplos
95
+ opcionais. A pergunta é: sem abrir essa ajuda, a pessoa ainda consegue escolher corretamente?
96
+ Se não, o trecho necessário deve ser hint. Não use o comprimento da frase como critério nem
97
+ esconda uma condição obrigatória no tooltip. Omita texto que apenas repete a label.
98
+
99
+ Hint e help podem coexistir se tiverem funções diferentes, sem duplicar a mesma frase. Um erro
100
+ explica uma entrada inválida e sua correção; não substitui uma orientação necessária antes de
101
+ preencher. Uma regra que afeta vários campos pertence ao corpo ou ao grupo correspondente.
93
102
 
94
103
  `LabelHelp` é esse mesmo ícone, exportado para um formulário sem contrato mostrar a ajuda no mesmo
95
104
  lugar:
@@ -185,3 +194,7 @@ ciclo de vida que o pattern não cobre; validação e execução continuam iguai
185
194
  | Propriedade | Tipo | Descrição |
186
195
  | ----------- | -------- | ------------------------------------------------------------ |
187
196
  | `help` | `string` | O texto do tooltip. Vazio ou ausente, o ícone não renderiza. |
197
+
198
+ O `hint` aceita texto ou `I18nRef` e permanece visível; o contexto do formulário entrega o texto
199
+ resolvido aos campos personalizados. O botão de `help` participa do Tab e abre por foco ou hover.
200
+ Dialog e Drawer evitam que a ajuda seja o primeiro foco automático quando há outro controle.
@@ -96,6 +96,13 @@ const roleOptions = roles.items.map((role) => ({ value: role.id, label: role.nam
96
96
  </ActionListDialog>;
97
97
  ```
98
98
 
99
+ ## Orientação e contexto
100
+
101
+ `description` apresenta a finalidade no cabeçalho, como “Escolha onde a pasta e suas subpastas
102
+ aparecem.”. É opcional; não repita o título. `intro` apresenta no corpo informações necessárias
103
+ à decisão que não pertencem a um campo específico. Regras, condições e consequências não ficam
104
+ somente no subheading. Quando ambos são fornecidos, ambos participam de `aria-describedby`.
105
+
99
106
  ## Propriedades de ActionListDialog
100
107
 
101
108
  | Propriedade | Tipo | Padrão | Descrição |
@@ -103,6 +110,7 @@ const roleOptions = roles.items.map((role) => ({ value: role.id, label: role.nam
103
110
  | `action / input` | `ListAction / TInput` | `input`: `{}` | O contrato e o escopo base da consulta. Uma mudança em `input` refaz a consulta; as invalidações declaradas por forms e triggers também. |
104
111
  | `open / onOpenChange` | `boolean / (open) => void` | | Estado controlado do modal. |
105
112
  | `title` | `string` | | Nome da lista exibido no cabeçalho fixo. |
113
+ | `description` | `ReactNode` | | Orientação opcional sob o título; não recebe regras de negócio. |
106
114
  | `intro` | `ReactNode` | | Contexto relevante no início do corpo, como texto ou `Alert`. |
107
115
  | `children` | `(items, refetch) => ReactNode` | | A composição dos itens. Carregamento, erro e vazio já são tratados pelo `ActionList`. |
108
116
  | `note` | `ReactNode` | | Contexto curto à esquerda da linha de ações. O total de itens já aparece no rodapé do `ActionList`. |
@@ -150,8 +150,21 @@ Voltar e ações do cabeçalho usam `Button` `ghost` no tamanho `icon` (2.25rem)
150
150
  [`Presentation`](/ui/presentation) usa no voltar. O botão de fechar é menor: um `CloseButton` no
151
151
  quadrado `icon-sm` (1.75rem) por padrão.
152
152
 
153
- O cabeçalho não recebe descrição: um modal já é a superfície de uma tarefa, e o contexto que ajuda a
154
- decidir aparece no começo do corpo, como texto ou `Alert`.
153
+ O cabeçalho aceita `DialogDescription` como subheading opcional para orientar sobre a finalidade.
154
+ Omita quando repetir o título. Condições, regras de negócio e consequências necessárias à decisão
155
+ ficam junto dos controles ou em `DialogBody`. Uma frase curta também pode ser regra: o critério é se
156
+ pular o texto compromete a decisão, não sua extensão.
157
+
158
+ ```tsx
159
+ <DialogHeader>
160
+ <DialogTitle>Organizar pasta</DialogTitle>
161
+ <DialogDescription>Escolha onde a pasta e suas subpastas aparecem.</DialogDescription>
162
+ </DialogHeader>
163
+ ```
164
+
165
+ Na composição direta, associe o `id` do texto por `aria-describedby` em `DialogContent` quando ele
166
+ descrever a superfície para tecnologias assistivas. A regra sobre seleção vazia pertence ao
167
+ campo de áreas e permanece fora do subheading.
155
168
 
156
169
  ## Identificar a tarefa com um ícone
157
170
 
@@ -634,3 +647,12 @@ conteúdo no primeiro bloco de `DialogBody`. `DialogIntro` exige um `DialogTitle
634
647
 
635
648
  No formato compacto, mantenha `DialogTitle` e `DialogActions` no próprio `DialogHeader`. Para
636
649
  comparar as duas formas em cada `layout`, abra [Composições](/ui/compositions).
650
+
651
+ ## Propriedades de DialogDescription
652
+
653
+ | Propriedade | Tipo | Padrão | Descrição |
654
+ | --- | --- | --- | --- |
655
+ | `children` | `ReactNode` | | Orientação opcional sob o título em `DialogHeader` ou `DialogIntro`. |
656
+ | `id` | `string` | | Identificador para associação acessível explícita. |
657
+ | `className` | `string` | | Ajusta a apresentação do parágrafo. |
658
+ | `…props` | `ComponentProps<"p">` | | Atributos nativos. |
@@ -46,6 +46,36 @@ conceitos relacionados, não para reproduzir automaticamente a árvore de arquiv
46
46
  tem `slug`, `title` e `render`; `searchText` acrescenta conteúdo à busca e `navigationTitle` permite
47
47
  um rótulo lateral curto sem mudar o título completo.
48
48
 
49
+ Um grupo pode conter páginas próprias e subgrupos em `groups`. Por exemplo, uma documentação
50
+ por domínio pode reunir as conexões externas em Integrações e manter um grupo por sistema:
51
+
52
+ ```tsx
53
+ const sections: DocSection[] = [{
54
+ label: 'Domínios',
55
+ groups: [{
56
+ label: 'Integrações',
57
+ pages: [{ slug: 'integrations', title: 'Integrações', render: () => <p>Conexões com sistemas externos.</p> }],
58
+ groups: [{
59
+ label: 'Insider',
60
+ pages: [{ slug: 'integrations/insider', title: 'Insider', render: () => <p>Envio de atributos e eventos.</p> }],
61
+ }],
62
+ }],
63
+ }]
64
+ ```
65
+
66
+ As páginas próprias vêm antes dos subgrupos na sequência de leitura. Um grupo com `pages: []`
67
+ pode reunir somente subgrupos; ramos sem páginas em nenhum nível não aparecem na lateral.
68
+ O contexto editorial usa o grupo nomeado mais próximo. A busca revela os ancestrais dos
69
+ resultados e, no modo `sidebarInitiallyCollapsed`, restaura os ramos abertos manualmente
70
+ quando a consulta é limpa. No modo expandido padrão, os ancestrais da página ativa são
71
+ reabertos para mantê-la visível. `groups` é opcional:
72
+ as declarações existentes continuam válidas. Essa hierarquia é uma escolha de leitura do host,
73
+ não exige mover arquivos nem muda a descoberta por pastas.
74
+
75
+ Se o host também percorre as páginas para montar índices ou resolver links, esse percurso
76
+ precisa visitar `group.groups` recursivamente. Achatar somente `section.groups[].pages`
77
+ não inclui as páginas dos subgrupos.
78
+
49
79
  Com `editorial`, a leitura recebe contexto, sumário dos títulos renderizados e links de página
50
80
  anterior e seguinte. `additionalPages` preserva destinos diretos que não entram nessa sequência.
51
81
  A busca documental do topo mantém seu rótulo, valor e função ao navegar para catálogos e detalhes;
@@ -64,6 +94,19 @@ o contrato desktop a partir de `64rem`. O aplicativo organiza a moldura do porta
64
94
 
65
95
  ADRs e changelog mantêm o mapa dos guias, sem acrescentar uma árvore para cada registro. Use `sidebarLinks` para oferecer entradas
66
96
  no mesmo nível dos grupos, após os capítulos, sem incluí-las na sequência anterior/próxima.
97
+ Destinos com URL HTTP(S) absoluta usam navegação nativa, inclusive para outro aplicativo;
98
+ destinos relativos usam o roteamento compartilhado.
99
+
100
+ Para abrir uma ferramenta sem substituir a leitura atual, declare `newTab` no destino:
101
+
102
+ ```tsx
103
+ <DocBrowser sections={sections}
104
+ sidebarLinks={[{ label: 'Lente', href: '/lens', newTab: true }]} />
105
+ ```
106
+
107
+ O link exibe um ícone de nova aba, informa a abertura no nome acessível e usa
108
+ `rel="noopener noreferrer"`. Sem `newTab`, mantém a navegação na aba atual.
109
+
67
110
  `DocCatalog` recebe registros derivados dos Markdown e uma consulta local independente do cabeçalho.
68
111
  Passe o catálogo em `content` para preservar a lateral:
69
112
 
@@ -148,7 +191,7 @@ não define as rotas nem a autorização do aplicativo.
148
191
 
149
192
  | Propriedade | Tipo | Padrão | Uso |
150
193
  | --- | --- | --- | --- |
151
- | `sections` | `DocSection[]` | Obrigatória na entrada leve | Seções, grupos e páginas na ordem de leitura. |
194
+ | `sections` | `DocSection[]` | Obrigatória na entrada leve | Seções, grupos, subgrupos opcionais e páginas na ordem de leitura. |
152
195
  | `basePath` | `string` | `/docs` | Prefixo das rotas. |
153
196
  | `path` | `string` | Rota compartilhada | Caminho controlado por um host externo. |
154
197
  | `redirects` | `Record<string, string>` | `{}` | Slugs anteriores e seus destinos canônicos. |
@@ -160,7 +203,7 @@ não define as rotas nem a autorização do aplicativo.
160
203
  | `navigationLabel` | `string` | `Documentação` | Nome da área no contexto editorial; por exemplo, `UI` para o catálogo de componentes. |
161
204
  | `additionalPages` | `DocEntry[]` | `[]` | Destinos diretos fora da árvore e da sequência. |
162
205
  | `content` | `ReactNode` | — | Conteúdo do host no lugar da página documental, mantendo a lateral. O host resolve rota e título; `null` deixa a leitura vazia. |
163
- | `sidebarLinks` | `{ label: string; href: string; active?: boolean }[]` | `[]` | Destinos globais fora dos capítulos; `active` marca o catálogo atual. |
206
+ | `sidebarLinks` | `{ label: string; href: string; active?: boolean; newTab?: boolean }[]` | `[]` | Destinos globais fora dos capítulos; `active` marca o catálogo atual e `newTab` abre em outra aba com ícone e descrição acessível. |
164
207
  | `sidebarInitiallyCollapsed` | `boolean` | `false` | Seções e grupos nomeados começam recolhidos, inclusive em links diretos. A busca revela resultados temporariamente sem alterar os ramos abertos pela pessoa. |
165
208
 
166
209
  A entrada histórica `@softize/opus/ui/docs` inclui o catálogo e oferece suas páginas quando
@@ -148,8 +148,21 @@ Voltar e ações do cabeçalho usam `Button` `ghost` no tamanho `icon` (2.25rem)
148
148
  [`Presentation`](/ui/presentation) usa no voltar. O botão de fechar é menor: um `CloseButton` no
149
149
  quadrado `icon-sm` (1.75rem) por padrão.
150
150
 
151
- O cabeçalho não recebe descrição: o painel já nomeia o recurso, e o contexto que ajuda a decidir
152
- aparece no começo do corpo, como texto ou `Alert`.
151
+ O cabeçalho aceita `DrawerDescription` como subheading opcional para orientar sobre a finalidade.
152
+ Omita quando repetir o título. Condições, regras de negócio e consequências necessárias à decisão
153
+ ficam junto dos controles ou em `DrawerBody`. Uma frase curta também pode ser regra: o critério é se
154
+ pular o texto compromete a decisão, não sua extensão.
155
+
156
+ ```tsx
157
+ <DrawerHeader>
158
+ <DrawerTitle>Organizar pasta</DrawerTitle>
159
+ <DrawerDescription>Escolha onde a pasta e suas subpastas aparecem.</DrawerDescription>
160
+ </DrawerHeader>
161
+ ```
162
+
163
+ Na composição direta, associe o `id` do texto por `aria-describedby` em `DrawerContent` quando ele
164
+ descrever a superfície para tecnologias assistivas. A regra sobre seleção vazia pertence ao
165
+ campo de áreas e permanece fora do subheading.
153
166
 
154
167
  ## Impedir o fechamento durante uma operação
155
168
 
@@ -301,3 +314,12 @@ Assim o título acompanha o conteúdo rolável enquanto o chrome permanece está
301
314
 
302
315
  No formato compacto, mantenha `DrawerTitle` e `DrawerActions` no próprio `DrawerHeader`. Para
303
316
  comparar as duas formas em cada `layout`, abra [Composições](/ui/compositions).
317
+
318
+ ## Propriedades de DrawerDescription
319
+
320
+ | Propriedade | Tipo | Padrão | Descrição |
321
+ | --- | --- | --- | --- |
322
+ | `children` | `ReactNode` | | Orientação opcional sob o título em `DrawerHeader` ou `DrawerIntro`. |
323
+ | `id` | `string` | | Identificador para associação acessível explícita. |
324
+ | `className` | `string` | | Ajusta a apresentação do parágrafo. |
325
+ | `…props` | `ComponentProps<"p">` | | Atributos nativos. |
@@ -1,7 +1,7 @@
1
1
  ## Esqueleto de página
2
2
 
3
3
  Use `Page` para manter cabeçalho, estado e conteúdo na mesma anatomia. `PageHeader` é a única
4
- introdução estrutural: reúne navegação contextual opcional, título e ações. O container é
4
+ introdução estrutural: reúne navegação contextual opcional, título, orientação opcional e ações. O container é
5
5
  centralizado e ocupa a largura disponível até `80rem` (`max-w-7xl`). Use `className` somente quando
6
6
  a composição pedir outro teto ou largura total.
7
7
 
@@ -41,6 +41,19 @@ render(
41
41
  );
42
42
  ```
43
43
 
44
+ ## Orientar sobre a finalidade
45
+
46
+ Use `description` na forma curta ou `PageDescription` em `PageHeader` quando uma frase breve
47
+ ajudar a reconhecer a finalidade da página. Omita texto que só repita o título. Regras de negócio,
48
+ limites e consequências ficam nos campos, ações ou corpo em que são necessários. Pular o
49
+ subheading não deve comprometer a decisão nem a conclusão correta da tarefa.
50
+
51
+ ```tsx
52
+ <Page title="Conhecimento" description="Consulte os materiais de apoio da sua área.">
53
+ <KnowledgeList />
54
+ </Page>
55
+ ```
56
+
44
57
  ## Retorno para a página pai
45
58
 
46
59
  Use `PageBack` em uma subpágina simples. O destino é explícito para continuar correto após refresh ou
@@ -311,6 +324,7 @@ estado na própria seção com [`DataState`](/ui/data-state), [`ActionView`](/ui
311
324
 
312
325
  | Propriedade | Tipo | Padrão | Descrição |
313
326
  | ----------- | -------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
327
+ | `description` | `ReactNode` | | Orientação opcional sob o título na forma curta. |
314
328
  | `title` | `ReactNode` | | O h1 da página. |
315
329
  | `actions` | `ReactNode` | | Ações contextuais no extremo oposto do cabeçalho. |
316
330
  | `layout` | `'flow' \| 'fill'` | `'flow'` | Ocupação de altura, não anatomia: `fill` limita a página ao shell para que uma região interna role e leva essa altura até uma `ActionList` composta em `PageBody`. |
@@ -414,3 +428,11 @@ ocupa a altura.
414
428
  | `retryLabel` | `string` | `'Tentar de novo'` | Texto do botão de recuperação. |
415
429
  | `children` | `ReactNode` | | Conteúdo renderizado somente em `ready`. |
416
430
  | `className` | `string` | | Classes da superfície de estado. |
431
+
432
+ ## Propriedades de PageDescription
433
+
434
+ | Propriedade | Tipo | Padrão | Descrição |
435
+ | --- | --- | --- | --- |
436
+ | `children` | `ReactNode` | | Orientação breve e opcional; filho direto de `PageHeader`. |
437
+ | `className` | `string` | | Ajusta a apresentação do parágrafo. |
438
+ | `…props` | `HTMLAttributes<HTMLParagraphElement>` | | Atributos nativos. |
@@ -73,6 +73,10 @@ diretamente quando a composição precisar de controles, badges ou hierarquia pa
73
73
  Quando o host usa um roteador, forneça também `onClick`: somente o clique simples é interceptado;
74
74
  cliques com modificadores preservam o comportamento do link. Sem `href`, o item continua sendo um botão.
75
75
 
76
+ Para manter a página atual aberta ao visitar outro destino, declare `newTab` junto de `href`.
77
+ O link usa `target="_blank"` e `rel="noopener noreferrer"`, não chama `onClick` e informa
78
+ “abre em nova aba” no nome acessível, inclusive no rail recolhido. Sem `href`, `newTab` não tem efeito.
79
+
76
80
  ## Colapso
77
81
 
78
82
  `collapsed` informa o estado compacto aos itens. Forneça labels acessíveis mesmo quando somente os
@@ -99,3 +103,22 @@ render(
99
103
  - `Sidebar` oferece a coluna de navegação.
100
104
  - `SidebarNav`, `SidebarItem` e elementos nativos organizam o conteúdo interno.
101
105
  - `Surface` oferece anatomia e tratamento visual quando a região não é uma sidebar.
106
+
107
+ ## Propriedades de SidebarItem
108
+
109
+ | Propriedade | Tipo | Padrão | Uso |
110
+ | --- | --- | --- | --- |
111
+ | `label` | `string` | Obrigatória | Nome do destino, também usado no modo recolhido. |
112
+ | `href` | `string` | — | Renderiza um link nativo quando o item está habilitado; sem ela, renderiza um botão. |
113
+ | `newTab` | `boolean` | `false` | Com `href`, abre em outra aba, informa a abertura no nome acessível e não chama `onClick`. |
114
+ | `onClick` | `() => void` | — | Ativa o botão ou intercepta o clique simples do link, exceto com `newTab`. |
115
+ | `icon`, `badge` | `ReactNode` | — | Ícone e complemento visual da linha. O modo recolhido mostra somente o ícone. |
116
+ | `active` | `boolean` | `false` | Destaca o destino atual e declara `aria-current="page"`. |
117
+ | `disabled` | `boolean` | `false` | Desabilita o alvo principal e impede que seja um link. |
118
+ | `actions` | `ReactNode` | — | Controles irmãos do alvo principal, no modo aberto. |
119
+ | `onToggle` | `() => void` | — | Exibe um controle separado para expandir ou recolher descendentes no modo aberto. |
120
+ | `expanded` | `boolean` | `false` | Estado visual e acessível do controle de expansão. |
121
+ | `dropPosition` | `'before' \| 'inside' \| 'after'` | — | Indicação visual de posição durante arraste, no modo aberto. |
122
+ | `dragProps` | `HTMLAttributes<HTMLDivElement>` | — | Eventos e atributos de arraste aplicados à linha. |
123
+ | `tooltipHint` | `ReactNode` | — | Complementa o tooltip do modo recolhido. |
124
+ | `className` | `string` | — | Classes da linha nos dois modos. |
@@ -131,12 +131,11 @@ render(
131
131
  as="section"
132
132
  framed
133
133
  title="Plano atual"
134
- description="Renova em 12 de maio."
135
134
  level={3}
136
135
  >
137
136
  <SurfaceBody>
138
137
  <p className="text-sm text-muted-foreground">
139
- Até 10 workspaces e 50 agentes.
138
+ Renova em 12 de maio. Até 10 workspaces e 50 agentes.
140
139
  </p>
141
140
  </SurfaceBody>
142
141
  <SurfaceFooter>
@@ -511,3 +510,10 @@ um wrapper filho declara relações locais mais compactas.
511
510
  | ----------- | ----------- | ------ | ---------------------------------------------------- |
512
511
  | `children` | `ReactNode` | | Ações persistentes, alinhadas no fim da linha. |
513
512
  | `className` | `string` | | Ajusta o rodapé, inclusive a distribuição das ações. |
513
+
514
+ ## Subheading e regras de negócio
515
+
516
+ `description` e `SurfaceDescription` apresentam brevemente a finalidade da região. A orientação
517
+ é opcional e não repete o título. A mesma regra vale para uma página (`level={1}`), uma seção ou
518
+ um card: condições, limites e consequências necessários à decisão ficam no corpo ou junto do
519
+ controle correspondente. Pular o subheading não deve comprometer a tarefa.
@@ -18,6 +18,8 @@ export interface DocGroup {
18
18
  label?: string
19
19
  icon?: React.ReactNode
20
20
  pages: DocEntry[]
21
+ /** Subgrupos opcionais; páginas próprias vêm antes dos grupos na sequência de leitura. */
22
+ groups?: DocGroup[]
21
23
  }
22
24
 
23
25
  export interface DocSection {
@@ -143,6 +143,21 @@ function syncHosts(): void {
143
143
  }
144
144
  }
145
145
 
146
+ /** A ajuda permanece no Tab, mas não toma o foco inicial do campo ao abrir um modal. */
147
+ export function skipHelpOnAutoFocus(event: Event): void {
148
+ if (event.defaultPrevented || !(event.target instanceof HTMLElement)) return;
149
+ const candidates = tabbables(event.target).filter(
150
+ (element) => element.tagName !== "A",
151
+ );
152
+ if (candidates[0]?.dataset.slot !== "label-help") return;
153
+ const control = candidates.find(
154
+ (element) => element.dataset.slot !== "label-help",
155
+ );
156
+ if (control === undefined) return;
157
+ control.focus();
158
+ if (document.activeElement === control) event.preventDefault();
159
+ }
160
+
146
161
  /** Elementos alcançáveis pelo Tab dentro de `root`, na ordem do documento. */
147
162
  function tabbables(root: HTMLElement): HTMLElement[] {
148
163
  const nodes: HTMLElement[] = [];
@@ -178,7 +193,10 @@ function tabbables(root: HTMLElement): HTMLElement[] {
178
193
  function focusFromKeyboard(element: HTMLElement): void {
179
194
  element.focus();
180
195
  // O Tab do navegador seleciona o texto do campo que recebe o foco; a passagem manual imita.
181
- if (element instanceof HTMLInputElement && typeof element.select === "function") {
196
+ if (
197
+ element instanceof HTMLInputElement &&
198
+ typeof element.select === "function"
199
+ ) {
182
200
  element.select();
183
201
  }
184
202
  }
@@ -342,7 +360,10 @@ export function registerFloatingLayer(
342
360
  // podem tratar o Tab e marcá-lo com `preventDefault`.
343
361
  const onKeyDown = (event: KeyboardEvent): void => {
344
362
  if (!isTabKey(event) || event.defaultPrevented) return;
345
- if (!layerExemptionsActive() || layerOf(document.activeElement) !== record) {
363
+ if (
364
+ !layerExemptionsActive() ||
365
+ layerOf(document.activeElement) !== record
366
+ ) {
346
367
  return;
347
368
  }
348
369
  const candidates = tabbables(host);
@@ -361,7 +382,10 @@ export function registerFloatingLayer(
361
382
  if (layerWherePersonIs() === record) escape()?.(event);
362
383
  };
363
384
  const observer = new MutationObserver(() => {
364
- if (pointerAnchor !== null && pointerAnchor.parentNode !== pointerLayer?.host) {
385
+ if (
386
+ pointerAnchor !== null &&
387
+ pointerAnchor.parentNode !== pointerLayer?.host
388
+ ) {
365
389
  // O painel apertado saiu da camada, como quando é fechado por código e vira o FAB.
366
390
  pointerAnchor = null;
367
391
  pointerLayer = null;
package/src/ui/react.tsx CHANGED
@@ -173,6 +173,7 @@ export {
173
173
  DialogBody,
174
174
  DialogFooter,
175
175
  DialogTitle,
176
+ DialogDescription,
176
177
  } from "./components/primitives/dialog.tsx";
177
178
  export type {
178
179
  DialogProps,
@@ -326,6 +327,7 @@ export {
326
327
  DrawerNavigation,
327
328
  DrawerBody,
328
329
  DrawerTitle,
330
+ DrawerDescription,
329
331
  DrawerTrigger,
330
332
  } from "./components/primitives/drawer.tsx";
331
333
  export {
@@ -509,6 +511,7 @@ export {
509
511
  PageNavigation,
510
512
  PageBack,
511
513
  PageTitle,
514
+ PageDescription,
512
515
  PageActions,
513
516
  PageActionsTarget,
514
517
  PageBody,