@softize/opus 22.2.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 (115) hide show
  1. package/CHANGELOG.md +120 -1
  2. package/bin/cli.mjs +3 -9
  3. package/bin/lib/check.mjs +242 -49
  4. package/bin/lib/copy.mjs +17 -35
  5. package/bin/lib/gen-runner.mjs +2 -0
  6. package/bin/lib/gen-stubs.mjs +3 -1
  7. package/docs/adr/0005-structural-surfaces-share-an-explicit-anatomy.md +1 -1
  8. package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +6 -0
  9. package/docs/adr/0032-tab-baseline-respects-the-container-inset.md +16 -12
  10. package/docs/adr/0034-tabs-can-own-a-frame-without-owning-a-surface.md +8 -6
  11. package/docs/adr/0036-tabs-declares-trigger-distribution.md +11 -8
  12. package/docs/adr/0037-design-data-lives-outside-the-contract.md +1 -1
  13. package/docs/adr/0038-content-level-defines-primary-presentation.md +62 -0
  14. package/docs/adr/0039-filter-presentation-is-independent-from-filter-type.md +62 -0
  15. package/docs/adr/0040-tabs-use-a-single-navigation-presentation.md +59 -0
  16. package/docs/adr/0042-toggle-group-shares-the-grouped-control-grammar.md +79 -0
  17. package/docs/adr/0043-neutral-surfaces-and-interactions-use-opaque-role-tokens.md +73 -0
  18. package/docs/adr/0044-neutral-appearance-derives-from-bounded-axes-per-mode.md +133 -0
  19. package/docs/adr/0045-surface-replaces-content-and-card.md +230 -0
  20. package/docs/adr/0046-structural-body-owns-content-rhythm.md +103 -0
  21. package/docs/adr/0047-pane-is-only-a-split-layout-unit.md +71 -0
  22. package/docs/adr/0048-modal-intro-separates-content-from-chrome.md +34 -0
  23. package/docs/code-style.md +1 -1
  24. package/package.json +5 -1
  25. package/registry/skills/build-opus-ui/SKILL.md +11 -11
  26. package/registry/skills/build-opus-ui/references/evaluations.md +10 -9
  27. package/registry/skills/build-opus-ui/references/ui-patterns.md +17 -16
  28. package/registry/skills/create-opus-action/references/contract-and-binding.md +3 -3
  29. package/registry/templates/app/src/App.tsx +24 -12
  30. package/src/core/contracts.ts +1 -1
  31. package/src/core/runtime.ts +4 -37
  32. package/src/core/types.ts +5 -23
  33. package/src/design/index.ts +14 -3
  34. package/src/testing/index.ts +6 -26
  35. package/src/ui/appearance.ts +149 -0
  36. package/src/ui/components/patterns/action-form-card.tsx +46 -33
  37. package/src/ui/components/patterns/action-list-dialog.tsx +2 -4
  38. package/src/ui/components/patterns/confirm.tsx +3 -1
  39. package/src/ui/components/patterns/fill-region.ts +8 -7
  40. package/src/ui/components/patterns/form-dialog.tsx +1 -3
  41. package/src/ui/components/patterns/form.tsx +8 -7
  42. package/src/ui/components/patterns/list.tsx +64 -14
  43. package/src/ui/components/patterns/page.tsx +15 -11
  44. package/src/ui/components/patterns/pane.tsx +9 -240
  45. package/src/ui/components/patterns/presentation.tsx +14 -14
  46. package/src/ui/components/patterns/sidebar.tsx +7 -17
  47. package/src/ui/components/patterns/split.tsx +111 -59
  48. package/src/ui/components/patterns/{surface-header.tsx → structural-header.tsx} +50 -19
  49. package/src/ui/components/patterns/structural-media.tsx +26 -0
  50. package/src/ui/components/patterns/surface-anatomy.ts +71 -61
  51. package/src/ui/components/patterns/surface-assistant.tsx +2 -4
  52. package/src/ui/components/patterns/surface.tsx +620 -0
  53. package/src/ui/components/patterns/trigger.tsx +11 -9
  54. package/src/ui/components/primitives/ask.tsx +155 -83
  55. package/src/ui/components/primitives/button.tsx +11 -9
  56. package/src/ui/components/primitives/checkbox.tsx +8 -8
  57. package/src/ui/components/primitives/command.tsx +1 -1
  58. package/src/ui/components/primitives/composer.tsx +47 -45
  59. package/src/ui/components/primitives/detail.tsx +9 -2
  60. package/src/ui/components/primitives/dialog.tsx +60 -26
  61. package/src/ui/components/primitives/drawer.tsx +58 -12
  62. package/src/ui/components/primitives/icon-picker.tsx +93 -70
  63. package/src/ui/components/primitives/input-group.tsx +35 -28
  64. package/src/ui/components/primitives/input-otp.tsx +16 -16
  65. package/src/ui/components/primitives/input.tsx +20 -10
  66. package/src/ui/components/primitives/item.tsx +9 -12
  67. package/src/ui/components/primitives/menu.tsx +44 -65
  68. package/src/ui/components/primitives/metric-card.tsx +75 -59
  69. package/src/ui/components/primitives/radio-group.tsx +9 -9
  70. package/src/ui/components/primitives/select.tsx +344 -209
  71. package/src/ui/components/primitives/switch.tsx +10 -10
  72. package/src/ui/components/primitives/table.tsx +29 -24
  73. package/src/ui/components/primitives/tabs.tsx +58 -88
  74. package/src/ui/components/primitives/textarea.tsx +6 -6
  75. package/src/ui/components/primitives/toggle-group-context.ts +24 -0
  76. package/src/ui/components/primitives/toggle-group.tsx +116 -74
  77. package/src/ui/components/primitives/toggle.tsx +82 -16
  78. package/src/ui/docs/DocBrowser.tsx +77 -69
  79. package/src/ui/docs/content/action-form-card.md +9 -9
  80. package/src/ui/docs/content/action-list.md +85 -70
  81. package/src/ui/docs/content/action-view.md +33 -18
  82. package/src/ui/docs/content/actions.md +6 -7
  83. package/src/ui/docs/content/button.md +1 -1
  84. package/src/ui/docs/content/communication.md +1 -1
  85. package/src/ui/docs/content/customization.md +64 -11
  86. package/src/ui/docs/content/detail.md +8 -6
  87. package/src/ui/docs/content/dialog.md +100 -58
  88. package/src/ui/docs/content/drawer.md +63 -19
  89. package/src/ui/docs/content/field.md +14 -15
  90. package/src/ui/docs/content/icon-tile.md +14 -13
  91. package/src/ui/docs/content/page.md +34 -26
  92. package/src/ui/docs/content/pane.md +41 -290
  93. package/src/ui/docs/content/presentation.md +3 -3
  94. package/src/ui/docs/content/semantic-context.md +0 -1
  95. package/src/ui/docs/content/separator.md +6 -6
  96. package/src/ui/docs/content/sidebar.md +68 -266
  97. package/src/ui/docs/content/skeleton.md +20 -18
  98. package/src/ui/docs/content/split.md +14 -14
  99. package/src/ui/docs/content/surface.md +513 -0
  100. package/src/ui/docs/content/tabs.md +82 -83
  101. package/src/ui/docs/content/testing.md +2 -3
  102. package/src/ui/docs/content/toggle.md +120 -63
  103. package/src/ui/docs/content/tokens.md +124 -40
  104. package/src/ui/docs/registry.tsx +4 -6
  105. package/src/ui/drivers/react.tsx +4 -3
  106. package/src/ui/index.ts +30 -27
  107. package/src/ui/meta.ts +15 -27
  108. package/src/ui/react.tsx +42 -46
  109. package/src/ui/theme.css +311 -64
  110. package/src/vite/design.ts +23 -21
  111. package/src/ui/components/patterns/content-header.tsx +0 -532
  112. package/src/ui/components/patterns/surface-media.tsx +0 -69
  113. package/src/ui/components/primitives/card.tsx +0 -191
  114. package/src/ui/docs/content/card.md +0 -292
  115. package/src/ui/docs/content/content.md +0 -276
@@ -34,19 +34,19 @@ render(
34
34
 
35
35
  ## Coleção nomeada
36
36
 
37
- Em uma página, envolva a listagem com [`Content`](/ui/content). O título e a criação ocupam
37
+ Em uma página, envolva a listagem com [`Surface`](/ui/surface). O título e a criação ocupam
38
38
  extremos opostos do cabeçalho da coleção; busca, filtros e atualização permanecem na toolbar do
39
39
  `ActionList`.
40
40
 
41
41
  ```tsx
42
- <Content
42
+ <Surface
43
+ as="section"
43
44
  title="Workspaces"
44
45
  level={1}
45
- variant="page"
46
46
  actions={<Button>Criar workspace</Button>}
47
47
  >
48
48
  <ActionList action={workspaceList} input={{}} />
49
- </Content>
49
+ </Surface>
50
50
  ```
51
51
 
52
52
  Use `toolbarActions` somente quando a operação depender do recorte visível, como exportar os
@@ -95,12 +95,11 @@ ao modal “Filtros”; `external` mantém o filtro no estado navegável e no in
95
95
  apresentação para o consumidor, como cards de status acima da lista. No modal, filtros com a mesma
96
96
  `section` formam um grupo, e `advancedFilters.columns` distribui os grupos em até três colunas.
97
97
  Use `advancedFilters.sectionIcons` quando os grupos representarem dimensões recorrentes; a chave é
98
- o mesmo rótulo de `section`, e cada grupo usa `Content as="fieldset"` para manter uma legenda
99
- nativa. O ícone ocupa a mídia do grupo, antes do título, e recebe a moldura padrão: passe só o
100
- glifo. Um `IconTile` é adotado pela mídia com o que declara, e um ícone `null` não desenha nada.
101
- Quando os grupos precisarem de limites mais explícitos, `advancedFilters.sectionVariant: 'framed'`
102
- envolve cada seção e pede ao `Content` o acabamento `frameVariant="legend"`, integrando visualmente
103
- a legenda à borda sem atribuir esse design à semântica de `fieldset`.
98
+ o mesmo rótulo de `section`, e cada grupo usa `Surface as="fieldset"` para manter uma legenda
99
+ nativa. O ícone ocupa a mídia do grupo antes do título. Passe um `IconTile` quando quiser moldura;
100
+ um glifo solto permanece sem decoração, e um ícone `null` não desenha nada. Quando os grupos
101
+ precisarem de limites mais explícitos, `advancedFilters.sectionVariant: 'framed'` acrescenta a
102
+ borda da `Surface` sem mudar a semântica do `fieldset`.
104
103
  Um filtro `select` pode declarar `searchable: true` quando a lista fechada for longa; a opção geral
105
104
  continua disponível e participa da busca. Campos de data avançados usam calendário. A antiga `advanced: true` continua aceita durante a
106
105
  migração e equivale a `placement: 'advanced'`.
@@ -230,7 +229,7 @@ Por padrão, a lista cresce com os resultados e a rolagem acontece na janela ou
230
229
  hospeda. Quando a coleção é a região principal de uma tela de altura fixa, a barra de filtros e o
231
230
  rodapé podem continuar visíveis enquanto só os resultados rolam. Para isso, declare
232
231
  `layout="fill"` na [`Page`](/ui/page) e componha a lista com `PageBody` e
233
- [`Content`](/ui/content), como no exemplo abaixo. Role a tabela para ver o cabeçalho fixo.
232
+ [`Surface`](/ui/surface), como no exemplo abaixo. Role a tabela para ver o cabeçalho fixo.
234
233
 
235
234
  ```tsx preview col
236
235
  render(
@@ -238,17 +237,17 @@ render(
238
237
  <div className="h-80 w-full overflow-x-auto overflow-y-hidden rounded-lg border border-border">
239
238
  <Page layout="fill">
240
239
  <PageBody>
241
- <Content level={1} variant="page">
242
- <ContentHeader>
243
- <ContentTitle>Workspaces</ContentTitle>
244
- <ContentActions>
240
+ <Surface as="section" level={1}>
241
+ <SurfaceHeader>
242
+ <SurfaceTitle>Workspaces</SurfaceTitle>
243
+ <SurfaceActions>
245
244
  <Button>Criar workspace</Button>
246
- </ContentActions>
247
- </ContentHeader>
248
- <ContentBody>
245
+ </SurfaceActions>
246
+ </SurfaceHeader>
247
+ <SurfaceBody>
249
248
  <ActionList action={docWorkspaceList} input={{}} />
250
- </ContentBody>
251
- </Content>
249
+ </SurfaceBody>
250
+ </Surface>
252
251
  </PageBody>
253
252
  </Page>
254
253
  </div>
@@ -257,7 +256,7 @@ render(
257
256
  ```
258
257
 
259
258
  A página limita a própria altura à do contêiner que a hospeda, normalmente o shell, e `PageBody`,
260
- `Content` e `ContentBody` repassam essa altura até a lista, que passa a usar `layout="fill"` sem que
259
+ `Surface` e `SurfaceBody` repassam essa altura até a lista, que passa a usar `layout="fill"` sem que
261
260
  você o declare. Em uma [`Presentation`](/ui/presentation) de lista, basta `layout="fill"` no
262
261
  próprio `Presentation`. Só as regiões que contêm a lista mudam: uma seção irmã, como um resumo acima
263
262
  da coleção, mantém a altura do próprio conteúdo. Os demais filhos de cada região que repassa a
@@ -270,16 +269,16 @@ rola, sobre a cor da superfície e com uma linha inferior, e a moldura dos estad
270
269
  erro e vazio ocupa a altura disponível. Em outras visualizações e em `children`, a rolagem interna
271
270
  continua, mas não existe cabeçalho fixo.
272
271
 
273
- A lista só assume `fill` sozinha quando é filha direta de `PageBody`, `ContentBody` ou do corpo de
272
+ A lista só assume `fill` sozinha quando é filha direta de `PageBody`, `SurfaceBody` ou do corpo de
274
273
  uma `Presentation`, e a altura só chega até ela se não houver um contêiner próprio entre esses
275
274
  componentes no caminho. Nos demais casos, a decisão é sua:
276
275
 
277
276
  - Para a lista manter a altura dos resultados, como em uma seção secundária cuja rolagem pertence ao
278
277
  `PageBody`, declare `layout="flow"`.
279
- - Um contêiner próprio entre `ContentBody` e a lista, como um `TabsContent`, não repassa a altura, e
278
+ - Um contêiner próprio entre `SurfaceBody` e a lista, como um `TabsPanel`, não repassa a altura, e
280
279
  a lista volta a `flow`. Declare `layout="fill"` na lista e dê ao contêiner as classes
281
280
  `flex min-h-0 flex-1 flex-col`.
282
- - Dentro de [`Card`](/ui/card), [`Dialog`](/ui/dialog), [`Drawer`](/ui/drawer) ou
281
+ - Dentro de [`Surface`](/ui/surface), [`Dialog`](/ui/dialog), [`Drawer`](/ui/drawer) ou
283
282
  [`Pane`](/ui/pane), a lista não herda o `fill` da página: a altura do corpo pertence à
284
283
  superfície.
285
284
 
@@ -336,17 +335,20 @@ render(
336
335
  render: (workspaces: Workspace[]) => (
337
336
  <div className="grid grid-cols-2 gap-3">
338
337
  {workspaces.map((w) => (
339
- <Card key={w.id}>
340
- <CardHeader>
341
- <CardTitle>{w.name}</CardTitle>
342
- <CardDescription>
338
+ <Surface framed elevation="raised" key={w.id}>
339
+ <SurfaceHeader>
340
+ <SurfaceTitle>{w.name}</SurfaceTitle>
341
+ <SurfaceDescription>
343
342
  {w.agents === 1 ? "1 agente" : `${w.agents} agentes`}
344
- </CardDescription>
345
- <CardAction>
346
- <DictionaryValue dict={docWorkspaceStatus} value={w.status} />
347
- </CardAction>
348
- </CardHeader>
349
- </Card>
343
+ </SurfaceDescription>
344
+ <SurfaceActions>
345
+ <DictionaryValue
346
+ dict={docWorkspaceStatus}
347
+ value={w.status}
348
+ />
349
+ </SurfaceActions>
350
+ </SurfaceHeader>
351
+ </Surface>
350
352
  ))}
351
353
  </div>
352
354
  ),
@@ -364,6 +366,9 @@ render(
364
366
 
365
367
  - **`depends: ['outroFiltro']`:** desabilita o filtro até que suas dependências tenham valor e o
366
368
  limpa quando uma delas muda.
369
+ - **`presentation: 'toggle'`:** mantém visíveis as opções curtas de um `select` de escolha única;
370
+ o `ToggleGroup` usa `mode="segmented"`, enquanto estado, URL e input da action continuam iguais
371
+ aos do controle padrão. Entradas de dicionário com `icon` apresentam o ícone junto ao rótulo.
367
372
  - **`options: { kind: 'dictionary', ref: 'sessionStatus' }`:** resolve opções e rótulos por um
368
373
  dicionário registrado no `OpusProvider`. `filterOptions` substitui essa fonte quando informado.
369
374
  - **`options: { kind: 'lookup', source: 'x.lookup' }`:** consulta uma action com `{ q }` e os valores
@@ -371,6 +376,13 @@ render(
371
376
 
372
377
  ```tsx
373
378
  filters: {
379
+ channel: {
380
+ label: 'Canal',
381
+ type: 'select',
382
+ presentation: 'toggle',
383
+ allowEmpty: false,
384
+ options: { kind: 'static', items: [...] },
385
+ },
374
386
  project: { label: 'Projeto', type: 'select', options: { kind: 'static', items: [...] } },
375
387
  sprint: {
376
388
  label: 'Sprint',
@@ -433,7 +445,10 @@ render(
433
445
  <ItemDescription>Cliente {w.client}.</ItemDescription>
434
446
  </ItemHeader>
435
447
  <ItemActions>
436
- <DictionaryValue dict={docWorkspaceStatus} value={w.status} />
448
+ <DictionaryValue
449
+ dict={docWorkspaceStatus}
450
+ value={w.status}
451
+ />
437
452
  </ItemActions>
438
453
  </Item>
439
454
  </React.Fragment>
@@ -466,13 +481,13 @@ render(
466
481
 
467
482
  ## Declaração na ListAction
468
483
 
469
- | Chave | O que declara |
470
- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
471
- | `columns` | `{ key, label, type?, sortable?, fit?, hidden?, dateFormat?, dictionary?, empty? }` — a tabela. `hidden` nasce oculta e pode ser exibida pelo seletor de colunas; `dictionary` nomeia o dicionário do provider; `empty` dá o significado da ausência. |
472
- | `filters` | `{ [nome]: { label, type, options?, multiple?, searchable?, allowEmpty?, placement?, section?, depends?, … } }` — a toolbar. `placement` classifica o controle como `inline`, `advanced` ou `external`; filtros avançados com `section` são agrupados no diálogo; `external` mantém estado e input, mas delega a apresentação ao consumidor. `searchable` habilita busca local em `select`; `allowEmpty: false` omite a opção vazia. `depends` desabilita/cascateia; options `kind: 'lookup'` busca em uma action. Valor aplicado entra no input com o mesmo nome. |
473
- | `text` | `{ fields }` — liga a busca; convenção: param `q` no input. Ela aparece depois dos filtros inline e imediatamente antes do acesso aos filtros avançados. A busca participa da contagem de controles inline: com mais de um controle, a barra exibe as labels; quando ela é o único recorte, sua label é omitida. |
474
- | `sort` | `{ fields, default }` — ordenação inicial; header ordenável escreve `sort: 'chave:dir'`. |
475
- | `periods` | `{ value, label, default? }[]` — o controle de período (presets + Personalizado com calendário); `default: true` marca o preset inicial, senão vale o primeiro; materializa em `from`/`to` no input. |
484
+ | Chave | O que declara |
485
+ | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
486
+ | `columns` | `{ key, label, type?, sortable?, fit?, hidden?, dateFormat?, dictionary?, empty? }` — a tabela. `hidden` nasce oculta e pode ser exibida pelo seletor de colunas; `dictionary` nomeia o dicionário do provider; `empty` dá o significado da ausência. |
487
+ | `filters` | `{ [nome]: { label, type, options?, multiple?, presentation?, searchable?, allowEmpty?, placement?, section?, depends?, … } }` — a toolbar. `placement` classifica o controle como `inline`, `advanced` ou `external`; filtros avançados com `section` são agrupados no diálogo; `external` mantém estado e input, mas delega a apresentação ao consumidor. `presentation: 'toggle'` mostra todas as opções curtas de um `select` de escolha única em um `ToggleGroup`; sem a propriedade, o controle continua sendo `Select`. `searchable` habilita busca local em `select`; `allowEmpty: false` omite a opção vazia. `depends` desabilita/cascateia; options `kind: 'lookup'` busca em uma action. Valor aplicado entra no input com o mesmo nome. |
488
+ | `text` | `{ fields }` — liga a busca; convenção: param `q` no input. Ela aparece depois dos filtros inline e imediatamente antes do acesso aos filtros avançados. A busca participa da contagem de controles inline: com mais de um controle, a barra exibe as labels; quando ela é o único recorte, sua label é omitida. |
489
+ | `sort` | `{ fields, default }` — ordenação inicial; header ordenável escreve `sort: 'chave:dir'`. |
490
+ | `periods` | `{ value, label, default? }[]` — o controle de período (presets + Personalizado com calendário); `default: true` marca o preset inicial, senão vale o primeiro; materializa em `from`/`to` no input. |
476
491
 
477
492
  ## useListAction
478
493
 
@@ -484,31 +499,31 @@ chamar a action.
484
499
 
485
500
  ## Propriedades de ActionList
486
501
 
487
- | Propriedade | Tipo | Padrão | Descrição |
488
- | ----------------------- | ----------------------------------------------------------------------------------------------------------- | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
489
- | `action` | `ListActionLike` | | A ListAction do Opus (kind list, output = item). A paginação envia `limit`/`page` e usa o `total` devolvido. |
490
- | `input` | `TInput` | | O escopo base, como `{ workspaceId }`. A toolbar acrescenta busca, filtros e paginação sem sobrescrever essas chaves. |
491
- | `layout` | `'flow' \| 'fill'` | `'fill'` como filha direta de `PageBody`, `ContentBody` ou do corpo da `Presentation` em uma página `fill`; `'flow'` nos demais casos | Em `fill`, ocupa a altura de um ancestral limitado e rola somente a região de resultados; na tabela, o cabeçalho fica fixo. Sem ancestral limitado, equivale a `flow`. |
492
- | `cells` | `Record<string, (item) => ReactNode>` | | Células custom por cima das colunas do contrato (chave = column.key). |
493
- | `filterOptions` | `Record<string, SelectOption[]>` | | Opções de runtime para os filtros select/lookup (chave = nome do filtro). |
494
- | `filterDefaults` | `Record<string, string>` | | Valores de repouso aplicados à consulta; o `X` só aparece quando o filtro diverge deles e retorna a eles. |
495
- | `filterIcons` | `Record<string, ReactNode>` | | Ícones leading dos filtros, indexados pelo nome declarado no contrato. |
496
- | `advancedFilters` | `{ columns?: 1 \| 2 \| 3; sectionIcons?: Record<string, ReactNode>; sectionVariant?: 'plain' \| 'framed' }` | `{ columns: 1, sectionVariant: 'plain' }` | Define as colunas, os ícones e o tratamento dos grupos; o Opus deriva a largura correspondente do diálogo. |
497
- | `periodPosition` | `'before' \| 'after' \| { afterFilter: string }` | `'before'` | Posiciona o período antes, depois ou imediatamente após um filtro inline. |
498
- | `period` | `{ basisFilter: string }` | | Reúne o período e o filtro inline indicado em um único controle. |
499
- | `views` | `Record<string, ActionListView<TItem>>` | | Visualizações alternativas `{ label, icon?, render }`; o seletor alterna entre a tabela e estas. |
500
- | `beforeContent` | `ReactNode` | | Conteúdo entre a toolbar e o corpo, como um resumo que reage ao recorte atual. |
501
- | `columns` | `ActionListColumn<TItem>[]` | | Tabela explícita, que substitui as colunas declaradas no contrato. |
502
- | `children` | `(items, refetch) => ReactNode` | | Modo composição: layout livre; a toolbar segue. Tem precedência sobre columns. |
503
- | `batch` | `ActionListBatchAction[]` | | Ações em lote: `{ label, can?, run, confirm?: { title, message? }, destructive? }` — liga a multi-seleção. |
504
- | `rowId` | `(item) => string` | `item.id` | Identidade da linha para seleção. |
505
- | `pageSize` | `number` | `50` | Itens por página padrão (vira `limit`/`page` no input; handler devolve `total`). O usuário troca no popover de exibição. |
506
- | `state / onStateChange` | `ActionListState / (next) => void` | interno | Estado da toolbar controlado — para quem embala sincronizar com a URL. |
507
- | `empty` | `(items) => boolean` | `items.length === 0` | Sobrepõe o vazio derivado, por exemplo quando um formulário inline aberto conta como conteúdo. |
508
- | `loading` | `boolean` | | Carga extra agregada à da consulta, como uma query irmã de que os `children` dependem. |
509
- | `emptyMessage` | `string` | `'Nenhum resultado.'` | Frase do estado vazio (o `Empty` do `DataState`). |
510
- | `errorMessage` | `string` | `'Não foi possível carregar.'` | Título do aviso de erro; a ação de tentar de novo refaz a consulta. |
511
- | `retryLabel` | `string` | `'Tentar de novo'` | Nome acessível e tooltip da ação de recuperação. |
512
- | `onRowClick` | `(item: TItem) => void` | | Clique na linha (ex.: navegar para o detalhe) — só na tabela. |
513
- | `toolbarActions` | `ReactNode` | | Operações ligadas ao recorte atual, no fim da barra e separadas do grupo de controles. Os nós são renderizados como recebidos: nome acessível e tooltip de uma ação somente com ícone ficam com o consumidor. |
514
- | `rowActions` | `(item: TItem) => ReactNode` | | Ações por linha (coluna final, à direita). A partir de duas, entram em um grupo com intervalo compacto; uma só fica solta, sem grupo. Cliques ali não disparam o `onRowClick`. |
502
+ | Propriedade | Tipo | Padrão | Descrição |
503
+ | ----------------------- | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
504
+ | `action` | `ListActionLike` | | A ListAction do Opus (kind list, output = item). A paginação envia `limit`/`page` e usa o `total` devolvido. |
505
+ | `input` | `TInput` | | O escopo base, como `{ workspaceId }`. A toolbar acrescenta busca, filtros e paginação sem sobrescrever essas chaves. |
506
+ | `layout` | `'flow' \| 'fill'` | `'fill'` como filha direta de `PageBody`, `SurfaceBody` ou do corpo da `Presentation` em uma página `fill`; `'flow'` nos demais casos | Em `fill`, ocupa a altura de um ancestral limitado e rola somente a região de resultados; na tabela, o cabeçalho fica fixo. Sem ancestral limitado, equivale a `flow`. |
507
+ | `cells` | `Record<string, (item) => ReactNode>` | | Células custom por cima das colunas do contrato (chave = column.key). |
508
+ | `filterOptions` | `Record<string, SelectOption[]>` | | Opções de runtime para os filtros select/lookup (chave = nome do filtro). |
509
+ | `filterDefaults` | `Record<string, string>` | | Valores de repouso aplicados à consulta; o `X` só aparece quando o filtro diverge deles e retorna a eles. |
510
+ | `filterIcons` | `Record<string, ReactNode>` | | Ícones leading dos filtros, indexados pelo nome declarado no contrato. |
511
+ | `advancedFilters` | `{ columns?: 1 \| 2 \| 3; sectionIcons?: Record<string, ReactNode>; sectionVariant?: 'plain' \| 'framed' }` | `{ columns: 1, sectionVariant: 'plain' }` | Define as colunas, os ícones e o tratamento dos grupos; o Opus deriva a largura correspondente do diálogo. |
512
+ | `periodPosition` | `'before' \| 'after' \| { afterFilter: string }` | `'before'` | Posiciona o período antes, depois ou imediatamente após um filtro inline. |
513
+ | `period` | `{ basisFilter: string }` | | Reúne o período e o filtro inline indicado em um único controle. |
514
+ | `views` | `Record<string, ActionListView<TItem>>` | | Visualizações alternativas `{ label, icon?, render }`; o seletor alterna entre a tabela e estas. |
515
+ | `beforeContent` | `ReactNode` | | Conteúdo entre a toolbar e o corpo, como um resumo que reage ao recorte atual. |
516
+ | `columns` | `ActionListColumn<TItem>[]` | | Tabela explícita, que substitui as colunas declaradas no contrato. |
517
+ | `children` | `(items, refetch) => ReactNode` | | Modo composição: layout livre; a toolbar segue. Tem precedência sobre columns. |
518
+ | `batch` | `ActionListBatchAction[]` | | Ações em lote: `{ label, can?, run, confirm?: { title, message? }, destructive? }` — liga a multi-seleção. |
519
+ | `rowId` | `(item) => string` | `item.id` | Identidade da linha para seleção. |
520
+ | `pageSize` | `number` | `50` | Itens por página padrão (vira `limit`/`page` no input; handler devolve `total`). O usuário troca no popover de exibição. |
521
+ | `state / onStateChange` | `ActionListState / (next) => void` | interno | Estado da toolbar controlado — para quem embala sincronizar com a URL. |
522
+ | `empty` | `(items) => boolean` | `items.length === 0` | Sobrepõe o vazio derivado, por exemplo quando um formulário inline aberto conta como conteúdo. |
523
+ | `loading` | `boolean` | | Carga extra agregada à da consulta, como uma query irmã de que os `children` dependem. |
524
+ | `emptyMessage` | `string` | `'Nenhum resultado.'` | Frase do estado vazio (o `Empty` do `DataState`). |
525
+ | `errorMessage` | `string` | `'Não foi possível carregar.'` | Título do aviso de erro; a ação de tentar de novo refaz a consulta. |
526
+ | `retryLabel` | `string` | `'Tentar de novo'` | Nome acessível e tooltip da ação de recuperação. |
527
+ | `onRowClick` | `(item: TItem) => void` | | Clique na linha (ex.: navegar para o detalhe) — só na tabela. |
528
+ | `toolbarActions` | `ReactNode` | | Operações ligadas ao recorte atual, no fim da barra e separadas do grupo de controles. Os nós são renderizados como recebidos: nome acessível e tooltip de uma ação somente com ícone ficam com o consumidor. |
529
+ | `rowActions` | `(item: TItem) => ReactNode` | | Ações por linha (coluna final, à direita). A partir de duas, entram em um grupo com intervalo compacto; uma só fica solta, sem grupo. Cliques ali não disparam o `onRowClick`. |
@@ -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. |
@@ -120,8 +120,8 @@ nome de uma action não começa pelo caminho do domínio, como `workspace.` ou `
120
120
  `subdomains` aceita outros domínios e forma esse caminho.
121
121
 
122
122
  `actions` aceita um mapa, uma lista ou o resultado de `import * as actions`. Um domínio também pode
123
- registrar só o contrato, sem handler: no modo design, descrito abaixo, o `mockHandler` responde;
124
- fora dele, a execução falha por falta de handler.
123
+ registrar só o contrato, sem handler: o plugin do modo design liga uma resposta gerada ou um
124
+ cenário; fora dele, a execução falha por falta de handler.
125
125
 
126
126
  ## Ordem canônica dos campos
127
127
 
@@ -134,7 +134,7 @@ name, kind
134
134
  → label, title, summary, description, icon, color, messages, tags, examples, errors
135
135
  → input, output, effect, personalData
136
136
  → public, requires, authorize, loads
137
- → handler, mockHandler
137
+ → handler
138
138
  → audit, confirm, invalidates, rateLimit, idempotency
139
139
  → ai, automatable, internal
140
140
  ```
@@ -228,10 +228,9 @@ O Opus não decide onde os cenários ficam nem se entram no repositório. Eles n
228
228
  aplicação: podem ficar numa pasta ignorada pelo git e ser escritos durante a sessão de design,
229
229
  quando forem necessários.
230
230
 
231
- `mockHandler` está obsoleto desde a 22.1.0 e sai na próxima major. Enquanto existir, ele ainda
232
- responde no modo design, abaixo do cenário e acima da resposta gerada, e o `opus check` conta
233
- quantas actions ainda o declaram. Para migrar, apague o `mockHandler`; se a resposta dele importar
234
- para o design, transforme-a em cenário.
231
+ Dados de design não pertencem ao contrato. Quando uma resposta específica importar para a revisão
232
+ da interface, declare um cenário com `defineDesignScenario`; para os demais casos, preserve apenas
233
+ o contrato e deixe o plugin gerar a resposta pelo schema.
235
234
 
236
235
  O plugin `opusDesign()`, de `@softize/opus/vite`, monta o runtime do Opus dentro do dev server e
237
236
  responde `/api` no próprio processo. No modo design, qualquer `server.proxy` para um backend externo
@@ -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. |