@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.
- package/CHANGELOG.md +120 -1
- package/bin/cli.mjs +3 -9
- package/bin/lib/check.mjs +242 -49
- package/bin/lib/copy.mjs +17 -35
- package/bin/lib/gen-runner.mjs +2 -0
- package/bin/lib/gen-stubs.mjs +3 -1
- package/docs/adr/0005-structural-surfaces-share-an-explicit-anatomy.md +1 -1
- package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +6 -0
- package/docs/adr/0032-tab-baseline-respects-the-container-inset.md +16 -12
- package/docs/adr/0034-tabs-can-own-a-frame-without-owning-a-surface.md +8 -6
- package/docs/adr/0036-tabs-declares-trigger-distribution.md +11 -8
- package/docs/adr/0037-design-data-lives-outside-the-contract.md +1 -1
- package/docs/adr/0038-content-level-defines-primary-presentation.md +62 -0
- package/docs/adr/0039-filter-presentation-is-independent-from-filter-type.md +62 -0
- package/docs/adr/0040-tabs-use-a-single-navigation-presentation.md +59 -0
- package/docs/adr/0042-toggle-group-shares-the-grouped-control-grammar.md +79 -0
- package/docs/adr/0043-neutral-surfaces-and-interactions-use-opaque-role-tokens.md +73 -0
- package/docs/adr/0044-neutral-appearance-derives-from-bounded-axes-per-mode.md +133 -0
- package/docs/adr/0045-surface-replaces-content-and-card.md +230 -0
- package/docs/adr/0046-structural-body-owns-content-rhythm.md +103 -0
- package/docs/adr/0047-pane-is-only-a-split-layout-unit.md +71 -0
- package/docs/adr/0048-modal-intro-separates-content-from-chrome.md +34 -0
- package/docs/code-style.md +1 -1
- package/package.json +5 -1
- package/registry/skills/build-opus-ui/SKILL.md +11 -11
- package/registry/skills/build-opus-ui/references/evaluations.md +10 -9
- package/registry/skills/build-opus-ui/references/ui-patterns.md +17 -16
- package/registry/skills/create-opus-action/references/contract-and-binding.md +3 -3
- package/registry/templates/app/src/App.tsx +24 -12
- package/src/core/contracts.ts +1 -1
- package/src/core/runtime.ts +4 -37
- package/src/core/types.ts +5 -23
- package/src/design/index.ts +14 -3
- package/src/testing/index.ts +6 -26
- package/src/ui/appearance.ts +149 -0
- package/src/ui/components/patterns/action-form-card.tsx +46 -33
- package/src/ui/components/patterns/action-list-dialog.tsx +2 -4
- package/src/ui/components/patterns/confirm.tsx +3 -1
- package/src/ui/components/patterns/fill-region.ts +8 -7
- package/src/ui/components/patterns/form-dialog.tsx +1 -3
- package/src/ui/components/patterns/form.tsx +8 -7
- package/src/ui/components/patterns/list.tsx +64 -14
- package/src/ui/components/patterns/page.tsx +15 -11
- package/src/ui/components/patterns/pane.tsx +9 -240
- package/src/ui/components/patterns/presentation.tsx +14 -14
- package/src/ui/components/patterns/sidebar.tsx +7 -17
- package/src/ui/components/patterns/split.tsx +111 -59
- package/src/ui/components/patterns/{surface-header.tsx → structural-header.tsx} +50 -19
- package/src/ui/components/patterns/structural-media.tsx +26 -0
- package/src/ui/components/patterns/surface-anatomy.ts +71 -61
- package/src/ui/components/patterns/surface-assistant.tsx +2 -4
- package/src/ui/components/patterns/surface.tsx +620 -0
- package/src/ui/components/patterns/trigger.tsx +11 -9
- package/src/ui/components/primitives/ask.tsx +155 -83
- package/src/ui/components/primitives/button.tsx +11 -9
- package/src/ui/components/primitives/checkbox.tsx +8 -8
- package/src/ui/components/primitives/command.tsx +1 -1
- package/src/ui/components/primitives/composer.tsx +47 -45
- package/src/ui/components/primitives/detail.tsx +9 -2
- package/src/ui/components/primitives/dialog.tsx +60 -26
- package/src/ui/components/primitives/drawer.tsx +58 -12
- package/src/ui/components/primitives/icon-picker.tsx +93 -70
- package/src/ui/components/primitives/input-group.tsx +35 -28
- package/src/ui/components/primitives/input-otp.tsx +16 -16
- package/src/ui/components/primitives/input.tsx +20 -10
- package/src/ui/components/primitives/item.tsx +9 -12
- package/src/ui/components/primitives/menu.tsx +44 -65
- package/src/ui/components/primitives/metric-card.tsx +75 -59
- package/src/ui/components/primitives/radio-group.tsx +9 -9
- package/src/ui/components/primitives/select.tsx +344 -209
- package/src/ui/components/primitives/switch.tsx +10 -10
- package/src/ui/components/primitives/table.tsx +29 -24
- package/src/ui/components/primitives/tabs.tsx +58 -88
- package/src/ui/components/primitives/textarea.tsx +6 -6
- package/src/ui/components/primitives/toggle-group-context.ts +24 -0
- package/src/ui/components/primitives/toggle-group.tsx +116 -74
- package/src/ui/components/primitives/toggle.tsx +82 -16
- package/src/ui/docs/DocBrowser.tsx +77 -69
- package/src/ui/docs/content/action-form-card.md +9 -9
- package/src/ui/docs/content/action-list.md +85 -70
- package/src/ui/docs/content/action-view.md +33 -18
- package/src/ui/docs/content/actions.md +6 -7
- package/src/ui/docs/content/button.md +1 -1
- package/src/ui/docs/content/communication.md +1 -1
- package/src/ui/docs/content/customization.md +64 -11
- package/src/ui/docs/content/detail.md +8 -6
- package/src/ui/docs/content/dialog.md +100 -58
- package/src/ui/docs/content/drawer.md +63 -19
- package/src/ui/docs/content/field.md +14 -15
- package/src/ui/docs/content/icon-tile.md +14 -13
- package/src/ui/docs/content/page.md +34 -26
- package/src/ui/docs/content/pane.md +41 -290
- package/src/ui/docs/content/presentation.md +3 -3
- package/src/ui/docs/content/semantic-context.md +0 -1
- package/src/ui/docs/content/separator.md +6 -6
- package/src/ui/docs/content/sidebar.md +68 -266
- package/src/ui/docs/content/skeleton.md +20 -18
- package/src/ui/docs/content/split.md +14 -14
- package/src/ui/docs/content/surface.md +513 -0
- package/src/ui/docs/content/tabs.md +82 -83
- package/src/ui/docs/content/testing.md +2 -3
- package/src/ui/docs/content/toggle.md +120 -63
- package/src/ui/docs/content/tokens.md +124 -40
- package/src/ui/docs/registry.tsx +4 -6
- package/src/ui/drivers/react.tsx +4 -3
- package/src/ui/index.ts +30 -27
- package/src/ui/meta.ts +15 -27
- package/src/ui/react.tsx +42 -46
- package/src/ui/theme.css +311 -64
- package/src/vite/design.ts +23 -21
- package/src/ui/components/patterns/content-header.tsx +0 -532
- package/src/ui/components/patterns/surface-media.tsx +0 -69
- package/src/ui/components/primitives/card.tsx +0 -191
- package/src/ui/docs/content/card.md +0 -292
- 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 [`
|
|
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
|
-
<
|
|
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
|
-
</
|
|
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 `
|
|
99
|
-
nativa. O ícone ocupa a mídia do grupo
|
|
100
|
-
glifo
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
[`
|
|
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
|
-
<
|
|
242
|
-
<
|
|
243
|
-
<
|
|
244
|
-
<
|
|
240
|
+
<Surface as="section" level={1}>
|
|
241
|
+
<SurfaceHeader>
|
|
242
|
+
<SurfaceTitle>Workspaces</SurfaceTitle>
|
|
243
|
+
<SurfaceActions>
|
|
245
244
|
<Button>Criar workspace</Button>
|
|
246
|
-
</
|
|
247
|
-
</
|
|
248
|
-
<
|
|
245
|
+
</SurfaceActions>
|
|
246
|
+
</SurfaceHeader>
|
|
247
|
+
<SurfaceBody>
|
|
249
248
|
<ActionList action={docWorkspaceList} input={{}} />
|
|
250
|
-
</
|
|
251
|
-
</
|
|
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
|
-
`
|
|
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`, `
|
|
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 `
|
|
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 [`
|
|
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
|
-
<
|
|
340
|
-
<
|
|
341
|
-
<
|
|
342
|
-
<
|
|
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
|
-
</
|
|
345
|
-
<
|
|
346
|
-
<DictionaryValue
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
|
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
|
|
488
|
-
| ----------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
489
|
-
| `action` | `ListActionLike` |
|
|
490
|
-
| `input` | `TInput` |
|
|
491
|
-
| `layout` | `'flow' \| 'fill'` | `'fill'` como filha direta de `PageBody`, `
|
|
492
|
-
| `cells` | `Record<string, (item) => ReactNode>` |
|
|
493
|
-
| `filterOptions` | `Record<string, SelectOption[]>` |
|
|
494
|
-
| `filterDefaults` | `Record<string, string>` |
|
|
495
|
-
| `filterIcons` | `Record<string, ReactNode>` |
|
|
496
|
-
| `advancedFilters` | `{ columns?: 1 \| 2 \| 3; sectionIcons?: Record<string, ReactNode>; sectionVariant?: 'plain' \| 'framed' }` | `{ columns: 1, sectionVariant: 'plain' }`
|
|
497
|
-
| `periodPosition` | `'before' \| 'after' \| { afterFilter: string }` | `'before'`
|
|
498
|
-
| `period` | `{ basisFilter: string }` |
|
|
499
|
-
| `views` | `Record<string, ActionListView<TItem>>` |
|
|
500
|
-
| `beforeContent` | `ReactNode` |
|
|
501
|
-
| `columns` | `ActionListColumn<TItem>[]` |
|
|
502
|
-
| `children` | `(items, refetch) => ReactNode` |
|
|
503
|
-
| `batch` | `ActionListBatchAction[]` |
|
|
504
|
-
| `rowId` | `(item) => string` | `item.id`
|
|
505
|
-
| `pageSize` | `number` | `50`
|
|
506
|
-
| `state / onStateChange` | `ActionListState / (next) => void` | interno
|
|
507
|
-
| `empty` | `(items) => boolean` | `items.length === 0`
|
|
508
|
-
| `loading` | `boolean` |
|
|
509
|
-
| `emptyMessage` | `string` | `'Nenhum resultado.'`
|
|
510
|
-
| `errorMessage` | `string` | `'Não foi possível carregar.'`
|
|
511
|
-
| `retryLabel` | `string` | `'Tentar de novo'`
|
|
512
|
-
| `onRowClick` | `(item: TItem) => void` |
|
|
513
|
-
| `toolbarActions` | `ReactNode` |
|
|
514
|
-
| `rowActions` | `(item: TItem) => ReactNode` |
|
|
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(
|
|
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(
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
<
|
|
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={
|
|
37
|
+
value={
|
|
38
|
+
<DictionaryValue
|
|
39
|
+
dict={docWorkspaceStatus}
|
|
40
|
+
value={ws.status}
|
|
41
|
+
/>
|
|
42
|
+
}
|
|
28
43
|
/>
|
|
29
44
|
</DetailGroup>
|
|
30
45
|
)}
|
|
31
46
|
</ActionView>
|
|
32
|
-
</
|
|
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
|
|
51
|
-
|
|
52
|
-
| `action`
|
|
53
|
-
| `input`
|
|
54
|
-
| `children`
|
|
55
|
-
| `render`
|
|
56
|
-
| `loading`
|
|
57
|
-
| `empty`
|
|
58
|
-
| `emptyMessage` | `string`
|
|
59
|
-
| `error`
|
|
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:
|
|
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
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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 [`
|
|
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`, `
|
|
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
|
|
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;
|
|
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
|
-
<
|
|
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 `
|
|
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()}>
|
|
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
|
-
<
|
|
66
|
-
<
|
|
67
|
-
<
|
|
68
|
-
</
|
|
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
|
|
132
|
-
|
|
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
|
-
##
|
|
47
|
+
## Elevação, moldura e divisórias internas
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
|
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. |
|