@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
package/bin/lib/copy.mjs
CHANGED
|
@@ -94,12 +94,8 @@ const JSX_CHILD_ROLES = new Map([
|
|
|
94
94
|
['BreadcrumbLink', 'breadcrumb'],
|
|
95
95
|
['BreadcrumbPage', 'breadcrumb'],
|
|
96
96
|
['Button', 'button'],
|
|
97
|
-
['CardDescription', 'description'],
|
|
98
|
-
['CardTitle', 'title'],
|
|
99
97
|
['CommandEmpty', 'empty-state'],
|
|
100
98
|
['CommandItem', 'menu-item'],
|
|
101
|
-
['ContentDescription', 'description'],
|
|
102
|
-
['ContentTitle', 'title'],
|
|
103
99
|
['DialogDescription', 'dialog-body'],
|
|
104
100
|
['DialogTitle', 'title'],
|
|
105
101
|
['DrawerDescription', 'dialog-body'],
|
|
@@ -124,11 +120,11 @@ const JSX_CHILD_ROLES = new Map([
|
|
|
124
120
|
// comando. Classificado como `button`, a política universal cobraria verbo de ação.
|
|
125
121
|
['PageBack', 'breadcrumb'],
|
|
126
122
|
['PageTitle', 'title'],
|
|
127
|
-
['
|
|
128
|
-
['
|
|
123
|
+
['SurfaceDescription', 'description'],
|
|
124
|
+
['SurfaceTitle', 'title'],
|
|
129
125
|
['TableCaption', 'description'],
|
|
130
126
|
['TableHead', 'heading'],
|
|
131
|
-
['
|
|
127
|
+
['TabsListItem', 'tab'],
|
|
132
128
|
// A Base não tem papel `tooltip`. O tooltip nomeia um controle icon-only, logo é um
|
|
133
129
|
// fragmento (sem ponto final), não uma frase; `label` é o papel que reflete isso.
|
|
134
130
|
['TooltipContent', 'label'],
|
|
@@ -222,20 +218,6 @@ const JSX_PROP_ROLES = new Map([
|
|
|
222
218
|
]),
|
|
223
219
|
],
|
|
224
220
|
['CommandInput', new Map([['placeholder', 'placeholder']])],
|
|
225
|
-
[
|
|
226
|
-
'Content',
|
|
227
|
-
new Map([
|
|
228
|
-
['title', 'title'],
|
|
229
|
-
['description', 'description'],
|
|
230
|
-
]),
|
|
231
|
-
],
|
|
232
|
-
[
|
|
233
|
-
'ContentHeader',
|
|
234
|
-
new Map([
|
|
235
|
-
['title', 'title'],
|
|
236
|
-
['description', 'description'],
|
|
237
|
-
]),
|
|
238
|
-
],
|
|
239
221
|
[
|
|
240
222
|
'DataState',
|
|
241
223
|
new Map([
|
|
@@ -290,6 +272,13 @@ const JSX_PROP_ROLES = new Map([
|
|
|
290
272
|
['emptyText', 'empty-state'],
|
|
291
273
|
]),
|
|
292
274
|
],
|
|
275
|
+
[
|
|
276
|
+
'Surface',
|
|
277
|
+
new Map([
|
|
278
|
+
['title', 'title'],
|
|
279
|
+
['description', 'description'],
|
|
280
|
+
]),
|
|
281
|
+
],
|
|
293
282
|
['Textarea', new Map([['placeholder', 'placeholder']])],
|
|
294
283
|
])
|
|
295
284
|
|
|
@@ -363,20 +352,6 @@ const JSX_PROP_CLASS = new Map([
|
|
|
363
352
|
]),
|
|
364
353
|
],
|
|
365
354
|
['CommandInput', new Map([['placeholder', 'className']])],
|
|
366
|
-
[
|
|
367
|
-
'Content',
|
|
368
|
-
new Map([
|
|
369
|
-
['title', 'className'],
|
|
370
|
-
['description', 'className'],
|
|
371
|
-
]),
|
|
372
|
-
],
|
|
373
|
-
[
|
|
374
|
-
'ContentHeader',
|
|
375
|
-
new Map([
|
|
376
|
-
['title', 'className'],
|
|
377
|
-
['description', 'className'],
|
|
378
|
-
]),
|
|
379
|
-
],
|
|
380
355
|
['Input', new Map([['placeholder', 'className']])],
|
|
381
356
|
['InputGroupInput', new Map([['placeholder', 'className']])],
|
|
382
357
|
['InputGroupTextarea', new Map([['placeholder', 'className']])],
|
|
@@ -394,6 +369,13 @@ const JSX_PROP_CLASS = new Map([
|
|
|
394
369
|
]),
|
|
395
370
|
],
|
|
396
371
|
['Select', new Map([['placeholder', 'className']])],
|
|
372
|
+
[
|
|
373
|
+
'Surface',
|
|
374
|
+
new Map([
|
|
375
|
+
['title', 'className'],
|
|
376
|
+
['description', 'className'],
|
|
377
|
+
]),
|
|
378
|
+
],
|
|
397
379
|
['Textarea', new Map([['placeholder', 'className']])],
|
|
398
380
|
])
|
|
399
381
|
|
package/bin/lib/gen-runner.mjs
CHANGED
|
@@ -466,6 +466,8 @@ function serializeFilters(filters) {
|
|
|
466
466
|
placeholder: spec.placeholder ?? null,
|
|
467
467
|
type: spec.type ?? null,
|
|
468
468
|
multiple: spec.multiple === true,
|
|
469
|
+
presentation: spec.presentation ?? null,
|
|
470
|
+
allowEmpty: spec.allowEmpty ?? null,
|
|
469
471
|
mode: spec.mode ?? null,
|
|
470
472
|
operators: Array.isArray(spec.operators) ? spec.operators : null,
|
|
471
473
|
path: spec.path ?? null,
|
package/bin/lib/gen-stubs.mjs
CHANGED
|
@@ -182,7 +182,9 @@ function stripNullsFromFields(record) {
|
|
|
182
182
|
for (const [k, v] of Object.entries(spec)) {
|
|
183
183
|
if (v === null || v === undefined) continue
|
|
184
184
|
if (k === 'hasShowWhen' || k === 'hasRequireWhen') continue
|
|
185
|
-
|
|
185
|
+
// `false` costuma ser o default implícito e não precisa viajar. `allowEmpty` é a
|
|
186
|
+
// exceção: seu default é aceitar vazio, então `false` altera o comportamento.
|
|
187
|
+
if (typeof v === 'boolean' && v === false && k !== 'allowEmpty') continue
|
|
186
188
|
clean[k] = v
|
|
187
189
|
}
|
|
188
190
|
out[name] = clean
|
|
@@ -62,7 +62,7 @@ por propriedades continua disponível temporariamente dentro de `Content`, mas a
|
|
|
62
62
|
explícita usa os slots da família.
|
|
63
63
|
|
|
64
64
|
`Content` técnico mantém seu nome quando representa o contêiner montado por uma primitiva ou um
|
|
65
|
-
painel controlado, como `DialogContent`, `PopoverContent`, `
|
|
65
|
+
painel controlado, como `DialogContent`, `PopoverContent`, `TabsPanel` e `AccordionContent`.
|
|
66
66
|
`CarouselContent` também permanece porque representa o trilho técnico da primitiva, não o body de
|
|
67
67
|
uma superfície estrutural. No `Empty`, a antiga região `EmptyContent` não é um body: como contém
|
|
68
68
|
somente comandos, seu nome canônico passa a ser `EmptyActions`.
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# ADR 0023 — Superfícies compartilham uma anatomia e dois layouts
|
|
2
2
|
|
|
3
|
+
> **Atualização em 2026-10-01:** a ADR 0046 torna o body responsável pelo ritmo entre seus filhos
|
|
4
|
+
> diretos e dá ao conteúdo um respiro vertical maior que o das faixas; “regiões simétricas” abaixo
|
|
5
|
+
> descreve a implementação original. A ADR 0047 também remove `Pane` desta família: ele passa a ser
|
|
6
|
+
> somente a unidade de layout de `Split`, enquanto `Surface`, `Dialog` e `Drawer` preservam a
|
|
7
|
+
> anatomia.
|
|
8
|
+
|
|
3
9
|
- **Status:** aceita.
|
|
4
10
|
- **Data:** 2026-09-23.
|
|
5
11
|
- **Complementa:** ADRs 0005, 0014, 0018, 0019 e 0021.
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
- **Status:** aceita.
|
|
4
4
|
- **Data:** 2026-09-28.
|
|
5
|
-
- **
|
|
5
|
+
- **Relacionadas:** ADR 0023, quanto ao alinhamento horizontal dentro de superfícies; ADR 0040,
|
|
6
|
+
que torna a linha a única apresentação de `TabsList`.
|
|
6
7
|
|
|
7
8
|
## Contexto
|
|
8
9
|
|
|
@@ -23,14 +24,16 @@ continua pertencendo à anatomia da superfície, e Tabs apenas ocupa a largura q
|
|
|
23
24
|
|
|
24
25
|
### Emenda de 2026-09-28 — a aba combina uma área de botão e o indicador
|
|
25
26
|
|
|
26
|
-
Na
|
|
27
|
-
interativos e a mesma altura de um Button do tamanho correspondente, e o
|
|
28
|
-
linha-base da lista. A área interna permanece transparente e recebe fundo
|
|
29
|
-
ativa se distingue pelo indicador primário. Um respiro de `0.375rem` separa
|
|
30
|
-
comum; dentro de `Tabs framed`, ele aumenta para `0.5rem` e acompanha o
|
|
27
|
+
Na apresentação horizontal que então se chamava `line`, o gatilho mantém duas camadas: uma área
|
|
28
|
+
interna com os estados interativos e a mesma altura de um Button do tamanho correspondente, e o
|
|
29
|
+
indicador ancorado na linha-base da lista. A área interna permanece transparente e recebe fundo
|
|
30
|
+
apenas no hover; a aba ativa se distingue pelo indicador primário. Um respiro de `0.375rem` separa
|
|
31
|
+
as duas camadas no uso comum; dentro de `Tabs framed`, ele aumenta para `0.5rem` e acompanha o
|
|
32
|
+
padding superior da moldura.
|
|
31
33
|
|
|
32
|
-
Essa apresentação pertence ao Opus: consumidores
|
|
33
|
-
|
|
34
|
+
Essa apresentação pertence ao Opus: consumidores não repetem classes de fundo, raio, padding ou
|
|
35
|
+
interação para obter o estado selecionado. Desde a ADR 0040, a linha é a única apresentação de
|
|
36
|
+
`TabsList` e `variant` não faz mais parte da API.
|
|
34
37
|
|
|
35
38
|
## Consequências
|
|
36
39
|
|
|
@@ -38,7 +41,7 @@ de fundo, raio, padding ou interação para obter o estado selecionado.
|
|
|
38
41
|
- A seleção permanece indicada pela linha inferior, sem manter um segundo fundo persistente.
|
|
39
42
|
- Drawer e Dialog não mantêm uma integração implícita com Tabs.
|
|
40
43
|
- Um consumidor que realmente precise de uma linha até as bordas deve compor essa apresentação de
|
|
41
|
-
forma explícita, pois ela não
|
|
44
|
+
forma explícita, pois ela não faz parte do comportamento canônico de `TabsList`.
|
|
42
45
|
|
|
43
46
|
## Alternativas descartadas
|
|
44
47
|
|
|
@@ -54,6 +57,7 @@ sem consumidor conhecido. A composição explícita é suficiente caso essa nece
|
|
|
54
57
|
|
|
55
58
|
## Verificação
|
|
56
59
|
|
|
57
|
-
O teste da
|
|
58
|
-
dentro de `DrawerBody`. O mesmo teste verifica a separação estrutural entre a superfície
|
|
59
|
-
o indicador da aba ativa. A documentação viva demonstra a
|
|
60
|
+
O teste da apresentação horizontal verifica que a linha usa `inset-inline: 0`, inclusive quando
|
|
61
|
+
Tabs está dentro de `DrawerBody`. O mesmo teste verifica a separação estrutural entre a superfície
|
|
62
|
+
interna e o indicador da aba ativa. A documentação viva demonstra a linha com os limites do
|
|
63
|
+
contêiner.
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
- **Status:** aceita.
|
|
4
4
|
- **Data:** 2026-09-28.
|
|
5
|
-
- **Relacionadas:** ADR 0023, quanto à anatomia das superfícies; ADR 0032, quanto à linha-base das
|
|
5
|
+
- **Relacionadas:** ADR 0023, quanto à anatomia das superfícies; ADR 0032, quanto à linha-base das
|
|
6
|
+
abas; ADR 0040, que substitui a escolha de variante em `TabsList`.
|
|
6
7
|
|
|
7
8
|
## Contexto
|
|
8
9
|
|
|
@@ -23,9 +24,10 @@ Quando ativa, `framed` aplica ao root a moldura estrutural canônica para esse b
|
|
|
23
24
|
`rounded-xl` e recorte do conteúdo. Ela não aplica token de fundo nem de foreground. O conjunto
|
|
24
25
|
herda a superfície do contêiner em que foi composto.
|
|
25
26
|
|
|
26
|
-
Essa propriedade não altera a apresentação de `TabsList`, a seleção, o tamanho nem a orientação.
|
|
27
|
-
consumidor
|
|
28
|
-
|
|
27
|
+
Essa propriedade não altera a apresentação de `TabsList`, a seleção, o tamanho nem a orientação. À
|
|
28
|
+
época desta decisão, o consumidor ainda escolhia a apresentação com `TabsList variant="line"`.
|
|
29
|
+
Essa parte foi substituída pela ADR 0040, que tornou a linha a única apresentação; a independência
|
|
30
|
+
entre moldura e apresentação permanece válida.
|
|
29
31
|
|
|
30
32
|
## Consequências
|
|
31
33
|
|
|
@@ -33,8 +35,8 @@ consumidor continua escolhendo esses comportamentos nas APIs existentes, inclusi
|
|
|
33
35
|
- Em Drawer, Dialog ou Pane, a moldura acompanha o fundo da superfície externa nos temas claro e
|
|
34
36
|
escuro.
|
|
35
37
|
- `Card` continua correto quando as abas realmente precisarem de uma superfície semântica própria.
|
|
36
|
-
- `framed` permanece independente de `TabsList
|
|
37
|
-
|
|
38
|
+
- `framed` permanece independente da apresentação de `TabsList`; desde a ADR 0040, a lista usa
|
|
39
|
+
somente a linha de navegação e não aceita `variant`.
|
|
38
40
|
|
|
39
41
|
## Alternativas descartadas
|
|
40
42
|
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
- **Status:** aceita.
|
|
4
4
|
- **Data:** 2026-09-28.
|
|
5
|
-
- **Relacionadas:** ADR 0032, quanto à composição da
|
|
5
|
+
- **Relacionadas:** ADR 0032, quanto à composição da linha; ADR 0034, quanto à moldura de Tabs; ADR
|
|
6
|
+
0035, quanto ao eixo de distribuição em grupos de controles; ADR 0040, que substitui a escolha de
|
|
7
|
+
variante em `TabsList`.
|
|
6
8
|
|
|
7
9
|
## Contexto
|
|
8
10
|
|
|
@@ -11,10 +13,11 @@ entre os gatilhos. A view de Evento usa quatro abas no cabeçalho de um bloco em
|
|
|
11
13
|
derivadas do texto deixam esse cabeçalho visualmente desequilibrado, embora todas as seções tenham o
|
|
12
14
|
mesmo nível.
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
lista com grid, duplicando uma decisão que já
|
|
17
|
-
`distribution="content" | "equal"`.
|
|
16
|
+
Na API vigente quando esta decisão foi registrada, a variante horizontal `line` forçava cada
|
|
17
|
+
gatilho a usar a largura do próprio conteúdo. Um consumidor que precisasse de larguras iguais teria
|
|
18
|
+
de sobrescrever o `flex` interno ou reconstruir a lista com grid, duplicando uma decisão que já
|
|
19
|
+
existia em `ButtonGroup` como `distribution="content" | "equal"`. A ADR 0040 depois tornou a linha
|
|
20
|
+
a única apresentação e removeu `variant`, sem alterar o problema de distribuição resolvido aqui.
|
|
18
21
|
|
|
19
22
|
## Decisão
|
|
20
23
|
|
|
@@ -30,9 +33,9 @@ largura nem o layout dos painéis de conteúdo.
|
|
|
30
33
|
## Consequências
|
|
31
34
|
|
|
32
35
|
- Consumidores escolhem a mesma linguagem de distribuição usada por `ButtonGroup`.
|
|
33
|
-
- `TabsList
|
|
34
|
-
classes de layout no consumidor.
|
|
35
|
-
- A view de Evento declara `distribution="equal"` no `Tabs` sem alterar cada `
|
|
36
|
+
- `TabsList` continua responsável pela linha e pelos estados visuais; desde a ADR 0040, não aceita
|
|
37
|
+
`variant` nem recebe classes de layout no consumidor.
|
|
38
|
+
- A view de Evento declara `distribution="equal"` no `Tabs` sem alterar cada `TabsListItem`.
|
|
36
39
|
- Usos existentes preservam a distribuição pelo conteúdo.
|
|
37
40
|
|
|
38
41
|
## Alternativas descartadas
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ADR 0037 — Dados do modo design ficam fora do contrato
|
|
2
2
|
|
|
3
|
-
- **Status:** aceita;
|
|
3
|
+
- **Status:** aceita; transição concluída na 23.0.0.
|
|
4
4
|
- **Data:** 2026-09-28.
|
|
5
5
|
- **Relacionadas:** ADR 0002 (seeds estruturados).
|
|
6
6
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# ADR 0038 — O nível do Content define sua apresentação principal
|
|
2
|
+
|
|
3
|
+
- **Status:** aceita.
|
|
4
|
+
- **Data:** 2026-09-29.
|
|
5
|
+
- **Complementa:** ADRs 0016, 0021, 0023 e 0029.
|
|
6
|
+
|
|
7
|
+
## Contexto
|
|
8
|
+
|
|
9
|
+
`Content` aceitava duas declarações independentes para a mesma hierarquia: `level={1}` fazia da
|
|
10
|
+
região o heading principal da `Page`, enquanto `variant="page"` escolhia a escala visual desse
|
|
11
|
+
mesmo papel. Todos os consumidores válidos precisavam repetir as duas propriedades, e o contrato
|
|
12
|
+
também permitia combinações contraditórias como um heading principal com aparência de seção.
|
|
13
|
+
|
|
14
|
+
A duplicação ainda produzia ritmos diferentes entre páginas equivalentes. Quando `PageHeader`
|
|
15
|
+
fornecia o heading, `Page` separava introdução e corpo por `1.5rem`; quando `Content` fornecia o
|
|
16
|
+
heading principal, a raiz mantinha os `0.75rem` próprios de uma seção interna.
|
|
17
|
+
|
|
18
|
+
## Decisão
|
|
19
|
+
|
|
20
|
+
`Content` deixa de expor `variant`. O nível semântico passa a selecionar a única apresentação
|
|
21
|
+
válida para esse componente:
|
|
22
|
+
|
|
23
|
+
- `level={1}` identifica o conteúdo principal, usa a escala de `PageHeader` e separa header e body
|
|
24
|
+
por `1.5rem` no layout contínuo;
|
|
25
|
+
- níveis de 2 a 6 identificam seções internas e preservam a escala e o intervalo de `0.75rem`;
|
|
26
|
+
- `fieldset` permanece uma seção interna: sua legenda não participa da hierarquia de headings.
|
|
27
|
+
|
|
28
|
+
`Page` continua sendo a superfície estrutural, e `heading: "content"` continua declarando na
|
|
29
|
+
`Presentation` quem possui o heading principal. A decisão não transforma `Content` em `Page`; ela
|
|
30
|
+
remove uma escolha visual que não tinha significado independente dentro de `Content`.
|
|
31
|
+
|
|
32
|
+
## Consequências
|
|
33
|
+
|
|
34
|
+
- combinações contraditórias deixam de ser representáveis;
|
|
35
|
+
- consumidores removem `variant="page"` e preservam `level={1}`;
|
|
36
|
+
- o ritmo do conteúdo principal fica igual ao de páginas cujo heading pertence a `PageHeader`;
|
|
37
|
+
- a remoção da propriedade é incompatível e exige migração coordenada dos consumidores;
|
|
38
|
+
- a escala interna continua centralizada em `SurfaceHeader`, sem duplicação no consumidor.
|
|
39
|
+
|
|
40
|
+
## Alternativas descartadas
|
|
41
|
+
|
|
42
|
+
### Manter variant e corrigir somente o espaço
|
|
43
|
+
|
|
44
|
+
Corrigiria o sintoma visual, mas preservaria a duplicação e os estados inválidos no contrato.
|
|
45
|
+
|
|
46
|
+
### Renomear variant para role
|
|
47
|
+
|
|
48
|
+
Tornaria a intenção mais explícita, porém continuaria exigindo duas propriedades para declarar o
|
|
49
|
+
mesmo papel e permitiria que elas divergissem.
|
|
50
|
+
|
|
51
|
+
### Derivar a apresentação da presença em Page
|
|
52
|
+
|
|
53
|
+
Acoplaria `Content` ao local de renderização. O heading principal já é declarado diretamente por
|
|
54
|
+
`level={1}` e pode ser validado por `Page` sem outro contexto implícito.
|
|
55
|
+
|
|
56
|
+
## Verificação
|
|
57
|
+
|
|
58
|
+
- testes de `Content` confirmam escala e intervalo de página no nível 1 e de seção nos demais;
|
|
59
|
+
- tipos e exemplos públicos não aceitam mais `variant` em `Content`;
|
|
60
|
+
- testes de `Page`, `Presentation` e preenchimento preservam `Content level={1}` como heading
|
|
61
|
+
principal;
|
|
62
|
+
- consumidores compilam sem `variant="page"`.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# ADR 0039 — A apresentação do filtro é independente de seu tipo
|
|
2
|
+
|
|
3
|
+
- **Status:** aceita.
|
|
4
|
+
- **Data:** 2026-09-29.
|
|
5
|
+
- **Complementa:** ADR 0019.
|
|
6
|
+
|
|
7
|
+
## Contexto
|
|
8
|
+
|
|
9
|
+
`FilterSpec` declara o significado e o transporte de cada filtro, mas a interface apresentava todo
|
|
10
|
+
filtro `select` com o mesmo controle. Escolhas únicas, curtas e mutuamente exclusivas — como um
|
|
11
|
+
canal com três opções — continuavam escondidas em um menu mesmo quando a comparação direta entre
|
|
12
|
+
as alternativas era mais útil.
|
|
13
|
+
|
|
14
|
+
Transformar `toggle` em um novo `type` misturaria apresentação com valor: tanto o menu quanto o
|
|
15
|
+
grupo produzem a mesma string, ocupam o mesmo campo no estado navegável e chegam sem mudança à
|
|
16
|
+
action.
|
|
17
|
+
|
|
18
|
+
## Decisão
|
|
19
|
+
|
|
20
|
+
`FilterSpec` passa a aceitar `presentation: "select" | "toggle"`. A ausência da propriedade
|
|
21
|
+
preserva `select` como apresentação padrão.
|
|
22
|
+
|
|
23
|
+
`toggle` aplica-se a filtros `type: "select"` de escolha única e usa as mesmas opções, valor de
|
|
24
|
+
repouso, regra de vazio, dependências e atualização de estado do controle padrão. A apresentação
|
|
25
|
+
mostra todas as opções em um `ToggleGroup`; a composição visual vigente é definida pela ADR 0042.
|
|
26
|
+
O controle não permite desmarcar a opção ativa quando o filtro não aceita vazio. Quando a opção vem
|
|
27
|
+
de um dicionário e sua entrada declara um ícone do catálogo, o grupo apresenta ícone e rótulo juntos.
|
|
28
|
+
|
|
29
|
+
O tipo do filtro continua descrevendo o valor e sua operação. A apresentação escolhe somente o
|
|
30
|
+
controle usado para editá-lo.
|
|
31
|
+
|
|
32
|
+
## Consequências
|
|
33
|
+
|
|
34
|
+
- contratos podem expor escolhas pequenas e fechadas sem reconstruir a barra no consumidor;
|
|
35
|
+
- URL, input da action, defaults e handlers permanecem inalterados;
|
|
36
|
+
- filtros existentes continuam usando `Select` sem migração;
|
|
37
|
+
- `toggle` não se aplica a seleção múltipla, lookup, texto, número ou data;
|
|
38
|
+
- conjuntos extensos ou pesquisáveis continuam usando `Select`.
|
|
39
|
+
|
|
40
|
+
## Alternativas descartadas
|
|
41
|
+
|
|
42
|
+
### Criar `type: "toggle"`
|
|
43
|
+
|
|
44
|
+
Duplicaria a semântica de `select` e exigiria que serialização, validação e adaptadores conhecessem
|
|
45
|
+
um tipo cujo valor não mudou.
|
|
46
|
+
|
|
47
|
+
### Compor o ToggleGroup em cada página
|
|
48
|
+
|
|
49
|
+
Preservaria o contrato incompleto e duplicaria a sincronização entre controle, URL, default e input
|
|
50
|
+
efetivo da action.
|
|
51
|
+
|
|
52
|
+
### Trocar automaticamente selects com poucas opções
|
|
53
|
+
|
|
54
|
+
Quantidade de opções não determina a melhor apresentação. O contrato precisa declarar a intenção,
|
|
55
|
+
sem uma heurística visual invisível.
|
|
56
|
+
|
|
57
|
+
## Verificação
|
|
58
|
+
|
|
59
|
+
- teste do `ActionFilterBar` confirma que `presentation: "toggle"` renderiza todas as opções e
|
|
60
|
+
atualiza o mesmo campo de filtro;
|
|
61
|
+
- teste de regressão confirma que um `select` sem apresentação continua usando `Select`;
|
|
62
|
+
- typecheck impede valores de apresentação fora do vocabulário público.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# ADR 0040 — Tabs usa uma única apresentação de navegação
|
|
2
|
+
|
|
3
|
+
- **Status:** aceita.
|
|
4
|
+
- **Data:** 2026-09-29.
|
|
5
|
+
- **Substitui parcialmente:** ADR 0032, ADR 0034 e ADR 0036 quanto à escolha de variante em `TabsList`.
|
|
6
|
+
|
|
7
|
+
## Contexto
|
|
8
|
+
|
|
9
|
+
`TabsList` oferecia uma superfície preenchida e uma variante com linha. A superfície preenchida
|
|
10
|
+
ficava visualmente próxima de um controle segmentado, embora `Tabs` represente navegação entre
|
|
11
|
+
painéis e um `ToggleGroup` represente escolha de valores. A mesma aparência para comportamentos
|
|
12
|
+
diferentes dificultava escolher o componente correto e levou filtros a serem descritos como tabs.
|
|
13
|
+
|
|
14
|
+
## Decisão
|
|
15
|
+
|
|
16
|
+
`TabsList` usa somente a linha de navegação e deixa de aceitar `variant`. Cada `TabsListItem`
|
|
17
|
+
continua associado a um `TabsPanel` pelo mesmo valor. Orientação, tamanho, distribuição e moldura
|
|
18
|
+
permanecem propriedades independentes do conjunto.
|
|
19
|
+
|
|
20
|
+
A anatomia pública nomeia a posição de cada parte: `TabsListItem` pertence a `TabsList`, enquanto
|
|
21
|
+
`TabsPanel` identifica o painel associado. Os nomes anteriores, `TabsTrigger` e `TabsContent`, não
|
|
22
|
+
são mantidos como aliases porque há um único consumidor e a migração é atômica.
|
|
23
|
+
|
|
24
|
+
Escolhas únicas ou múltiplas que alteram um valor no contexto atual usam `ToggleGroup`. A composição
|
|
25
|
+
visual vigente do grupo é definida pela ADR 0042.
|
|
26
|
+
|
|
27
|
+
## Consequências
|
|
28
|
+
|
|
29
|
+
- Tabs mantém uma identidade visual única e vinculada à navegação entre painéis.
|
|
30
|
+
- Filtros e escolhas de formulário não usam semântica de tabs para obter uma aparência compacta.
|
|
31
|
+
- Consumidores removem `variant="line"`; não há substituição para a antiga superfície preenchida.
|
|
32
|
+
- Consumidores renomeiam `TabsTrigger` para `TabsListItem` e `TabsContent` para `TabsPanel`.
|
|
33
|
+
- A documentação de ToggleGroup explicita seus modos de composição.
|
|
34
|
+
|
|
35
|
+
## Alternativas descartadas
|
|
36
|
+
|
|
37
|
+
### Manter duas variantes em TabsList
|
|
38
|
+
|
|
39
|
+
Preservaria flexibilidade visual, mas manteria a ambiguidade entre navegação e escolha de valor.
|
|
40
|
+
|
|
41
|
+
### Compartilhar uma apresentação segmentada entre Tabs e ToggleGroup
|
|
42
|
+
|
|
43
|
+
Permitiria a mesma aparência com semânticas distintas, mas não resolveria a dificuldade de leitura
|
|
44
|
+
observada no produto. A linha torna a navegação reconhecível sem depender do contexto ao redor.
|
|
45
|
+
|
|
46
|
+
### Preservar Trigger e Content como nomes públicos
|
|
47
|
+
|
|
48
|
+
Manteria proximidade com a primitiva subjacente, mas nomearia mecanismo e conteúdo genérico em vez
|
|
49
|
+
da anatomia exposta ao consumidor. `ListItem` e `Panel` deixam a composição legível no JSX.
|
|
50
|
+
|
|
51
|
+
## Verificação
|
|
52
|
+
|
|
53
|
+
- o tipo público de `TabsList` não aceita `variant`;
|
|
54
|
+
- a exportação pública oferece `TabsListItem` e `TabsPanel`, sem aliases para os nomes anteriores;
|
|
55
|
+
- testes da primitiva confirmam a linha-base, o indicador ativo e a distribuição;
|
|
56
|
+
- testes do `ActionFilterBar` confirmam que `presentation: "toggle"` usa seleção exclusiva sem
|
|
57
|
+
alterar o estado transportado para a action;
|
|
58
|
+
- a documentação de Tabs não oferece apresentação preenchida e a de ToggleGroup demonstra seus
|
|
59
|
+
modos próprios de escolha.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# ADR 0042 — ToggleGroup compartilha a gramática de controles agrupados
|
|
2
|
+
|
|
3
|
+
- **Status:** aceita.
|
|
4
|
+
- **Data:** 2026-09-30.
|
|
5
|
+
- **Substitui parcialmente:** ADR 0040 quanto à composição e à anatomia pública de `ToggleGroup`.
|
|
6
|
+
|
|
7
|
+
## Contexto
|
|
8
|
+
|
|
9
|
+
`ButtonGroup` expressa a relação entre controles com `mode`, distribui largura com `distribution` e
|
|
10
|
+
mantém orientação, forma e moldura como eixos independentes. `ToggleGroup` resolvia necessidades
|
|
11
|
+
equivalentes por outra gramática: `spacing` controlava indiretamente se os itens pareciam conectados,
|
|
12
|
+
e cada controle era exposto como `ToggleGroupItem`, embora o controle isolado se chamasse `Toggle`.
|
|
13
|
+
|
|
14
|
+
Essa diferença ficou mais visível ao buscar uma apresentação segmentada para filtros. Criar
|
|
15
|
+
`appearance="segmented"` repetiria o significado visual de `variant`; criar
|
|
16
|
+
`variant="segmented"` misturaria o tratamento de cada controle com a relação entre os controles.
|
|
17
|
+
Nenhuma opção aproveitaria a gramática que o catálogo já usa para grupos de botões.
|
|
18
|
+
|
|
19
|
+
## Decisão
|
|
20
|
+
|
|
21
|
+
`ToggleGroup` compartilha com `ButtonGroup` os eixos de composição:
|
|
22
|
+
|
|
23
|
+
- `mode="connected" | "spaced" | "divided" | "segmented"` define a relação entre controles;
|
|
24
|
+
- `distribution="content" | "equal"` preserva a largura dos rótulos ou reparte a largura disponível;
|
|
25
|
+
- `orientation`, `shape` e `framed` mantêm seus significados estruturais;
|
|
26
|
+
- `variant` e `size` continuam tratando cada `Toggle` e podem ser declarados no grupo para chegar
|
|
27
|
+
aos controles por contexto.
|
|
28
|
+
|
|
29
|
+
`connected` substitui o `spacing={0}` implícito; `spaced` substitui valores positivos de `spacing`;
|
|
30
|
+
`divided` insere divisores sem incluí-los na distribuição; `segmented` cria uma superfície
|
|
31
|
+
compartilhada, destaca a seleção dentro dela e separa somente segmentos inativos vizinhos.
|
|
32
|
+
`spacing` sai do contrato.
|
|
33
|
+
|
|
34
|
+
A anatomia pública passa a usar `ToggleGroup + Toggle`, em paralelo a `ButtonGroup + Button`.
|
|
35
|
+
Dentro do grupo, `Toggle` exige `value` e participa da seleção coordenada; fora dele, mantém
|
|
36
|
+
`pressed`, `defaultPressed` e `onPressedChange`. `ToggleGroupItem` sai sem alias porque existe um
|
|
37
|
+
único consumidor e a migração é atômica.
|
|
38
|
+
|
|
39
|
+
Filtros `select` declarados com `presentation: "toggle"` usam `mode="segmented"`. A propriedade do
|
|
40
|
+
filtro continua escolhendo entre `Select` e `ToggleGroup`; estado, URL, defaults e input da action
|
|
41
|
+
não mudam.
|
|
42
|
+
|
|
43
|
+
## Consequências
|
|
44
|
+
|
|
45
|
+
- grupos de ações e grupos de seleção compartilham vocabulário para relação, distribuição e forma;
|
|
46
|
+
- `segmented` fica no eixo que descreve a relação entre controles, sem criar um sinônimo de
|
|
47
|
+
`variant`;
|
|
48
|
+
- a mesma unidade pública, `Toggle`, funciona isolada ou sob seleção coordenada;
|
|
49
|
+
- a ausência de `value` em um `Toggle` dentro do grupo é recusada em runtime, pois JSX não permite
|
|
50
|
+
que o tipo do filho dependa estaticamente do pai;
|
|
51
|
+
- consumidores migram `ToggleGroupItem` para `Toggle` e `spacing` para o `mode` correspondente;
|
|
52
|
+
- filtros toggle mudam apenas de acabamento visual e preservam o contrato transportado.
|
|
53
|
+
|
|
54
|
+
## Alternativas descartadas
|
|
55
|
+
|
|
56
|
+
### `appearance="segmented"`
|
|
57
|
+
|
|
58
|
+
Separaria a composição do grupo, mas criaria um eixo visual sinônimo de `variant` sem organizar as
|
|
59
|
+
outras diferenças já existentes entre `ButtonGroup` e `ToggleGroup`.
|
|
60
|
+
|
|
61
|
+
### `variant="segmented"`
|
|
62
|
+
|
|
63
|
+
Seria curto, porém faria `variant` responder ao mesmo tempo pelo tratamento do controle e pela
|
|
64
|
+
relação entre controles. Também deixaria `spacing` e `distribution` sem uma gramática comum.
|
|
65
|
+
|
|
66
|
+
### Manter `ToggleGroupItem`
|
|
67
|
+
|
|
68
|
+
Preservaria a exigência estática de `value`, mas manteria uma anatomia diferente de
|
|
69
|
+
`ButtonGroup + Button` para conceitos equivalentes. A verificação em runtime é um custo localizado
|
|
70
|
+
e produz uma mensagem direta no uso inválido.
|
|
71
|
+
|
|
72
|
+
## Verificação
|
|
73
|
+
|
|
74
|
+
- o tipo público de `ToggleGroup` oferece os quatro modos e as duas distribuições, sem `spacing`;
|
|
75
|
+
- `ToggleGroupItem` deixa de ser exportado e os consumidores usam `Toggle`;
|
|
76
|
+
- testes cobrem os modos, distribuição igual, orientação dos divisores e exigência de `value`;
|
|
77
|
+
- testes do `ActionFilterBar` confirmam que `presentation: "toggle"` usa `mode="segmented"` sem
|
|
78
|
+
alterar o valor transportado;
|
|
79
|
+
- documentação e metadata distinguem escolha com `ToggleGroup` de navegação com `Tabs`.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# ADR 0043 — Superfícies e interações neutras usam tokens opacos por papel
|
|
2
|
+
|
|
3
|
+
- **Status:** aceita.
|
|
4
|
+
- **Data:** 2026-09-30.
|
|
5
|
+
- **Relacionadas:** ADR 0023, quanto à anatomia das superfícies; ADR 0042, quanto aos controles
|
|
6
|
+
segmentados.
|
|
7
|
+
|
|
8
|
+
## Contexto
|
|
9
|
+
|
|
10
|
+
O tema claro usava o mesmo branco em `background` e `card`, enquanto o escuro elevava cards sobre
|
|
11
|
+
um canvas mais escuro. Estados neutros também eram construídos com combinações como `muted/30`,
|
|
12
|
+
`muted/50`, `background/60`, `brightness` e correções `dark:` nos componentes.
|
|
13
|
+
|
|
14
|
+
Uma cor translúcida não representa um resultado: ela depende da superfície que estiver atrás.
|
|
15
|
+
Consequentemente, a mesma classe podia funcionar dentro de um card e desaparecer no canvas. Quem
|
|
16
|
+
compunha uma tela no tema claro não conseguia presumir a hierarquia nem o comportamento no escuro;
|
|
17
|
+
cada contexto precisava ser inspecionado.
|
|
18
|
+
|
|
19
|
+
## Decisão
|
|
20
|
+
|
|
21
|
+
O tema declara uma escala neutra opaca por papel:
|
|
22
|
+
|
|
23
|
+
- `background` é o canvas da aplicação;
|
|
24
|
+
- `card` e `popover` são superfícies elevadas;
|
|
25
|
+
- `control` é a superfície de inputs e controles editáveis;
|
|
26
|
+
- `interaction-hover`, `interaction-pressed` e `interaction-selected` são estados de interação.
|
|
27
|
+
|
|
28
|
+
Os dois temas preservam a mesma ordem comportamental: superfície em repouso, hover, seleção e
|
|
29
|
+
pressão ganham contraste progressivo. Os valores podem avançar em direções luminosas diferentes,
|
|
30
|
+
mas o componente usa o mesmo token e a mesma classe nos dois temas. `accent` passa a apontar para
|
|
31
|
+
`interaction-hover`, preservando a integração com primitives externas que esperam esse nome.
|
|
32
|
+
|
|
33
|
+
Estados neutros essenciais não são obtidos com alpha sobre uma superfície desconhecida. Alpha
|
|
34
|
+
continua permitido para decoração, feedback semântico colorido, overlays e efeitos cuja mistura é
|
|
35
|
+
parte deliberada do resultado.
|
|
36
|
+
|
|
37
|
+
No tema claro, cards e popovers permanecem brancos, enquanto o canvas recebe um cinza quase branco.
|
|
38
|
+
No escuro, a hierarquia existente é preservada. A distinção entre superfícies não substitui borda,
|
|
39
|
+
foco nem texto quando esses sinais forem necessários para identificar um controle.
|
|
40
|
+
|
|
41
|
+
## Consequências
|
|
42
|
+
|
|
43
|
+
- componentes deixam de carregar receitas `dark:` para estados neutros equivalentes;
|
|
44
|
+
- mover um controle entre canvas, card e modal não muda a cor resultante de seu estado;
|
|
45
|
+
- hover, pressed e selected deixam de disputar o significado de `muted`;
|
|
46
|
+
- o tema pode ser calibrado sem editar cada primitive;
|
|
47
|
+
- light e dark ainda exigem verificação visual, mas compartilham a mesma gramática e deixam de
|
|
48
|
+
depender da superfície ancestral para produzir seus estados essenciais.
|
|
49
|
+
|
|
50
|
+
## Alternativas descartadas
|
|
51
|
+
|
|
52
|
+
### Manter transparências e calibrar por componente
|
|
53
|
+
|
|
54
|
+
Preservaria menos tokens, mas manteria o resultado dependente do ancestral e multiplicaria exceções
|
|
55
|
+
por tema.
|
|
56
|
+
|
|
57
|
+
### Tornar o card cinza sobre canvas branco
|
|
58
|
+
|
|
59
|
+
Criaria contraste, porém inverteria a relação de elevação já consolidada no escuro e daria ao card
|
|
60
|
+
um peso visual desnecessário. O canvas quase branco com superfícies brancas preserva a elevação sem
|
|
61
|
+
transformar a aplicação numa coleção de blocos cinza.
|
|
62
|
+
|
|
63
|
+
### Usar somente `accent` para todos os estados
|
|
64
|
+
|
|
65
|
+
Reduziria o vocabulário, mas hover transitório, seleção persistente e pressão deixariam de ser
|
|
66
|
+
distinguíveis e voltariam a depender de modifiers locais.
|
|
67
|
+
|
|
68
|
+
## Verificação
|
|
69
|
+
|
|
70
|
+
- o tema publica os quatro papéis opacos (`control` e os três estados de interação) nos dois temas;
|
|
71
|
+
- primitives neutras usam os mesmos tokens em light e dark, sem receitas locais equivalentes;
|
|
72
|
+
- testes cobrem a projeção dos tokens e os estados de tabela, toggle, menu e navegação lateral;
|
|
73
|
+
- a aplicação consumidora é compilada e verificada em páginas com canvas, card, formulário e grid.
|