@softize/opus 23.0.0 → 24.0.1

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 (97) hide show
  1. package/CHANGELOG.md +99 -0
  2. package/bin/cli.mjs +2 -0
  3. package/bin/lib/check.mjs +241 -42
  4. package/bin/lib/copy.mjs +16 -34
  5. package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +6 -0
  6. package/docs/adr/0039-filter-presentation-is-independent-from-filter-type.md +3 -3
  7. package/docs/adr/0040-tabs-use-a-single-navigation-presentation.md +5 -6
  8. package/docs/adr/0042-toggle-group-shares-the-grouped-control-grammar.md +79 -0
  9. package/docs/adr/0043-neutral-surfaces-and-interactions-use-opaque-role-tokens.md +73 -0
  10. package/docs/adr/0044-neutral-appearance-derives-from-bounded-axes-per-mode.md +133 -0
  11. package/docs/adr/0045-surface-replaces-content-and-card.md +230 -0
  12. package/docs/adr/0046-structural-body-owns-content-rhythm.md +103 -0
  13. package/docs/adr/0047-pane-is-only-a-split-layout-unit.md +71 -0
  14. package/docs/adr/0048-modal-intro-separates-content-from-chrome.md +34 -0
  15. package/docs/adr/0049-inline-navigation-groups-with-the-heading.md +95 -0
  16. package/package.json +5 -1
  17. package/registry/skills/build-opus-ui/SKILL.md +11 -11
  18. package/registry/skills/build-opus-ui/references/evaluations.md +10 -9
  19. package/registry/skills/build-opus-ui/references/ui-patterns.md +17 -16
  20. package/registry/templates/app/src/App.tsx +24 -12
  21. package/src/ui/appearance.ts +149 -0
  22. package/src/ui/components/patterns/action-form-card.tsx +46 -33
  23. package/src/ui/components/patterns/action-list-dialog.tsx +2 -4
  24. package/src/ui/components/patterns/confirm.tsx +3 -1
  25. package/src/ui/components/patterns/fill-region.ts +8 -7
  26. package/src/ui/components/patterns/form-dialog.tsx +1 -3
  27. package/src/ui/components/patterns/form.tsx +8 -7
  28. package/src/ui/components/patterns/list.tsx +15 -19
  29. package/src/ui/components/patterns/page.tsx +15 -11
  30. package/src/ui/components/patterns/pane.tsx +9 -240
  31. package/src/ui/components/patterns/presentation.tsx +14 -14
  32. package/src/ui/components/patterns/sidebar.tsx +7 -17
  33. package/src/ui/components/patterns/split.tsx +111 -59
  34. package/src/ui/components/patterns/{surface-header.tsx → structural-header.tsx} +90 -27
  35. package/src/ui/components/patterns/structural-media.tsx +26 -0
  36. package/src/ui/components/patterns/surface-anatomy.ts +71 -61
  37. package/src/ui/components/patterns/surface-assistant.tsx +2 -4
  38. package/src/ui/components/patterns/surface.tsx +620 -0
  39. package/src/ui/components/patterns/trigger.tsx +11 -9
  40. package/src/ui/components/primitives/ask.tsx +155 -83
  41. package/src/ui/components/primitives/button.tsx +11 -9
  42. package/src/ui/components/primitives/checkbox.tsx +8 -8
  43. package/src/ui/components/primitives/command.tsx +1 -1
  44. package/src/ui/components/primitives/composer.tsx +47 -45
  45. package/src/ui/components/primitives/detail.tsx +9 -2
  46. package/src/ui/components/primitives/dialog.tsx +60 -26
  47. package/src/ui/components/primitives/drawer.tsx +58 -12
  48. package/src/ui/components/primitives/icon-picker.tsx +90 -73
  49. package/src/ui/components/primitives/input-group.tsx +35 -28
  50. package/src/ui/components/primitives/input-otp.tsx +16 -16
  51. package/src/ui/components/primitives/input.tsx +20 -10
  52. package/src/ui/components/primitives/item.tsx +9 -12
  53. package/src/ui/components/primitives/menu.tsx +44 -65
  54. package/src/ui/components/primitives/metric-card.tsx +75 -59
  55. package/src/ui/components/primitives/radio-group.tsx +9 -9
  56. package/src/ui/components/primitives/select.tsx +344 -209
  57. package/src/ui/components/primitives/switch.tsx +10 -10
  58. package/src/ui/components/primitives/table.tsx +29 -24
  59. package/src/ui/components/primitives/tabs.tsx +4 -2
  60. package/src/ui/components/primitives/textarea.tsx +6 -6
  61. package/src/ui/components/primitives/toggle-group-context.ts +24 -0
  62. package/src/ui/components/primitives/toggle-group.tsx +116 -74
  63. package/src/ui/components/primitives/toggle.tsx +82 -16
  64. package/src/ui/docs/DocBrowser.tsx +77 -69
  65. package/src/ui/docs/content/action-form-card.md +9 -9
  66. package/src/ui/docs/content/action-list.md +76 -70
  67. package/src/ui/docs/content/action-view.md +33 -18
  68. package/src/ui/docs/content/button.md +1 -1
  69. package/src/ui/docs/content/communication.md +1 -1
  70. package/src/ui/docs/content/compositions.md +479 -0
  71. package/src/ui/docs/content/customization.md +64 -11
  72. package/src/ui/docs/content/detail.md +8 -6
  73. package/src/ui/docs/content/dialog.md +105 -60
  74. package/src/ui/docs/content/drawer.md +68 -22
  75. package/src/ui/docs/content/field.md +14 -15
  76. package/src/ui/docs/content/icon-tile.md +14 -13
  77. package/src/ui/docs/content/page.md +44 -34
  78. package/src/ui/docs/content/pane.md +42 -290
  79. package/src/ui/docs/content/presentation.md +3 -3
  80. package/src/ui/docs/content/separator.md +6 -6
  81. package/src/ui/docs/content/sidebar.md +68 -266
  82. package/src/ui/docs/content/skeleton.md +20 -18
  83. package/src/ui/docs/content/split.md +14 -14
  84. package/src/ui/docs/content/surface.md +513 -0
  85. package/src/ui/docs/content/tabs.md +20 -20
  86. package/src/ui/docs/content/toggle.md +117 -65
  87. package/src/ui/docs/content/tokens.md +124 -40
  88. package/src/ui/docs/registry.tsx +10 -6
  89. package/src/ui/index.ts +30 -27
  90. package/src/ui/meta.ts +15 -27
  91. package/src/ui/react.tsx +40 -43
  92. package/src/ui/theme.css +311 -64
  93. package/src/ui/components/patterns/content-header.tsx +0 -528
  94. package/src/ui/components/patterns/surface-media.tsx +0 -69
  95. package/src/ui/components/primitives/card.tsx +0 -191
  96. package/src/ui/docs/content/card.md +0 -292
  97. package/src/ui/docs/content/content.md +0 -275
@@ -17,12 +17,12 @@ variáveis.
17
17
 
18
18
  ```css
19
19
  /* index.css do app — depois do import do tema. */
20
- @import '@softize/opus/ui/theme.css';
20
+ @import "@softize/opus/ui/theme.css";
21
21
 
22
22
  :root {
23
23
  --primary: hsl(262 83% 58%); /* A cor da marca do projeto. */
24
24
  --ring: hsl(262 83% 58%);
25
- --radius: 0.5rem; /* Uma base menor deixa toda a escala mais contida. */
25
+ --radius: 0.5rem; /* Uma base menor deixa toda a escala mais contida. */
26
26
  }
27
27
  ```
28
28
 
@@ -36,6 +36,55 @@ Quando o produto precisa de outra densidade, declare essa escolha no CSS do app.
36
36
  Opus mede em `rem` acompanha a proporção. O valor continua sendo uma escolha do app, não uma regra
37
37
  da biblioteca.
38
38
 
39
+ ### Ajuste de aparência dos neutros
40
+
41
+ Quando a aplicação quer neutros levemente tingidos pela marca, camadas mais separadas ou um escuro
42
+ mais profundo — como padrão do produto ou como preferência de cada pessoa —, ela não precisa
43
+ redeclarar `--background`, `--card` e os demais tokens neutros. O tema calcula essa escala a partir
44
+ de quatro eixos por modo, e basta alterar os eixos. Cada um aceita um inteiro de 0 a 100; a tabela
45
+ com o efeito e o preset de cada eixo está em [Tokens](/ui/tokens).
46
+
47
+ Para definir o padrão do produto, declare os eixos no CSS do app. Os eixos do modo claro e os do
48
+ modo escuro ficam juntos em `:root`; o tema usa o conjunto do modo ativo.
49
+
50
+ ```css
51
+ :root {
52
+ --neutral-hue: 262; /* O matiz da marca nos neutros. */
53
+ --appearance-light-color-intensity: 40;
54
+ --appearance-dark-color-intensity: 60;
55
+ --appearance-dark-surface-brightness: 30; /* Um escuro mais profundo que o preset. */
56
+ }
57
+ ```
58
+
59
+ Para aplicar a preferência de uma pessoa, use o helper de `@softize/opus/ui/appearance`. Ele
60
+ arredonda e limita os valores, grava os eixos informados e remove os demais. Guardar a preferência
61
+ e desenhar a tela de ajuste são responsabilidades da aplicação.
62
+
63
+ ```ts
64
+ import { applyAppearanceCustomProperties } from "@softize/opus/ui/appearance";
65
+
66
+ const style = document.documentElement.style;
67
+
68
+ // Aplica somente os eixos informados; os demais continuam no padrão.
69
+ applyAppearanceCustomProperties(style, "light", { layerContrast: 60 });
70
+ applyAppearanceCustomProperties(style, "dark", {
71
+ contrast: 40,
72
+ surfaceBrightness: 20,
73
+ });
74
+
75
+ // Voltar ao padrão é aplicar sem overrides: as propriedades são removidas.
76
+ applyAppearanceCustomProperties(style, "dark");
77
+ ```
78
+
79
+ O Opus não oferece um componente de reset. Sem o override, vale o que o CSS do app declarou ou,
80
+ na falta disso, o preset do tema. Em renderização no servidor, `appearanceCustomProperties` devolve
81
+ o mesmo mapa sem tocar no DOM, para a aplicação escrever os eixos no HTML inicial.
82
+
83
+ Nenhum valor de 0 a 100 deixa a interface menos legível que o tema padrão: a separação entre os
84
+ papéis e o contraste do texto sobre as superfícies em repouso nunca ficam abaixo do preset. Um
85
+ token neutro redeclarado diretamente pelo app, como `--background`, deixa de acompanhar os eixos e
86
+ fica fora dessa proteção.
87
+
39
88
  ## 2 · className em tudo
40
89
 
41
90
  Todo componente aceita `className` e o combina com as próprias classes por tailwind-merge: quando
@@ -44,28 +93,30 @@ local, como largura, margem e grid, e não para repintar o visual dos componente
44
93
 
45
94
  ```tsx
46
95
  <Button className="w-full">Continuar</Button>
47
- <Card className="max-w-sm" />
96
+ <Surface framed elevation="raised" className="max-w-sm" />
48
97
  <DialogContent className="max-w-2xl" />
49
98
  ```
50
99
 
51
100
  ## 3 · Recomposição estrutural
52
101
 
53
- Os componentes são divididos em partes nomeadas, como `CardHeader` e `CardBody`, e a tela usa só as
102
+ Os componentes são divididos em partes nomeadas, como `SurfaceHeader` e `SurfaceBody`, e a tela usa só as
54
103
  que precisa. `asChild` faz o filho assumir o estilo e o comportamento do componente, e as funções
55
104
  `*Variants` aplicam o visual de um componente a um elemento qualquer.
56
105
 
57
106
  ```tsx preview
58
107
  <Button asChild variant="outline">
59
- <a href="#" onClick={(e) => e.preventDefault()}>Link com aparência de botão</a>
108
+ <a href="#" onClick={(e) => e.preventDefault()}>
109
+ Link com aparência de botão
110
+ </a>
60
111
  </Button>
61
112
  ```
62
113
 
63
114
  ```tsx
64
115
  // Partes: só o que a tela pede.
65
- <Card>
66
- <CardHeader><CardTitle>Workspace</CardTitle></CardHeader>
67
- <CardBody>…</CardBody>
68
- </Card>
116
+ <Surface framed elevation="raised">
117
+ <SurfaceHeader><SurfaceTitle>Workspace</SurfaceTitle></SurfaceHeader>
118
+ <SurfaceBody>…</SurfaceBody>
119
+ </Surface>
69
120
 
70
121
  // asChild: o link assume o estilo e o comportamento do botão.
71
122
  <Button asChild><a href="/docs">Abrir documentação</a></Button>
@@ -128,8 +179,10 @@ componentes.
128
179
  ## O que fica fora das alavancas
129
180
 
130
181
  Alguns detalhes fazem parte da identidade visual do Opus e são iguais em todo projeto: a faixa do
131
- rodapé do dialog, a elevação no tema escuro, o escurecimento do botão ao ser pressionado
132
- (`active:brightness-90`) e a seta do tooltip. Eles não são configuráveis pela aplicação.
182
+ rodapé do dialog, a ordem entre canvas, controle, superfície elevada e estados neutros, o
183
+ escurecimento de botões sólidos ao serem pressionados e a seta do tooltip. Eles não são
184
+ configuráveis pela aplicação. O ajuste de aparência muda a distância entre os papéis neutros, mas
185
+ não a ordem entre eles.
133
186
 
134
187
  Se uma necessidade real não cabe nas alavancas (tokens, `className`, partes e `asChild`, props e
135
188
  contrato), não crie uma variação local. Proponha a evolução do componente no Opus, com a
@@ -44,12 +44,13 @@ React substitui a apresentação. `0` e `false` seguem como valores. A regra é
44
44
  </DetailGroup>
45
45
  ```
46
46
 
47
- ## Moldura e divisórias internas
47
+ ## Elevação, moldura e divisórias internas
48
48
 
49
- `variant="framed"` adiciona somente a borda externa, sem preencher o fundo. `dividers` desenha as
50
- divisórias internas; na orientação horizontal, também forma a grade tabular com fundo sutil e
51
- padding próprio na coluna de rótulos. As duas opções são independentes e podem ser combinadas.
52
- `orientation` define se a chave fica sobre o valor ou ao lado dele.
49
+ Por padrão, o grupo usa o mesmo fundo de uma superfície elevada. Use `elevation="none"` quando ele
50
+ precisar se integrar ao fundo do hospedeiro. `variant="framed"` adiciona somente a borda externa.
51
+ `dividers` desenha as divisórias internas; na orientação horizontal, também forma a grade tabular
52
+ com fundo sutil e padding próprio na coluna de rótulos. As opções são independentes e podem ser
53
+ combinadas. `orientation` define se a chave fica sobre o valor ou ao lado dele.
53
54
 
54
55
  Na orientação horizontal, todos os valores começam depois da mesma coluna de rótulo, com largura
55
56
  padrão de `7rem` em grupos simples e `8.5rem` quando `dividers` está ativo. Quando a superfície
@@ -73,7 +74,8 @@ exigir mais espaço para os rótulos, ajuste a variável no grupo, por exemplo c
73
74
 
74
75
  | Propriedade | Tipo | Padrão | Descrição |
75
76
  | ------------- | ---------------------------- | ------------ | --------------------------------------------------------------------------------------------------------- |
76
- | `variant` | `'plain' \| 'framed'` | `'plain'` | `framed` aplica somente a moldura canônica ao conjunto, com fundo transparente. |
77
+ | `variant` | `'plain' \| 'framed'` | `'plain'` | `framed` aplica somente a moldura canônica ao conjunto. |
78
+ | `elevation` | `'none' \| 'raised'` | `'raised'` | `raised` usa o fundo de superfície elevada; `none` herda o fundo do hospedeiro. |
77
79
  | `dividers` | `boolean` | `false` | Linhas finas entre os campos; na orientação horizontal, aplica fundo sutil e padding próprio aos rótulos. |
78
80
  | `columns` | `1 \| 2 \| 3 \| 4 \| 'auto'` | `1` | Número fixo de colunas ou distribuição automática por largura mínima. |
79
81
  | `orientation` | `'vertical' \| 'horizontal'` | `'vertical'` | Chave sobre o valor ou ao lado dele em cada campo. |
@@ -29,9 +29,11 @@ distinta da página.
29
29
  <DialogHeader>
30
30
  <DialogTitle>Criar workspace</DialogTitle>
31
31
  </DialogHeader>
32
- <DialogBody className="space-y-2">
33
- <Label htmlFor="workspace-name">Nome</Label>
34
- <Input id="workspace-name" placeholder="Ex.: Empresa X" />
32
+ <DialogBody>
33
+ <div className="space-y-2">
34
+ <Label htmlFor="workspace-name">Nome</Label>
35
+ <Input id="workspace-name" placeholder="Ex.: Empresa X" />
36
+ </div>
35
37
  </DialogBody>
36
38
  <DialogFooter>
37
39
  <ButtonGroup mode="spaced">
@@ -46,6 +48,9 @@ distinta da página.
46
48
  ```
47
49
 
48
50
  O corpo cresce até o limite da janela e passa a rolar; cabeçalho e rodapé permanecem visíveis.
51
+ Seus filhos diretos recebem o ritmo estrutural de `1.5rem`. Um grupo compacto, uma grade ou uma
52
+ linha declara a própria relação em um wrapper filho; `flush` fica reservado ao conteúdo que
53
+ controla todo o inset.
49
54
  O backdrop escurece a página sem aplicar desfoque.
50
55
  O fechamento do cabeçalho usa uma ação circular neutra e sutil. `closeSize` escolhe entre `xs` e
51
56
  `sm`; o padrão é `sm`.
@@ -104,8 +109,10 @@ compartilham a anatomia, e um fluxo que troca de superfície não muda de aparê
104
109
 
105
110
  O cabeçalho reconhece quatro slots — `DialogNavigation`, `DialogMedia`, `DialogTitle` e
106
111
  `DialogActions` — e posiciona cada um: a navegação abre a linha, o título ocupa a folga, as ações
107
- vão para o extremo oposto e o botão de fechar entra depois delas. Um nó que não seja um desses
108
- slots interrompe a renderização com um erro; contexto e conteúdo pertencem ao `DialogBody`.
112
+ vão para o extremo oposto e o botão de fechar entra depois delas. A navegação fica a `0.5rem` do
113
+ título, ou da mídia que o antecede, para se ler junto dele; as ações mantêm a distância maior da
114
+ família. Um nó que não seja um desses slots interrompe a renderização com um erro; contexto e
115
+ conteúdo pertencem ao `DialogBody`.
109
116
 
110
117
  Use `DialogActions` para uma ação da tela que precisa ficar à vista sem competir com o rodapé, como
111
118
  abrir um assistente ou alternar um modo de visualização. Use `DialogNavigation` para o retorno de um
@@ -131,7 +138,9 @@ fluxo modal encadeado, em que a superfície permanece aberta e troca de etapa.
131
138
  </DialogActions>
132
139
  </DialogHeader>
133
140
  <DialogBody>
134
- <p className="text-sm text-muted-foreground">Detalhe do evento selecionado.</p>
141
+ <p className="text-sm text-muted-foreground">
142
+ Detalhe do evento selecionado.
143
+ </p>
135
144
  </DialogBody>
136
145
  </DialogContent>
137
146
  </Dialog>
@@ -147,9 +156,9 @@ decidir aparece no começo do corpo, como texto ou `Alert`.
147
156
  ## Identificar a tarefa com um ícone
148
157
 
149
158
  Use `DialogMedia` quando um ícone ajuda a reconhecer a tarefa antes da leitura do título. No modo
150
- padrão, ele abre a linha do cabeçalho com a mesma moldura de [`IconTile`](/ui/icon-tile) das demais
151
- superfícies. No `mode="alert"`, a moldura cresce e fica centralizada acima do título, porque ali o
152
- ícone é o principal sinal de reconhecimento da resposta.
159
+ padrão, ele apenas reserva e alinha o espaço antes do título. Componha [`IconTile`](/ui/icon-tile)
160
+ explicitamente quando o ícone precisar de moldura; passe o glifo diretamente quando não precisar.
161
+ O mesmo contrato vale no `mode="alert"`: a mídia não ganha tamanho, fundo ou borda automaticamente.
153
162
 
154
163
  ```tsx preview
155
164
  render(
@@ -161,13 +170,17 @@ render(
161
170
  <DialogContent>
162
171
  <DialogHeader>
163
172
  <DialogMedia>
164
- <Share2 />
173
+ <IconTile size="sm">
174
+ <Share2 />
175
+ </IconTile>
165
176
  </DialogMedia>
166
177
  <DialogTitle>Compartilhar contrato</DialogTitle>
167
178
  </DialogHeader>
168
- <DialogBody className="space-y-2">
169
- <Label htmlFor="share-email">E-mail</Label>
170
- <Input id="share-email" placeholder="nome@empresa.com.br" />
179
+ <DialogBody>
180
+ <div className="space-y-2">
181
+ <Label htmlFor="share-email">E-mail</Label>
182
+ <Input id="share-email" placeholder="nome@empresa.com.br" />
183
+ </div>
171
184
  </DialogBody>
172
185
  <DialogFooter>
173
186
  <ButtonGroup mode="spaced">
@@ -185,8 +198,10 @@ render(
185
198
  </DialogTrigger>
186
199
  <DialogContent>
187
200
  <DialogHeader>
188
- <DialogMedia context="danger">
189
- <ShieldOff />
201
+ <DialogMedia>
202
+ <IconTile size="lg" context="danger">
203
+ <ShieldOff />
204
+ </IconTile>
190
205
  </DialogMedia>
191
206
  <DialogTitle>Revogar acesso de Marina?</DialogTitle>
192
207
  </DialogHeader>
@@ -211,7 +226,8 @@ render(
211
226
  );
212
227
  ```
213
228
 
214
- Na API imperativa, a opção `media` recebe o ícone e o apresenta na mesma moldura do alerta.
229
+ Na API imperativa, a opção `media` preserva o nó recebido. Passe um `IconTile` quando quiser
230
+ moldura.
215
231
 
216
232
  ## Pedir uma resposta pela API imperativa
217
233
 
@@ -245,7 +261,11 @@ function Demo() {
245
261
  const accepted = await dialog.confirm({
246
262
  title: "Excluir rascunho?",
247
263
  body: "O rascunho e os comentários dele serão removidos.",
248
- media: <Trash2 />,
264
+ media: (
265
+ <IconTile context="danger">
266
+ <Trash2 />
267
+ </IconTile>
268
+ ),
249
269
  action: "Excluir",
250
270
  context: "danger",
251
271
  });
@@ -443,42 +463,43 @@ botão. O texto informa o efeito real, como `Excluir`, `Revogar acesso` ou `Arqu
443
463
 
444
464
  ## Propriedades de DialogContent
445
465
 
446
- | Propriedade | Tipo | Padrão | Descrição |
447
- | ----------------- | ----------- | ------ | --------------------------------------------------------------------------------------------------------- |
448
- | `showCloseButton` | `boolean` | `true` | No modo padrão, inclui a ação de fechamento no header; o modo de alerta exige uma resposta identificável. |
449
- | `closeDisabled` | `boolean` | `false` | Mantém o botão de fechar visível e desabilitado e impede que Esc e o clique externo fechem a superfície. Handlers como `onEscapeKeyDown` e `onPointerDownOutside` continuam sendo chamados antes do bloqueio. |
450
- | `closeSize` | `"xs" \| "sm"` | `"sm"` | Define o tamanho da ação circular de fechar. |
451
- | `layout` | `'continuous' \| 'sectioned'` | `'sectioned'` | Anatomia da superfície inteira; os slots leem esse valor e não o recebem. Ignorado em `mode="alert"`. |
452
- | `className` | `string` | | Ajusta a superfície. A largura máxima é `32rem` no modo padrão e `20rem` no alerta; amplie o alerta com `max-w-lg` quando o rodapé tiver três ações. |
453
- | `children` | `ReactNode` | | Cabeçalho, corpo, rodapé ou conteúdo próprio. |
466
+ | Propriedade | Tipo | Padrão | Descrição |
467
+ | ----------------- | ----------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
468
+ | `showCloseButton` | `boolean` | `true` | No modo padrão, inclui a ação de fechamento no cabeçalho; o modo de alerta exige uma resposta identificável. |
469
+ | `closeDisabled` | `boolean` | `false` | Mantém o botão de fechar visível e desabilitado e impede que Esc e o clique externo fechem a superfície. Handlers como `onEscapeKeyDown` e `onPointerDownOutside` continuam sendo chamados antes do bloqueio. |
470
+ | `closeSize` | `"xs" \| "sm"` | `"sm"` | Define o tamanho da ação circular de fechar. |
471
+ | `layout` | `'continuous' \| 'sectioned'` | `'sectioned'` | Anatomia da superfície inteira; os slots leem esse valor e não o recebem. Ignorado em `mode="alert"`. |
472
+ | `className` | `string` | | Ajusta a superfície. A largura máxima é `32rem` no modo padrão e `20rem` no alerta; amplie o alerta com `max-w-lg` quando o rodapé tiver três ações. |
473
+ | `children` | `ReactNode` | | Cabeçalho, corpo, rodapé ou conteúdo próprio. |
454
474
 
455
475
  ## Propriedades de DialogHeader
456
476
 
457
- | Propriedade | Tipo | Padrão | Descrição |
458
- | ----------- | ----------- | ------ | ---------------------------------------------------------- |
459
- | `className` | `string` | | Ajusta a faixa superior sobre o recuo do layout. |
477
+ | Propriedade | Tipo | Padrão | Descrição |
478
+ | ----------- | ----------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
479
+ | `className` | `string` | | Ajusta a faixa superior sobre o recuo do layout. |
460
480
  | `children` | `ReactNode` | | Somente `DialogNavigation`, `DialogMedia`, `DialogTitle` e `DialogActions`, no máximo um de cada; o fechamento padrão entra depois deles. |
461
481
 
462
482
  ## Propriedades de DialogNavigation
463
483
 
464
- | Propriedade | Tipo | Padrão | Descrição |
465
- | ----------- | ----------- | ------ | -------------------------------------------------------------- |
466
- | `className` | `string` | | Ajusta a região introdutória da linha. |
467
- | `children` | `ReactNode` | | Retorno de um fluxo modal encadeado, antes do título. |
484
+ | Propriedade | Tipo | Padrão | Descrição |
485
+ | ----------- | ----------- | ------ | ----------------------------------------------------- |
486
+ | `className` | `string` | | Ajusta a região introdutória da linha. |
487
+ | `children` | `ReactNode` | | Retorno de um fluxo modal encadeado, antes do título. |
468
488
 
469
489
  ## Propriedades de DialogActions
470
490
 
471
- | Propriedade | Tipo | Padrão | Descrição |
472
- | ----------- | ----------- | ------ | ------------------------------------------------------------------ |
473
- | `className` | `string` | | Ajusta o agrupamento das ações. |
474
- | `children` | `ReactNode` | | Ações da tela no cabeçalho, antes do botão de fechar. |
491
+ | Propriedade | Tipo | Padrão | Descrição |
492
+ | ----------- | ----------- | ------ | ----------------------------------------------------- |
493
+ | `className` | `string` | | Ajusta o agrupamento das ações. |
494
+ | `children` | `ReactNode` | | Ações da tela no cabeçalho, antes do botão de fechar. |
475
495
 
476
496
  ## Propriedades de DialogBody
477
497
 
478
- | Propriedade | Tipo | Padrão | Descrição |
479
- | ----------- | ----------- | ------ | ---------------------------------------------------------------------- |
480
- | `className` | `string` | | Ajusta o corpo rolável do modo padrão ou o conteúdo próprio do alerta. |
481
- | `children` | `ReactNode` | | Conteúdo principal da tarefa. |
498
+ | Propriedade | Tipo | Padrão | Descrição |
499
+ | ----------- | ----------- | ------- | ---------------------------------------------------------------------- |
500
+ | `flush` | `boolean` | `false` | Remove o inset para conteúdo que controla o próprio recuo. |
501
+ | `className` | `string` | | Ajusta o corpo rolável do modo padrão ou o conteúdo próprio do alerta. |
502
+ | `children` | `ReactNode` | | Conteúdo principal da tarefa. |
482
503
 
483
504
  ## Propriedades de DialogFooter
484
505
 
@@ -489,25 +510,18 @@ botão. O texto informa o efeito real, como `Excluir`, `Revogar acesso` ou `Arqu
489
510
 
490
511
  ## Propriedades de DialogTitle
491
512
 
492
- | Propriedade | Tipo | Padrão | Descrição |
493
- | ----------- | ----------- | ------ | ------------------------------------ |
494
- | `level` | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | `h2` da primitiva | Hierarquia do documento. O tamanho vem do `layout`, nunca do nível. |
495
- | `className` | `string` | | Ajusta o título. No modo padrão, ele trunca quando não cabe na linha; no alerta, fica centralizado e pode ocupar mais de uma linha. |
496
- | `children` | `ReactNode` | | Nome da tarefa ou do conteúdo modal. |
513
+ | Propriedade | Tipo | Padrão | Descrição |
514
+ | ----------- | ---------------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
515
+ | `level` | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | `h2` da primitiva | Hierarquia do documento. O tamanho vem do `layout`, nunca do nível. |
516
+ | `className` | `string` | | Ajusta o título. No modo padrão, ele trunca quando não cabe na linha; no alerta, fica centralizado e pode ocupar mais de uma linha. |
517
+ | `children` | `ReactNode` | | Nome da tarefa ou do conteúdo modal. |
497
518
 
498
519
  ## Propriedades de DialogMedia
499
520
 
500
- A moldura vem do `IconTile`, como nas demais famílias. No `mode="alert"` ela cresce e centraliza:
501
- ali a mídia não acompanha uma linha de título, ela é o que faz a pessoa reconhecer a resposta.
502
-
503
- | Propriedade | Tipo | Padrão | Descrição |
504
- | ----------- | ----------------------------------------------------------- | --------- | -------------------------------------------------- |
505
- | `size` | `'xs' \| 'sm' \| 'default' \| 'lg'` | `sm` | Escala da moldura; no alerta vale a moldura maior. |
506
- | `context` | `UiContext` | `neutral` | Cor semântica da moldura. |
507
- | `variant` | `'subtle' \| 'outline'` | `subtle` | Tratamento visual da moldura. |
508
- | `className` | `string` | | Ajustes pontuais da moldura. |
509
- | `children` | `ReactNode` | | Ícone que reforça o contexto da resposta. |
510
- | `asChild` | `boolean` | `false` | Cede o elemento para uma marca com visual próprio — um orbe, um logotipo — que ocupa o slot sem ganhar a moldura por cima. |
521
+ | Propriedade | Tipo | Padrão | Descrição |
522
+ | ----------- | ----------- | ------ | ----------------------------------------------------------------------- |
523
+ | `className` | `string` | | Ajusta o alinhamento do slot. |
524
+ | `children` | `ReactNode` | | `IconTile`, marca, imagem ou glifo. O slot não acrescenta apresentação. |
511
525
 
512
526
  ## Propriedades de DialogClose
513
527
 
@@ -540,7 +554,7 @@ imperativa.
540
554
  | -------- | ----------- | ------ | ------------------------------------------------ |
541
555
  | `title` | `ReactNode` | | Nomeia o aviso que exige reconhecimento. |
542
556
  | `body` | `ReactNode` | | Apresenta contexto relevante no início do corpo. |
543
- | `media` | `ReactNode` | | Exibe um ícone na moldura de `DialogMedia`. |
557
+ | `media` | `ReactNode` | | Exibe o nó recebido em `DialogMedia`. |
544
558
  | `action` | `string` | `'OK'` | Define o rótulo de reconhecimento. |
545
559
 
546
560
  ## Opções de dialog.confirm
@@ -549,7 +563,7 @@ imperativa.
549
563
  | --------- | ----------------------- | ------------- | ------------------------------------------------------------------- |
550
564
  | `title` | `ReactNode` | | Nomeia a decisão. |
551
565
  | `body` | `ReactNode` | | Apresenta a consequência ou outro contexto necessário para decidir. |
552
- | `media` | `ReactNode` | | Exibe um ícone na moldura de `DialogMedia`. |
566
+ | `media` | `ReactNode` | | Exibe o nó recebido em `DialogMedia`. |
553
567
  | `action` | `string` | `'Confirmar'` | Define o rótulo do resultado afirmativo. |
554
568
  | `cancel` | `string` | `'Cancelar'` | Define o rótulo da resposta negativa. |
555
569
  | `context` | `'primary' \| 'danger'` | `'primary'` | Define o contexto da ação afirmativa. |
@@ -560,7 +574,7 @@ imperativa.
560
574
  | -------------- | ----------------------- | ------------- | ------------------------------------------------------------------ |
561
575
  | `title` | `ReactNode` | | Nomeia a informação solicitada. |
562
576
  | `body` | `ReactNode` | | Explica como a informação será usada quando isso mudar a resposta. |
563
- | `media` | `ReactNode` | | Exibe um ícone na moldura de `DialogMedia`. |
577
+ | `media` | `ReactNode` | | Exibe o nó recebido em `DialogMedia`. |
564
578
  | `action` | `string` | `'Confirmar'` | Define o rótulo do resultado afirmativo. |
565
579
  | `cancel` | `string` | `'Cancelar'` | Define o rótulo da resposta negativa. |
566
580
  | `context` | `'primary' \| 'danger'` | `'primary'` | Define o contexto da ação afirmativa. |
@@ -573,7 +587,7 @@ imperativa.
573
587
  | --------- | ------------------------- | ------ | ------------------------------------------------ |
574
588
  | `title` | `ReactNode` | | Nomeia a escolha. |
575
589
  | `body` | `ReactNode` | | Apresenta o contexto necessário antes das ações. |
576
- | `media` | `ReactNode` | | Exibe um ícone na moldura de `DialogMedia`. |
590
+ | `media` | `ReactNode` | | Exibe o nó recebido em `DialogMedia`. |
577
591
  | `actions` | `readonly DialogChoice[]` | | Declara os resultados disponíveis. |
578
592
 
579
593
  ## Propriedades de DialogChoice
@@ -586,3 +600,34 @@ imperativa.
586
600
  | `context` | `ButtonContext` | Última ação: `primary`; demais: `neutral` | Define o significado semântico. |
587
601
  | `variant` | `ButtonVariant` | Última ação: `solid`; demais: `outline` | Define o tratamento visual. |
588
602
  | `disabled` | `boolean` | `false` | Impede a escolha desta ação. |
603
+
604
+ ## Intro separada do chrome
605
+
606
+ Quando o cabeçalho contém apenas navegação, ações globais e o fechamento, coloque o título do
607
+ conteúdo no primeiro bloco de `DialogBody`. `DialogIntro` exige um `DialogTitle` e aceita um
608
+ `DialogIntroActions`; o body continua responsável pelo espaço antes, entre e depois dos blocos.
609
+
610
+ ```tsx
611
+ <Dialog>
612
+ <DialogContent>
613
+ <DialogHeader>
614
+ <DialogActions>
615
+ <Button variant="ghost">Ajuda</Button>
616
+ </DialogActions>
617
+ </DialogHeader>
618
+ <DialogBody>
619
+ <DialogIntro>
620
+ <DialogTitle>Editar evento</DialogTitle>
621
+ <DialogIntroActions>
622
+ <Button variant="outline">Revisar</Button>
623
+ </DialogIntroActions>
624
+ </DialogIntro>
625
+ <form>…</form>
626
+ </DialogBody>
627
+ <DialogFooter>…</DialogFooter>
628
+ </DialogContent>
629
+ </Dialog>
630
+ ```
631
+
632
+ No formato compacto, mantenha `DialogTitle` e `DialogActions` no próprio `DialogHeader`. Para
633
+ comparar as duas formas em cada `layout`, abra [Composições](/ui/compositions).
@@ -18,9 +18,11 @@ cantos, em qualquer valor de `side`.
18
18
  <DrawerHeader>
19
19
  <DrawerTitle>ABC-1D23</DrawerTitle>
20
20
  </DrawerHeader>
21
- <DrawerBody className="space-y-1 text-sm text-muted-foreground">
22
- <p>Corolla XEi 2022 · 38.400 km</p>
23
- <p>Última revisão em 12 de maio.</p>
21
+ <DrawerBody className="text-sm text-muted-foreground">
22
+ <div className="space-y-1">
23
+ <p>Corolla XEi 2022 · 38.400 km</p>
24
+ <p>Última revisão em 12 de maio.</p>
25
+ </div>
24
26
  </DrawerBody>
25
27
  </DrawerContent>
26
28
  </Drawer>
@@ -42,9 +44,11 @@ neutra e sutil; `closeSize` escolhe entre `xs` e `sm`, com `sm` como padrão.
42
44
  <DrawerHeader>
43
45
  <DrawerTitle>Editar veículo</DrawerTitle>
44
46
  </DrawerHeader>
45
- <DrawerBody className="space-y-2">
46
- <Label htmlFor="vehicle-owner">Responsável</Label>
47
- <Input id="vehicle-owner" defaultValue="Marina Alves" />
47
+ <DrawerBody>
48
+ <div className="space-y-2">
49
+ <Label htmlFor="vehicle-owner">Responsável</Label>
50
+ <Input id="vehicle-owner" defaultValue="Marina Alves" />
51
+ </div>
48
52
  </DrawerBody>
49
53
  <DrawerFooter>
50
54
  <ButtonGroup mode="spaced">
@@ -98,13 +102,14 @@ rodapé produz uma linha só. A tela não escreve `border-b` nem `border-t`.
98
102
 
99
103
  O cabeçalho reconhece quatro slots — `DrawerNavigation`, `DrawerMedia`, `DrawerTitle` e
100
104
  `DrawerActions` — e posiciona cada um: a navegação abre a linha, a mídia vem antes do título, o
101
- título ocupa a folga, as ações vão para o extremo oposto e o botão de fechar entra depois delas. Um
102
- nó que não seja um desses slots interrompe a renderização com um erro; contexto e conteúdo
103
- pertencem ao `DrawerBody`.
105
+ título ocupa a folga, as ações vão para o extremo oposto e o botão de fechar entra depois delas. A
106
+ navegação fica a `0.5rem` do título, ou da mídia que o antecede, para se ler junto dele; as ações
107
+ mantêm a distância maior da família. Um nó que não seja um desses slots interrompe a renderização
108
+ com um erro; contexto e conteúdo pertencem ao `DrawerBody`.
104
109
 
105
- `DrawerMedia` é a moldura de ícone do Opus — o mesmo [`IconTile`](/ui/icon-tile) —, útil para
106
- identificar o tipo do recurso aberto. `DrawerNavigation` carrega o retorno de um fluxo encadeado,
107
- em que o painel permanece aberto e troca de etapa.
110
+ `DrawerMedia` reserva o espaço que identifica o tipo do recurso aberto, sem acrescentar moldura.
111
+ Componha [`IconTile`](/ui/icon-tile) quando ela ajudar na hierarquia. `DrawerNavigation` carrega o
112
+ retorno de um fluxo encadeado, em que o painel permanece aberto e troca de etapa.
108
113
 
109
114
  ```tsx preview
110
115
  <Drawer>
@@ -118,8 +123,10 @@ em que o painel permanece aberto e troca de etapa.
118
123
  <ArrowLeft />
119
124
  </Button>
120
125
  </DrawerNavigation>
121
- <DrawerMedia context="primary">
122
- <CarFront />
126
+ <DrawerMedia>
127
+ <IconTile size="sm" context="primary">
128
+ <CarFront />
129
+ </IconTile>
123
130
  </DrawerMedia>
124
131
  <DrawerTitle>ABC-1D23</DrawerTitle>
125
132
  <DrawerActions>
@@ -210,13 +217,10 @@ de continuar, use [`Dialog mode="alert"`](/ui/dialog), que não fecha pelo cliqu
210
217
 
211
218
  ## Propriedades de DrawerMedia
212
219
 
213
- | Propriedade | Tipo | Padrão | Descrição |
214
- | ----------- | ------------------------------------------------------------------------ | ----------- | -------------------------------------------------------------------------------------------------------------------------- |
215
- | `size` | `'xs' \| 'sm' \| 'default' \| 'lg'` | `'sm'` | Tamanho da moldura e do glifo na escala compartilhada. |
216
- | `context` | `'neutral' \| 'primary' \| 'info' \| 'success' \| 'warning' \| 'danger'` | `'neutral'` | Declara o significado da cor. |
217
- | `variant` | `'subtle' \| 'outline'` | `'subtle'` | Usa preenchimento tonal ou acrescenta borda. |
218
- | `className` | `string` | | Ajusta a moldura sem substituir a escala. |
219
- | `asChild` | `boolean` | `false` | Cede o elemento para uma marca com visual próprio — um orbe, um logotipo — que ocupa o slot sem ganhar a moldura por cima. |
220
+ | Propriedade | Tipo | Padrão | Descrição |
221
+ | ----------- | ----------- | ------ | ----------------------------------------------------------------------- |
222
+ | `className` | `string` | | Ajusta o alinhamento do slot. |
223
+ | `children` | `ReactNode` | | `IconTile`, marca, imagem ou glifo. O slot não acrescenta apresentação. |
220
224
 
221
225
  ## Propriedades de DrawerNavigation
222
226
 
@@ -244,7 +248,14 @@ de continuar, use [`Dialog mode="alert"`](/ui/dialog), que não fecha pelo cliqu
244
248
 
245
249
  `DrawerBody` aceita os atributos de `HTMLDivElement` e ocupa a região flexível e rolável entre
246
250
  `DrawerHeader` e `DrawerFooter`. O cabeçalho nomeia o painel; contexto relevante entra no começo
247
- do corpo como texto ou `Alert`.
251
+ do corpo como texto ou `Alert`. Seus filhos diretos recebem o ritmo estrutural; use `flush` quando
252
+ o conteúdo controlar o próprio inset e um wrapper filho para relações internas mais compactas.
253
+
254
+ | Propriedade | Tipo | Padrão | Descrição |
255
+ | ----------- | ----------- | ------- | ---------------------------------------------------------- |
256
+ | `flush` | `boolean` | `false` | Remove o inset para conteúdo que controla o próprio recuo. |
257
+ | `className` | `string` | | Ajusta ocupação, overflow, alinhamento ou tipografia. |
258
+ | `children` | `ReactNode` | | Blocos principais do painel. |
248
259
 
249
260
  ## Propriedades de DrawerFooter
250
261
 
@@ -252,3 +263,38 @@ do corpo como texto ou `Alert`.
252
263
  | ----------- | ----------- | ------ | ------------------------------------------------------------- |
253
264
  | `className` | `string` | | Ajusta a faixa do rodapé, inclusive a distribuição das ações. |
254
265
  | `children` | `ReactNode` | | Ações persistentes, normalmente dentro de um `ButtonGroup`. |
266
+
267
+ ## Intro separada do chrome
268
+
269
+ Quando o cabeçalho contém apenas o recolhimento, ações globais e o fechamento, use `DrawerIntro`
270
+ como primeiro bloco de `DrawerBody`. Ele exige um `DrawerTitle` e aceita `DrawerIntroActions`.
271
+ Assim o título acompanha o conteúdo rolável enquanto o chrome permanece estável.
272
+
273
+ ```tsx
274
+ <Drawer>
275
+ <DrawerContent showCloseButton={false}>
276
+ <DrawerHeader>
277
+ <DrawerNavigation>
278
+ <DrawerClose asChild>
279
+ <Button>Recolher</Button>
280
+ </DrawerClose>
281
+ </DrawerNavigation>
282
+ <DrawerActions>
283
+ <Button variant="ghost">Conversa</Button>
284
+ </DrawerActions>
285
+ </DrawerHeader>
286
+ <DrawerBody>
287
+ <DrawerIntro>
288
+ <DrawerTitle>Editar evento</DrawerTitle>
289
+ <DrawerIntroActions>
290
+ <Button variant="outline">Revisar</Button>
291
+ </DrawerIntroActions>
292
+ </DrawerIntro>
293
+ <form>…</form>
294
+ </DrawerBody>
295
+ </DrawerContent>
296
+ </Drawer>
297
+ ```
298
+
299
+ No formato compacto, mantenha `DrawerTitle` e `DrawerActions` no próprio `DrawerHeader`. Para
300
+ comparar as duas formas em cada `layout`, abra [Composições](/ui/compositions).