@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
package/src/ui/index.ts CHANGED
@@ -5,7 +5,10 @@
5
5
  * Aqui ficam apenas types/contratos compartilhados.
6
6
  */
7
7
 
8
- import type { ActionError, Paginated } from '../core/index.ts'
8
+ import type { ActionError, Paginated } from "../core/index.ts";
9
+
10
+ // Personalização de aparência (ADR 0044): sem React, também disponível em `./ui/appearance`.
11
+ export * from "./appearance.ts";
9
12
 
10
13
  /**
11
14
  * Metadado de descoberta de um componente — cada componente co-localiza um `meta`
@@ -13,18 +16,18 @@ import type { ActionError, Paginated } from '../core/index.ts'
13
16
  */
14
17
  export interface ComponentMeta {
15
18
  /** Nome canônico (kebab), ex.: 'button', 'dropdown-menu'. */
16
- name: string
19
+ name: string;
17
20
  /** Origem: descendente do shadcn (forkado) ou nativo do Opus. */
18
- ancestry: 'shadcn' | 'opus'
21
+ ancestry: "shadcn" | "opus";
19
22
  /** Quando usar / quando não — guia o agente de design a escolher certo. */
20
- whenToUse: string
23
+ whenToUse: string;
21
24
  /** Aviso de retirada no catálogo, com o caminho de migração. */
22
25
  deprecated?: {
23
26
  /** Alternativa recomendada; aceita `código` inline como `whenToUse`. */
24
- alternative: string
27
+ alternative: string;
25
28
  /** Versão em que a retirada começou, quando houver. */
26
- since?: string
27
- }
29
+ since?: string;
30
+ };
28
31
  }
29
32
 
30
33
  /**
@@ -32,23 +35,23 @@ export interface ComponentMeta {
32
35
  * shape derivado disto.
33
36
  */
34
37
  export interface ActionState<TData> {
35
- data: TData | undefined
36
- error: ActionError | undefined
37
- isIdle: boolean
38
- isLoading: boolean
39
- isSuccess: boolean
40
- isError: boolean
38
+ data: TData | undefined;
39
+ error: ActionError | undefined;
40
+ isIdle: boolean;
41
+ isLoading: boolean;
42
+ isSuccess: boolean;
43
+ isError: boolean;
41
44
  }
42
45
 
43
46
  export interface ListState<TItem> {
44
- items: TItem[]
45
- cursor: { next: string | null; prev?: string | null }
46
- total: number | undefined
47
- error: ActionError | undefined
48
- isIdle: boolean
49
- isLoading: boolean
50
- isSuccess: boolean
51
- isError: boolean
47
+ items: TItem[];
48
+ cursor: { next: string | null; prev?: string | null };
49
+ total: number | undefined;
50
+ error: ActionError | undefined;
51
+ isIdle: boolean;
52
+ isLoading: boolean;
53
+ isSuccess: boolean;
54
+ isError: boolean;
52
55
  }
53
56
 
54
57
  /**
@@ -62,7 +65,7 @@ export function idleAction<T>(): ActionState<T> {
62
65
  isLoading: false,
63
66
  isSuccess: false,
64
67
  isError: false,
65
- }
68
+ };
66
69
  }
67
70
 
68
71
  export function idleLookup<T>(): ListState<T> {
@@ -75,7 +78,7 @@ export function idleLookup<T>(): ListState<T> {
75
78
  isLoading: false,
76
79
  isSuccess: false,
77
80
  isError: false,
78
- }
81
+ };
79
82
  }
80
83
 
81
84
  /**
@@ -83,10 +86,10 @@ export function idleLookup<T>(): ListState<T> {
83
86
  */
84
87
  export function isPaginated(value: unknown): value is Paginated<unknown> {
85
88
  return (
86
- typeof value === 'object' &&
89
+ typeof value === "object" &&
87
90
  value !== null &&
88
- 'items' in value &&
91
+ "items" in value &&
89
92
  Array.isArray((value as { items: unknown }).items) &&
90
- 'cursor' in value
91
- )
93
+ "cursor" in value
94
+ );
92
95
  }
package/src/ui/meta.ts CHANGED
@@ -11,11 +11,11 @@ import type { ComponentMeta } from "./index.ts";
11
11
 
12
12
  /** Todos os metas, chaveados pelo nome canônico (kebab). Fonte única — zero duplicação. */
13
13
  export const componentMeta = {
14
- content: {
15
- name: "content",
14
+ surface: {
15
+ name: "surface",
16
16
  ancestry: "opus",
17
17
  whenToUse:
18
- "Organize uma região nomeada dentro de uma página ou de outra superfície. Use as propriedades de Content no caso comum e componha seus slots quando precisar controlar a estrutura. Numa página centrada em uma coleção, `level={1}` faz do Content o heading principal; nas demais, ele vem de PageTitle.",
18
+ 'Delimite e nomeie um trecho da tela: uma região da página, um grupo de campos, um registro, um item de grade ou um bloco clicável. `as` escolhe o elemento (`div`, `section`, `article` ou `fieldset`) e `asChild` entrega a superfície a um botão ou link. `framed` acrescenta a moldura, `elevation="raised"` dá fundo próprio e `layout` escolhe entre ritmo contínuo e faixas com divisores. No caso comum, declare `title`, `description`, `count`, `actions` e `media` na própria Surface; componha SurfaceHeader, SurfaceBody e SurfaceFooter quando precisar controlar a estrutura. Numa página centrada em uma coleção, `level={1}` faz da Surface o heading principal; nas demais, ele vem de PageTitle. Para um modal use Dialog, para um painel lateral use Drawer e para uma coluna de layout use Pane.',
19
19
  },
20
20
  ask: {
21
21
  name: "ask",
@@ -77,17 +77,11 @@ export const componentMeta = {
77
77
  whenToUse:
78
78
  "Inicie uma ação com um controle clicável. Use `context` para o significado, `variant` para o tratamento visual e `size` para a escala compartilhada (`xs`…`lg` e os quadrados `icon-*`); `icon` recebe um nó e `busy` comunica o andamento e impede um novo acionamento. Para ações relacionadas, use ButtonGroup conectado, espaçado ou dividido.",
79
79
  },
80
- card: {
81
- name: "card",
82
- ancestry: "shadcn",
83
- whenToUse:
84
- "Agrupe conteúdo relacionado em uma superfície delimitada. Use Card diretamente para um bloco simples ou componha cabeçalho, corpo e rodapé quando houver hierarquia e ações. CardContent permanece apenas como alias temporário de compatibilidade.",
85
- },
86
80
  "metric-card": {
87
81
  name: "metric-card",
88
82
  ancestry: "opus",
89
83
  whenToUse:
90
- "Apresente uma medida resumida, como um total ou uma contagem, com rótulo, valor em destaque e descrição opcional. O consumidor calcula e formata os valores. Use `secondaryMetric` para uma segunda leitura da mesma dimensão, exibida como lâmina posterior com rótulo próprio; `context` realça somente o ícone, `action` acrescenta uma ação diretamente relacionada e `loading` substitui rótulo e valor durante a carga. Com `onActivate`, `active` e `activationLabel`, informados juntos, o card vira um controle que aplica o recorte que a medida resume e expõe o estado por `aria-pressed`. Para conteúdo geral ou estrutura livre, use Card.",
84
+ "Apresente uma medida resumida, como um total ou uma contagem, com rótulo, valor em destaque e descrição opcional. O consumidor calcula e formata os valores. Use `secondaryMetric` para uma segunda leitura da mesma dimensão, exibida como lâmina posterior com rótulo próprio; `context` realça somente o ícone, `action` acrescenta uma ação diretamente relacionada e `loading` substitui rótulo e valor durante a carga. Com `onActivate`, `active` e `activationLabel`, informados juntos, o card vira um controle que aplica o recorte que a medida resume e expõe o estado por `aria-pressed`. Para conteúdo geral ou estrutura livre, use Surface.",
91
85
  },
92
86
  chat: {
93
87
  name: "chat",
@@ -119,12 +113,6 @@ export const componentMeta = {
119
113
  whenToUse:
120
114
  "Receba texto para envio sem montar uma conversa completa. Enter envia, Shift+Enter quebra a linha e `actions` acrescenta uma faixa para seletores relacionados. O componente é controlado; use Chat quando também precisar de mensagens e estado da conversa.",
121
115
  },
122
- "content-header": {
123
- name: "content-header",
124
- ancestry: "opus",
125
- whenToUse:
126
- 'Estruture uma região com título e corpo. No caso comum, declare título, mídia, descrição, contador e ações diretamente em Content; use `framed` quando a região precisar de limite próprio, `layout="sectioned"` quando o filho controlar o recuo do body, `frameVariant="legend"` para integrar visualmente o header à borda e `as="fieldset"` somente para agrupar campos sob uma legenda nativa. Componha ContentHeader apenas quando precisar controlar a anatomia.',
127
- },
128
116
  copyable: {
129
117
  name: "copyable",
130
118
  ancestry: "opus",
@@ -177,7 +165,7 @@ export const componentMeta = {
177
165
  name: "separator",
178
166
  ancestry: "shadcn",
179
167
  whenToUse:
180
- 'Marque a fronteira entre blocos de conteúdo que não pertencem a uma superfície com regiões. A linha horizontal fica entre blocos empilhados; com `orientation="vertical"`, ela separa itens lado a lado e herda a altura do contêiner. A linha é decorativa por padrão e fica fora da árvore de acessibilidade; use `decorative={false}` quando a divisão tiver significado. Não use Separator entre cabeçalho, corpo e rodapé de Card, Dialog ou Drawer: nessas superfícies, os divisores são derivados das regiões com `layout="sectioned"`.',
168
+ 'Marque a fronteira entre blocos de conteúdo que não pertencem a uma superfície com regiões. A linha horizontal fica entre blocos empilhados; com `orientation="vertical"`, ela separa itens lado a lado e herda a altura do contêiner. A linha é decorativa por padrão e fica fora da árvore de acessibilidade; use `decorative={false}` quando a divisão tiver significado. Não use Separator entre cabeçalho, corpo e rodapé de Surface, Dialog ou Drawer: nessas superfícies, os divisores são derivados das regiões com `layout="sectioned"`.',
181
169
  },
182
170
  skeleton: {
183
171
  name: "skeleton",
@@ -201,7 +189,7 @@ export const componentMeta = {
201
189
  name: "tabs",
202
190
  ancestry: "shadcn",
203
191
  whenToUse:
204
- 'Navegue entre painéis relacionados no mesmo nível. Cada TabsListItem aponta para um TabsPanel pelo mesmo `value`; TabsList usa uma linha para indicar a aba ativa, enquanto Tabs define orientação, escala, distribuição e moldura. Use ToggleGroup para escolher valores sem navegar entre painéis.',
192
+ "Navegue entre painéis relacionados no mesmo nível. Cada TabsListItem aponta para um TabsPanel pelo mesmo `value`; TabsList usa uma linha para indicar a aba ativa, enquanto Tabs define orientação, escala, distribuição e moldura. Use ToggleGroup para escolher valores sem navegar entre painéis.",
205
193
  },
206
194
  textarea: {
207
195
  name: "textarea",
@@ -285,7 +273,7 @@ export const componentMeta = {
285
273
  name: "field",
286
274
  ancestry: "shadcn",
287
275
  whenToUse:
288
- 'Componha rótulo, controle, ajuda e erro com espaçamento consistente. Use a orientação vertical no caso comum e a horizontal quando controle e texto precisarem ficar lado a lado. FieldGroup organiza vários campos; quando o conjunto precisar de título, componha-o dentro de Content com `as="fieldset"`.',
276
+ 'Componha rótulo, controle, ajuda e erro com espaçamento consistente. Use a orientação vertical no caso comum e a horizontal quando controle e texto precisarem ficar lado a lado. FieldGroup organiza vários campos; quando o conjunto precisar de título, componha-o dentro de Surface com `as="fieldset"`.',
289
277
  },
290
278
  "input-otp": {
291
279
  name: "input-otp",
@@ -327,19 +315,19 @@ export const componentMeta = {
327
315
  name: "split",
328
316
  ancestry: "opus",
329
317
  whenToUse:
330
- "Divide uma área em panes em sequência horizontal ou vertical. Use `resizable` quando a pessoa deve ajustar a fronteira; o mesmo `<Split>` vira flex simples sem ele. Cada `<Pane>` declara tamanho inicial e mínimo; a anatomia do pane tem página própria. SurfaceAssistant especializa esse layout para assistência contextual dentro de Page, Dialog ou Drawer.",
318
+ "Divide uma área em panes em sequência horizontal ou vertical. Use `resizable` quando a pessoa deve ajustar a fronteira; o mesmo `<Split>` vira flex simples sem ele. Cada `<Pane>` declara apenas dimensão e crescimento; o filho compõe apresentação e rolagem. SurfaceAssistant especializa esse layout para assistência contextual dentro de Page, Dialog ou Drawer.",
331
319
  },
332
320
  sidebar: {
333
321
  name: "sidebar",
334
322
  ancestry: "opus",
335
323
  whenToUse:
336
- "Organize navegação global ou contextual em uma coluna lateral. Split e Pane definem posição e largura; Sidebar fornece a superfície e o colapso. Use SidebarNav para grupos planos e componha árvores com SidebarItem e SidebarTreeGroup; cabeçalho e rodapé próprios são do Pane que a hospeda.",
324
+ "Organize navegação global ou contextual em uma coluna lateral. Split e Pane definem posição e largura; Sidebar fornece a coluna e o colapso. Use SidebarNav para grupos planos e componha árvores com SidebarItem e SidebarTreeGroup; quando precisar de header, body ou footer visuais, envolva a Sidebar com Surface.",
337
325
  },
338
326
  pane: {
339
327
  name: "pane",
340
328
  ancestry: "opus",
341
329
  whenToUse:
342
- "Estruture a coluna ou faixa encaixada em um Split. PaneHeader nomeia a região e acomoda mídia e ações; ao lado desses slots, também aceita conteúdo livre, como uma busca ou a marca de uma barra lateral. PaneBody rola e PaneFooter mantém à vista o que não pode sair da tela. `layout` escolhe entre regiões seccionadas, com divisores derivados da vizinhança, e o fluxo contínuo. `inset` vale para o pane sem slots; com eles, o recuo pertence a cada região.",
330
+ "Declare o tamanho de uma região dentro de Split. Pane cuida somente de dimensão, limites e crescimento; o filho decide superfície, organização e rolagem. Use Surface quando a região precisar de header, body, footer, borda ou elevação, e Sidebar quando ela for uma navegação lateral.",
343
331
  },
344
332
  "scroll-area": {
345
333
  name: "scroll-area",
@@ -363,7 +351,7 @@ export const componentMeta = {
363
351
  name: "toggle",
364
352
  ancestry: "shadcn",
365
353
  whenToUse:
366
- 'Alterne um estado diretamente no contexto atual. ToggleGroup reúne escolhas únicas ou múltiplas e compartilha aparência e escala entre os itens. Use `variant="outline"` com `spacing={0}` para formar um grupo conectado com borda. Para navegar entre painéis, use Tabs.',
354
+ 'Alterne um estado diretamente no contexto atual. ToggleGroup reúne escolhas únicas ou múltiplas com a mesma gramática estrutural de ButtonGroup; `mode="segmented"` mantém alternativas curtas visíveis numa superfície compartilhada, enquanto `variant` trata cada Toggle. Para navegar entre painéis, use Tabs.',
367
355
  },
368
356
 
369
357
  confirm: {
@@ -388,13 +376,13 @@ export const componentMeta = {
388
376
  name: "action-form-card",
389
377
  ancestry: "opus",
390
378
  whenToUse:
391
- "Apresente uma FormAction como seção delimitada da página. ActionFormCard combina a estrutura do Card com o comportamento de ActionForm. Para um modal, use ActionFormDialog; sem superfície adicional, use ActionForm.",
379
+ "Apresente uma FormAction como seção delimitada da página. ActionFormCard combina a estrutura de uma Surface elevada com o comportamento de ActionForm. Para um modal, use ActionFormDialog; sem superfície adicional, use ActionForm.",
392
380
  },
393
381
  "action-list": {
394
382
  name: "action-list",
395
383
  ancestry: "opus",
396
384
  whenToUse:
397
- "Apresente uma coleção pesquisável a partir de uma ListAction. O contrato declara colunas, filtros, período e busca; ActionList coordena visualizações, paginação, seleção em lote e sincronização com a URL. A tabela derivada não cria moldura própria. Em uma página, envolva a lista com Content para manter título e criação junto da coleção. Para um único recurso, use ActionView.",
385
+ "Apresente uma coleção pesquisável a partir de uma ListAction. O contrato declara colunas, filtros, período e busca; ActionList coordena visualizações, paginação, seleção em lote e sincronização com a URL. A tabela derivada não cria moldura própria. Em uma página, envolva a lista com Surface para manter título e criação junto da coleção. Para um único recurso, use ActionView.",
398
386
  },
399
387
  "action-trigger": {
400
388
  name: "action-trigger",
@@ -412,13 +400,13 @@ export const componentMeta = {
412
400
  name: "page",
413
401
  ancestry: "opus",
414
402
  whenToUse:
415
- "Estruture uma página com cabeçalho, estado e conteúdo na mesma anatomia. Page renderiza o `<main>` com um container centralizado de até 80rem (`max-w-7xl`); PageHeader reúne navegação contextual opcional, título e ações, enquanto PageBody recebe o conteúdo. PageShell pode fornecer navegação e ações conhecidas pela aplicação sem criar uma barra visual. Uma região interna pode fornecer o heading principal com Content. PageActionsTarget fica restrito a workspaces imersivos. PageState substitui o header e o conteúdo durante carregamento, falha ou ausência, por isso informações indispensáveis entram no corpo, como Alert ou texto. Para uma falha parcial, mantenha o estado na seção com DataState; para uma região disponível para criação ou vínculo, use Empty.",
403
+ "Estruture uma página com cabeçalho, estado e conteúdo na mesma anatomia. Page renderiza o `<main>` com um container centralizado de até 80rem (`max-w-7xl`); PageHeader reúne navegação contextual opcional, título e ações, enquanto PageBody recebe o conteúdo. PageShell pode fornecer navegação e ações conhecidas pela aplicação sem criar uma barra visual. Uma região interna pode fornecer o heading principal com `Surface level={1}`. PageActionsTarget fica restrito a workspaces imersivos. PageState substitui o header e o conteúdo durante carregamento, falha ou ausência, por isso informações indispensáveis entram no corpo, como Alert ou texto. Para uma falha parcial, mantenha o estado na seção com DataState; para uma região disponível para criação ou vínculo, use Empty.",
416
404
  },
417
405
  presentation: {
418
406
  name: "presentation",
419
407
  ancestry: "opus",
420
408
  whenToUse:
421
- "Descreva um recurso que precisa manter a mesma anatomia ao aparecer como Page, Dialog ou Drawer. Presentation organiza navegação, título, ações de cabeçalho, body e ações de rodapé sem redefinir o conteúdo para cada superfície. Em Page de listagem, Content mantém título e ações junto do ActionList; modais usam o header da superfície. Route pode declarar URL, surface e origem do registro para deep links. A definição persistente liga esses slots a actions Opus; a invocação carrega o estado JSON da execução. PresentationInspector abre um snapshot isolado, enquanto a Lens apresenta o inventário estático projetado no manifest.",
409
+ "Descreva um recurso que precisa manter a mesma anatomia ao aparecer como Page, Dialog ou Drawer. Presentation organiza navegação, título, ações de cabeçalho, body e ações de rodapé sem redefinir o conteúdo para cada superfície. Em Page de listagem, Surface mantém título e ações junto do ActionList; modais usam o header da superfície. Route pode declarar URL, surface e origem do registro para deep links. A definição persistente liga esses slots a actions Opus; a invocação carrega o estado JSON da execução. PresentationInspector abre um snapshot isolado, enquanto a Lens apresenta o inventário estático projetado no manifest.",
422
410
  },
423
411
  router: {
424
412
  name: "router",
package/src/ui/react.tsx CHANGED
@@ -10,6 +10,9 @@ export * from "./drivers/react.tsx";
10
10
  // Utilitário
11
11
  export { cn } from "./lib/cn.ts";
12
12
 
13
+ // Personalização de aparência (ADR 0044): o mesmo módulo sem React de `./ui/appearance`.
14
+ export * from "./appearance.ts";
15
+
13
16
  // Primitivos
14
17
  export { Button, buttonVariants } from "./components/primitives/button.tsx";
15
18
  export type {
@@ -90,18 +93,6 @@ export type {
90
93
  DetailGroupVariant,
91
94
  } from "./components/primitives/detail.tsx";
92
95
 
93
- export {
94
- Card,
95
- CardHeader,
96
- CardMedia,
97
- CardTitle,
98
- CardDescription,
99
- CardAction,
100
- CardBody,
101
- CardContent,
102
- CardFooter,
103
- } from "./components/primitives/card.tsx";
104
-
105
96
  export { MetricCard } from "./components/primitives/metric-card.tsx";
106
97
  export type {
107
98
  MetricCardProps,
@@ -136,6 +127,8 @@ export {
136
127
  DialogNavigation,
137
128
  DialogMedia,
138
129
  DialogActions,
130
+ DialogIntro,
131
+ DialogIntroActions,
139
132
  DialogBody,
140
133
  DialogFooter,
141
134
  DialogTitle,
@@ -146,7 +139,7 @@ export type {
146
139
  DialogMode,
147
140
  } from "./components/primitives/dialog.tsx";
148
141
 
149
- // O pacote de anatomia que Card, Dialog, Drawer e Pane declaram no root (ADR 0023). As
142
+ // O pacote de anatomia que Surface, Dialog, Drawer e Pane declaram no root (ADR 0023). As
150
143
  // tabelas e o contexto continuam internos; o consumidor escolhe entre os dois nomes.
151
144
  export type { SurfaceLayout } from "./components/patterns/surface-anatomy.ts";
152
145
 
@@ -278,6 +271,8 @@ export {
278
271
  export {
279
272
  Drawer,
280
273
  DrawerActions,
274
+ DrawerIntro,
275
+ DrawerIntroActions,
281
276
  DrawerClose,
282
277
  DrawerContent,
283
278
  DrawerFooter,
@@ -372,10 +367,16 @@ export {
372
367
  export { ScrollArea, ScrollBar } from "./components/primitives/scroll-area.tsx";
373
368
  export { Slider } from "./components/primitives/slider.tsx";
374
369
  export { Switch } from "./components/primitives/switch.tsx";
375
- export { Toggle, toggleVariants } from "./components/primitives/toggle.tsx";
370
+ export {
371
+ Toggle,
372
+ type ToggleProps,
373
+ toggleVariants,
374
+ } from "./components/primitives/toggle.tsx";
376
375
  export {
377
376
  ToggleGroup,
378
- ToggleGroupItem,
377
+ type ToggleGroupDistribution,
378
+ type ToggleGroupMode,
379
+ type ToggleGroupProps,
379
380
  } from "./components/primitives/toggle-group.tsx";
380
381
  export { Truncate } from "./components/primitives/truncate.tsx";
381
382
  export type { TruncateProps } from "./components/primitives/truncate.tsx";
@@ -482,26 +483,30 @@ export type {
482
483
  PageStateStatus,
483
484
  } from "./components/patterns/page-state.tsx";
484
485
 
485
- // Região de conteúdo composta; shorthand e slots explícitos compartilham a mesma anatomia.
486
- export {
487
- Content,
488
- ContentHeader,
489
- ContentMedia,
490
- ContentTitle,
491
- ContentDescription,
492
- ContentMeta,
493
- ContentActions,
494
- ContentBody,
495
- } from "./components/patterns/content-header.tsx";
486
+ // Superfície: região de página, grupo de campos, entidade, item ou bloco clicável. A
487
+ // semântica vem de `as`/`asChild`; forma curta e slots compartilham a anatomia (ADR 0045).
488
+ export {
489
+ Surface,
490
+ SurfaceHeader,
491
+ SurfaceMedia,
492
+ SurfaceTitle,
493
+ SurfaceDescription,
494
+ SurfaceMeta,
495
+ SurfaceActions,
496
+ SurfaceBody,
497
+ SurfaceFooter,
498
+ } from "./components/patterns/surface.tsx";
496
499
  export type {
497
- ContentProps,
498
- ContentHeaderProps,
499
- ContentElement,
500
- ContentFrameVariant,
501
- ContentHeaderLevel,
502
- } from "./components/patterns/content-header.tsx";
503
-
504
- // Layout composicional: Split decide a relação espacial; Pane carrega conteúdo com inset.
500
+ SurfaceProps,
501
+ SurfaceHeaderProps,
502
+ SurfaceMediaProps,
503
+ SurfaceBodyProps,
504
+ SurfaceElement,
505
+ SurfaceElevation,
506
+ } from "./components/patterns/surface.tsx";
507
+ export type { HeadingLevel } from "./components/patterns/heading-level.ts";
508
+
509
+ // Layout composicional: Split decide a relação espacial; Pane declara somente dimensão.
505
510
  export { Split, Pane } from "./components/patterns/split.tsx";
506
511
  export type {
507
512
  SplitProps,
@@ -529,16 +534,8 @@ export type {
529
534
  SurfaceStatusContext,
530
535
  } from "./components/patterns/dock.tsx";
531
536
 
532
- // Barra lateral composicional: header/conteúdo/footer e navegação, sem possuir o layout.
537
+ // Barra lateral composicional e navegação, sem possuir o layout.
533
538
  export {
534
- PaneActions,
535
- PaneDescription,
536
- PaneHeader,
537
- PaneMedia,
538
- PaneTitle,
539
- PaneBody,
540
- PaneContent,
541
- PaneFooter,
542
539
  Sidebar,
543
540
  SidebarItem,
544
541
  SidebarGroupLabel,