@softize/opus 23.0.0 → 24.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 (95) hide show
  1. package/CHANGELOG.md +81 -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/package.json +5 -1
  16. package/registry/skills/build-opus-ui/SKILL.md +11 -11
  17. package/registry/skills/build-opus-ui/references/evaluations.md +10 -9
  18. package/registry/skills/build-opus-ui/references/ui-patterns.md +17 -16
  19. package/registry/templates/app/src/App.tsx +24 -12
  20. package/src/ui/appearance.ts +149 -0
  21. package/src/ui/components/patterns/action-form-card.tsx +46 -33
  22. package/src/ui/components/patterns/action-list-dialog.tsx +2 -4
  23. package/src/ui/components/patterns/confirm.tsx +3 -1
  24. package/src/ui/components/patterns/fill-region.ts +8 -7
  25. package/src/ui/components/patterns/form-dialog.tsx +1 -3
  26. package/src/ui/components/patterns/form.tsx +8 -7
  27. package/src/ui/components/patterns/list.tsx +15 -19
  28. package/src/ui/components/patterns/page.tsx +15 -11
  29. package/src/ui/components/patterns/pane.tsx +9 -240
  30. package/src/ui/components/patterns/presentation.tsx +14 -14
  31. package/src/ui/components/patterns/sidebar.tsx +7 -17
  32. package/src/ui/components/patterns/split.tsx +111 -59
  33. package/src/ui/components/patterns/{surface-header.tsx → structural-header.tsx} +50 -19
  34. package/src/ui/components/patterns/structural-media.tsx +26 -0
  35. package/src/ui/components/patterns/surface-anatomy.ts +71 -61
  36. package/src/ui/components/patterns/surface-assistant.tsx +2 -4
  37. package/src/ui/components/patterns/surface.tsx +620 -0
  38. package/src/ui/components/patterns/trigger.tsx +11 -9
  39. package/src/ui/components/primitives/ask.tsx +155 -83
  40. package/src/ui/components/primitives/button.tsx +11 -9
  41. package/src/ui/components/primitives/checkbox.tsx +8 -8
  42. package/src/ui/components/primitives/command.tsx +1 -1
  43. package/src/ui/components/primitives/composer.tsx +47 -45
  44. package/src/ui/components/primitives/detail.tsx +9 -2
  45. package/src/ui/components/primitives/dialog.tsx +60 -26
  46. package/src/ui/components/primitives/drawer.tsx +58 -12
  47. package/src/ui/components/primitives/icon-picker.tsx +90 -73
  48. package/src/ui/components/primitives/input-group.tsx +35 -28
  49. package/src/ui/components/primitives/input-otp.tsx +16 -16
  50. package/src/ui/components/primitives/input.tsx +20 -10
  51. package/src/ui/components/primitives/item.tsx +9 -12
  52. package/src/ui/components/primitives/menu.tsx +44 -65
  53. package/src/ui/components/primitives/metric-card.tsx +75 -59
  54. package/src/ui/components/primitives/radio-group.tsx +9 -9
  55. package/src/ui/components/primitives/select.tsx +344 -209
  56. package/src/ui/components/primitives/switch.tsx +10 -10
  57. package/src/ui/components/primitives/table.tsx +29 -24
  58. package/src/ui/components/primitives/tabs.tsx +4 -2
  59. package/src/ui/components/primitives/textarea.tsx +6 -6
  60. package/src/ui/components/primitives/toggle-group-context.ts +24 -0
  61. package/src/ui/components/primitives/toggle-group.tsx +116 -74
  62. package/src/ui/components/primitives/toggle.tsx +82 -16
  63. package/src/ui/docs/DocBrowser.tsx +77 -69
  64. package/src/ui/docs/content/action-form-card.md +9 -9
  65. package/src/ui/docs/content/action-list.md +76 -70
  66. package/src/ui/docs/content/action-view.md +33 -18
  67. package/src/ui/docs/content/button.md +1 -1
  68. package/src/ui/docs/content/communication.md +1 -1
  69. package/src/ui/docs/content/customization.md +64 -11
  70. package/src/ui/docs/content/detail.md +8 -6
  71. package/src/ui/docs/content/dialog.md +100 -58
  72. package/src/ui/docs/content/drawer.md +63 -19
  73. package/src/ui/docs/content/field.md +14 -15
  74. package/src/ui/docs/content/icon-tile.md +14 -13
  75. package/src/ui/docs/content/page.md +34 -26
  76. package/src/ui/docs/content/pane.md +41 -290
  77. package/src/ui/docs/content/presentation.md +3 -3
  78. package/src/ui/docs/content/separator.md +6 -6
  79. package/src/ui/docs/content/sidebar.md +68 -266
  80. package/src/ui/docs/content/skeleton.md +20 -18
  81. package/src/ui/docs/content/split.md +14 -14
  82. package/src/ui/docs/content/surface.md +513 -0
  83. package/src/ui/docs/content/tabs.md +20 -20
  84. package/src/ui/docs/content/toggle.md +117 -65
  85. package/src/ui/docs/content/tokens.md +124 -40
  86. package/src/ui/docs/registry.tsx +4 -6
  87. package/src/ui/index.ts +30 -27
  88. package/src/ui/meta.ts +15 -27
  89. package/src/ui/react.tsx +40 -43
  90. package/src/ui/theme.css +311 -64
  91. package/src/ui/components/patterns/content-header.tsx +0 -528
  92. package/src/ui/components/patterns/surface-media.tsx +0 -69
  93. package/src/ui/components/primitives/card.tsx +0 -191
  94. package/src/ui/docs/content/card.md +0 -292
  95. package/src/ui/docs/content/content.md +0 -275
@@ -6,17 +6,27 @@ erro. O consumidor compõe somente o conteúdo disponível, aqui com [`Detail`](
6
6
  coleção, use [`ActionList`](/ui/action-list).
7
7
 
8
8
  ```tsx preview col
9
- const [id, setId] = useState('empresa-x')
9
+ const [id, setId] = useState("empresa-x");
10
10
 
11
11
  render(
12
12
  <DocBrowserActionProvider>
13
13
  <div className="w-full space-y-3">
14
14
  <ButtonGroup mode="spaced">
15
- <Button variant="outline" size="sm" onClick={() => setId('empresa-x')}>Empresa X</Button>
16
- <Button variant="outline" size="sm" onClick={() => setId('empresa-y')}>Empresa Y</Button>
17
- <Button variant="outline" size="sm" onClick={() => setId('sessao-fantasma')}>Inexistente (erro)</Button>
15
+ <Button variant="outline" size="sm" onClick={() => setId("empresa-x")}>
16
+ Empresa X
17
+ </Button>
18
+ <Button variant="outline" size="sm" onClick={() => setId("empresa-y")}>
19
+ Empresa Y
20
+ </Button>
21
+ <Button
22
+ variant="outline"
23
+ size="sm"
24
+ onClick={() => setId("sessao-fantasma")}
25
+ >
26
+ Inexistente (erro)
27
+ </Button>
18
28
  </ButtonGroup>
19
- <Card className="p-4">
29
+ <Surface framed elevation="raised" className="p-4">
20
30
  <ActionView action={docWorkspaceView} input={{ id }}>
21
31
  {(ws) => (
22
32
  <DetailGroup columns={3}>
@@ -24,15 +34,20 @@ render(
24
34
  <DetailField label="Cliente" value={ws.client} />
25
35
  <DetailField
26
36
  label="Status"
27
- value={<DictionaryValue dict={docWorkspaceStatus} value={ws.status} />}
37
+ value={
38
+ <DictionaryValue
39
+ dict={docWorkspaceStatus}
40
+ value={ws.status}
41
+ />
42
+ }
28
43
  />
29
44
  </DetailGroup>
30
45
  )}
31
46
  </ActionView>
32
- </Card>
47
+ </Surface>
33
48
  </div>
34
49
  </DocBrowserActionProvider>,
35
- )
50
+ );
36
51
  ```
37
52
 
38
53
  O handler da view deve devolver um valor. Uma consulta que resolve `undefined` é tratada como erro
@@ -47,13 +62,13 @@ mais de uma região da tela ou quando o carregamento precisa ser orquestrado por
47
62
 
48
63
  ## Propriedades de ActionView
49
64
 
50
- | Propriedade | Tipo | Padrão | Descrição |
51
- |---|---|---|---|
52
- | `action` | `ViewContract<TInput, TData>` | | A ViewAction do Opus (kind view, 1 recurso). |
53
- | `input` | `TInput` | | Geralmente { id } — mudou, recarrega. |
54
- | `children` | `(data: TData, refetch) => ReactNode` | | Obrigatório, salvo quando `render` é informado. Conteúdo apresentado quando os dados estão disponíveis. `refetch` permite recarregar por código. |
55
- | `render` | `(data: TData, refetch) => ReactNode` | | Alias de compatibilidade de `children`; `children` tem precedência. Sem nenhum dos dois, o componente lança erro. |
56
- | `loading` | `ReactNode \| boolean` | `3 skeletons` | Sobrescreve o carregamento: um nó próprio, `true` para o padrão ou `false` para não renderizar nada enquanto carrega. |
57
- | `empty` | `ReactNode` | `emptyMessage` ou nada | Estado reservado a `data` indefinido. Como o react-query converte esse resultado em erro, ele não aparece na prática; devolva um valor ou o erro `not_found`. |
58
- | `emptyMessage` | `string` | | Atalho do vazio, sujeito à mesma limitação de `empty`: a frase na mesma superfície de `DataState` (`Empty` com moldura sólida). |
59
- | `error` | `(err, retry) => ReactNode` | `"Não foi possível carregar" + Tentar de novo` | Sobrescreve o estado de erro padrão. Sem ele, a frase do servidor só aparece quando é legível pela pessoa (`conflict`, `validation`, `not_found`, `authorization`, `authentication`); código técnico nunca vira título, e o botão de tentar de novo some quando o problema é de permissão ou sessão. |
65
+ | Propriedade | Tipo | Padrão | Descrição |
66
+ | -------------- | ------------------------------------- | ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
67
+ | `action` | `ViewContract<TInput, TData>` | | A ViewAction do Opus (kind view, 1 recurso). |
68
+ | `input` | `TInput` | | Geralmente { id } — mudou, recarrega. |
69
+ | `children` | `(data: TData, refetch) => ReactNode` | | Obrigatório, salvo quando `render` é informado. Conteúdo apresentado quando os dados estão disponíveis. `refetch` permite recarregar por código. |
70
+ | `render` | `(data: TData, refetch) => ReactNode` | | Alias de compatibilidade de `children`; `children` tem precedência. Sem nenhum dos dois, o componente lança erro. |
71
+ | `loading` | `ReactNode \| boolean` | `3 skeletons` | Sobrescreve o carregamento: um nó próprio, `true` para o padrão ou `false` para não renderizar nada enquanto carrega. |
72
+ | `empty` | `ReactNode` | `emptyMessage` ou nada | Estado reservado a `data` indefinido. Como o react-query converte esse resultado em erro, ele não aparece na prática; devolva um valor ou o erro `not_found`. |
73
+ | `emptyMessage` | `string` | | Atalho do vazio, sujeito à mesma limitação de `empty`: a frase na mesma superfície de `DataState` (`Empty` com moldura sólida). |
74
+ | `error` | `(err, retry) => ReactNode` | `"Não foi possível carregar" + Tentar de novo` | Sobrescreve o estado de erro padrão. Sem ele, a frase do servidor só aparece quando é legível pela pessoa (`conflict`, `validation`, `not_found`, `authorization`, `authentication`); código técnico nunca vira título, e o botão de tentar de novo some quando o problema é de permissão ou sessão. |
@@ -43,7 +43,7 @@ há texto visível. O glifo dentro do controle acompanha o tamanho (0.875rem em
43
43
  Escolha o tamanho pela região, não pela importância visual: `variant` e `context` resolvem a
44
44
  hierarquia da ação. Headers de Page, a criação no cabeçalho de uma coleção e footers de
45
45
  [`Dialog`](/ui/dialog) e [`Drawer`](/ui/drawer) usam `default`; a criação de uma coleção é um botão textual, sem
46
- ícone, como mostra [`Content`](/ui/content). Operações densas de toolbar ou de seção usam `sm`;
46
+ ícone, como mostra [`Surface`](/ui/surface). Operações densas de toolbar ou de seção usam `sm`;
47
47
  ações dentro de linha ou célula usam `xs` ou `icon-xs`. Assim a mesma decisão mantém a mesma altura
48
48
  mesmo quando uma superfície troca uma ação secundária por uma primária.
49
49
 
@@ -42,7 +42,7 @@ frase. Não copie as regras da Base para cá.
42
42
  | Superfície Opus | Papel enviado à Base |
43
43
  | ------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
44
44
  | `label`, botão de confirmação e filhos de `Button` | `button` |
45
- | `title`, `CardTitle`, `DialogTitle` | `title` |
45
+ | `title`, `SurfaceTitle`, `DialogTitle` | `title` |
46
46
  | `description`, `hint`, `help` | `description` ou `helper-text` |
47
47
  | `messages.success` e `messages.error` | `success` e `error` |
48
48
  | `confirm.message`, `dialog.*().body` e texto relevante no `DialogBody` | `dialog-body` |
@@ -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`.
@@ -131,7 +136,9 @@ fluxo modal encadeado, em que a superfície permanece aberta e troca de etapa.
131
136
  </DialogActions>
132
137
  </DialogHeader>
133
138
  <DialogBody>
134
- <p className="text-sm text-muted-foreground">Detalhe do evento selecionado.</p>
139
+ <p className="text-sm text-muted-foreground">
140
+ Detalhe do evento selecionado.
141
+ </p>
135
142
  </DialogBody>
136
143
  </DialogContent>
137
144
  </Dialog>
@@ -147,9 +154,9 @@ decidir aparece no começo do corpo, como texto ou `Alert`.
147
154
  ## Identificar a tarefa com um ícone
148
155
 
149
156
  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.
157
+ padrão, ele apenas reserva e alinha o espaço antes do título. Componha [`IconTile`](/ui/icon-tile)
158
+ explicitamente quando o ícone precisar de moldura; passe o glifo diretamente quando não precisar.
159
+ O mesmo contrato vale no `mode="alert"`: a mídia não ganha tamanho, fundo ou borda automaticamente.
153
160
 
154
161
  ```tsx preview
155
162
  render(
@@ -161,13 +168,17 @@ render(
161
168
  <DialogContent>
162
169
  <DialogHeader>
163
170
  <DialogMedia>
164
- <Share2 />
171
+ <IconTile size="sm">
172
+ <Share2 />
173
+ </IconTile>
165
174
  </DialogMedia>
166
175
  <DialogTitle>Compartilhar contrato</DialogTitle>
167
176
  </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" />
177
+ <DialogBody>
178
+ <div className="space-y-2">
179
+ <Label htmlFor="share-email">E-mail</Label>
180
+ <Input id="share-email" placeholder="nome@empresa.com.br" />
181
+ </div>
171
182
  </DialogBody>
172
183
  <DialogFooter>
173
184
  <ButtonGroup mode="spaced">
@@ -185,8 +196,10 @@ render(
185
196
  </DialogTrigger>
186
197
  <DialogContent>
187
198
  <DialogHeader>
188
- <DialogMedia context="danger">
189
- <ShieldOff />
199
+ <DialogMedia>
200
+ <IconTile size="lg" context="danger">
201
+ <ShieldOff />
202
+ </IconTile>
190
203
  </DialogMedia>
191
204
  <DialogTitle>Revogar acesso de Marina?</DialogTitle>
192
205
  </DialogHeader>
@@ -211,7 +224,8 @@ render(
211
224
  );
212
225
  ```
213
226
 
214
- Na API imperativa, a opção `media` recebe o ícone e o apresenta na mesma moldura do alerta.
227
+ Na API imperativa, a opção `media` preserva o nó recebido. Passe um `IconTile` quando quiser
228
+ moldura.
215
229
 
216
230
  ## Pedir uma resposta pela API imperativa
217
231
 
@@ -245,7 +259,11 @@ function Demo() {
245
259
  const accepted = await dialog.confirm({
246
260
  title: "Excluir rascunho?",
247
261
  body: "O rascunho e os comentários dele serão removidos.",
248
- media: <Trash2 />,
262
+ media: (
263
+ <IconTile context="danger">
264
+ <Trash2 />
265
+ </IconTile>
266
+ ),
249
267
  action: "Excluir",
250
268
  context: "danger",
251
269
  });
@@ -443,42 +461,43 @@ botão. O texto informa o efeito real, como `Excluir`, `Revogar acesso` ou `Arqu
443
461
 
444
462
  ## Propriedades de DialogContent
445
463
 
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. |
464
+ | Propriedade | Tipo | Padrão | Descrição |
465
+ | ----------------- | ----------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
466
+ | `showCloseButton` | `boolean` | `true` | No modo padrão, inclui a ação de fechamento no header; o modo de alerta exige uma resposta identificável. |
467
+ | `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. |
468
+ | `closeSize` | `"xs" \| "sm"` | `"sm"` | Define o tamanho da ação circular de fechar. |
469
+ | `layout` | `'continuous' \| 'sectioned'` | `'sectioned'` | Anatomia da superfície inteira; os slots leem esse valor e não o recebem. Ignorado em `mode="alert"`. |
470
+ | `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. |
471
+ | `children` | `ReactNode` | | Cabeçalho, corpo, rodapé ou conteúdo próprio. |
454
472
 
455
473
  ## Propriedades de DialogHeader
456
474
 
457
- | Propriedade | Tipo | Padrão | Descrição |
458
- | ----------- | ----------- | ------ | ---------------------------------------------------------- |
459
- | `className` | `string` | | Ajusta a faixa superior sobre o recuo do layout. |
475
+ | Propriedade | Tipo | Padrão | Descrição |
476
+ | ----------- | ----------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
477
+ | `className` | `string` | | Ajusta a faixa superior sobre o recuo do layout. |
460
478
  | `children` | `ReactNode` | | Somente `DialogNavigation`, `DialogMedia`, `DialogTitle` e `DialogActions`, no máximo um de cada; o fechamento padrão entra depois deles. |
461
479
 
462
480
  ## Propriedades de DialogNavigation
463
481
 
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. |
482
+ | Propriedade | Tipo | Padrão | Descrição |
483
+ | ----------- | ----------- | ------ | ----------------------------------------------------- |
484
+ | `className` | `string` | | Ajusta a região introdutória da linha. |
485
+ | `children` | `ReactNode` | | Retorno de um fluxo modal encadeado, antes do título. |
468
486
 
469
487
  ## Propriedades de DialogActions
470
488
 
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. |
489
+ | Propriedade | Tipo | Padrão | Descrição |
490
+ | ----------- | ----------- | ------ | ----------------------------------------------------- |
491
+ | `className` | `string` | | Ajusta o agrupamento das ações. |
492
+ | `children` | `ReactNode` | | Ações da tela no cabeçalho, antes do botão de fechar. |
475
493
 
476
494
  ## Propriedades de DialogBody
477
495
 
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. |
496
+ | Propriedade | Tipo | Padrão | Descrição |
497
+ | ----------- | ----------- | ------- | ---------------------------------------------------------------------- |
498
+ | `flush` | `boolean` | `false` | Remove o inset para conteúdo que controla o próprio recuo. |
499
+ | `className` | `string` | | Ajusta o corpo rolável do modo padrão ou o conteúdo próprio do alerta. |
500
+ | `children` | `ReactNode` | | Conteúdo principal da tarefa. |
482
501
 
483
502
  ## Propriedades de DialogFooter
484
503
 
@@ -489,25 +508,18 @@ botão. O texto informa o efeito real, como `Excluir`, `Revogar acesso` ou `Arqu
489
508
 
490
509
  ## Propriedades de DialogTitle
491
510
 
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. |
511
+ | Propriedade | Tipo | Padrão | Descrição |
512
+ | ----------- | ---------------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
513
+ | `level` | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | `h2` da primitiva | Hierarquia do documento. O tamanho vem do `layout`, nunca do nível. |
514
+ | `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. |
515
+ | `children` | `ReactNode` | | Nome da tarefa ou do conteúdo modal. |
497
516
 
498
517
  ## Propriedades de DialogMedia
499
518
 
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. |
519
+ | Propriedade | Tipo | Padrão | Descrição |
520
+ | ----------- | ----------- | ------ | ----------------------------------------------------------------------- |
521
+ | `className` | `string` | | Ajusta o alinhamento do slot. |
522
+ | `children` | `ReactNode` | | `IconTile`, marca, imagem ou glifo. O slot não acrescenta apresentação. |
511
523
 
512
524
  ## Propriedades de DialogClose
513
525
 
@@ -540,7 +552,7 @@ imperativa.
540
552
  | -------- | ----------- | ------ | ------------------------------------------------ |
541
553
  | `title` | `ReactNode` | | Nomeia o aviso que exige reconhecimento. |
542
554
  | `body` | `ReactNode` | | Apresenta contexto relevante no início do corpo. |
543
- | `media` | `ReactNode` | | Exibe um ícone na moldura de `DialogMedia`. |
555
+ | `media` | `ReactNode` | | Exibe o nó recebido em `DialogMedia`. |
544
556
  | `action` | `string` | `'OK'` | Define o rótulo de reconhecimento. |
545
557
 
546
558
  ## Opções de dialog.confirm
@@ -549,7 +561,7 @@ imperativa.
549
561
  | --------- | ----------------------- | ------------- | ------------------------------------------------------------------- |
550
562
  | `title` | `ReactNode` | | Nomeia a decisão. |
551
563
  | `body` | `ReactNode` | | Apresenta a consequência ou outro contexto necessário para decidir. |
552
- | `media` | `ReactNode` | | Exibe um ícone na moldura de `DialogMedia`. |
564
+ | `media` | `ReactNode` | | Exibe o nó recebido em `DialogMedia`. |
553
565
  | `action` | `string` | `'Confirmar'` | Define o rótulo do resultado afirmativo. |
554
566
  | `cancel` | `string` | `'Cancelar'` | Define o rótulo da resposta negativa. |
555
567
  | `context` | `'primary' \| 'danger'` | `'primary'` | Define o contexto da ação afirmativa. |
@@ -560,7 +572,7 @@ imperativa.
560
572
  | -------------- | ----------------------- | ------------- | ------------------------------------------------------------------ |
561
573
  | `title` | `ReactNode` | | Nomeia a informação solicitada. |
562
574
  | `body` | `ReactNode` | | Explica como a informação será usada quando isso mudar a resposta. |
563
- | `media` | `ReactNode` | | Exibe um ícone na moldura de `DialogMedia`. |
575
+ | `media` | `ReactNode` | | Exibe o nó recebido em `DialogMedia`. |
564
576
  | `action` | `string` | `'Confirmar'` | Define o rótulo do resultado afirmativo. |
565
577
  | `cancel` | `string` | `'Cancelar'` | Define o rótulo da resposta negativa. |
566
578
  | `context` | `'primary' \| 'danger'` | `'primary'` | Define o contexto da ação afirmativa. |
@@ -573,7 +585,7 @@ imperativa.
573
585
  | --------- | ------------------------- | ------ | ------------------------------------------------ |
574
586
  | `title` | `ReactNode` | | Nomeia a escolha. |
575
587
  | `body` | `ReactNode` | | Apresenta o contexto necessário antes das ações. |
576
- | `media` | `ReactNode` | | Exibe um ícone na moldura de `DialogMedia`. |
588
+ | `media` | `ReactNode` | | Exibe o nó recebido em `DialogMedia`. |
577
589
  | `actions` | `readonly DialogChoice[]` | | Declara os resultados disponíveis. |
578
590
 
579
591
  ## Propriedades de DialogChoice
@@ -586,3 +598,33 @@ imperativa.
586
598
  | `context` | `ButtonContext` | Última ação: `primary`; demais: `neutral` | Define o significado semântico. |
587
599
  | `variant` | `ButtonVariant` | Última ação: `solid`; demais: `outline` | Define o tratamento visual. |
588
600
  | `disabled` | `boolean` | `false` | Impede a escolha desta ação. |
601
+
602
+ ## Intro separada do chrome
603
+
604
+ Quando o header contém apenas navegação, ações globais e o fechamento, coloque o título do
605
+ conteúdo no primeiro bloco de `DialogBody`. `DialogIntro` exige um `DialogTitle` e aceita um
606
+ `DialogIntroActions`; o body continua responsável pelo espaço antes, entre e depois dos blocos.
607
+
608
+ ```tsx
609
+ <Dialog>
610
+ <DialogContent>
611
+ <DialogHeader>
612
+ <DialogActions>
613
+ <Button variant="ghost">Ajuda</Button>
614
+ </DialogActions>
615
+ </DialogHeader>
616
+ <DialogBody>
617
+ <DialogIntro>
618
+ <DialogTitle>Editar evento</DialogTitle>
619
+ <DialogIntroActions>
620
+ <Button variant="outline">Revisar</Button>
621
+ </DialogIntroActions>
622
+ </DialogIntro>
623
+ <form>…</form>
624
+ </DialogBody>
625
+ <DialogFooter>…</DialogFooter>
626
+ </DialogContent>
627
+ </Dialog>
628
+ ```
629
+
630
+ No formato compacto, mantenha `DialogTitle` e `DialogActions` no próprio `DialogHeader`.