@softize/opus 18.4.0 → 19.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 +94 -0
- package/docs/adr/0020-icon-tiles-are-explicitly-composed.md +46 -0
- package/docs/adr/0021-content-carries-structure-and-semantic-element.md +65 -0
- package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +293 -0
- package/package.json +1 -1
- package/src/ui/components/patterns/content-header.tsx +169 -50
- package/src/ui/components/patterns/heading-level.ts +16 -0
- package/src/ui/components/patterns/list.tsx +76 -27
- package/src/ui/components/patterns/pane.tsx +294 -0
- package/src/ui/components/patterns/presentation.tsx +29 -17
- package/src/ui/components/patterns/sidebar.tsx +13 -72
- package/src/ui/components/patterns/split.tsx +8 -35
- package/src/ui/components/patterns/surface-anatomy.ts +219 -0
- package/src/ui/components/patterns/surface-header.tsx +51 -6
- package/src/ui/components/patterns/surface-media.tsx +69 -0
- package/src/ui/components/primitives/card.tsx +91 -29
- package/src/ui/components/primitives/command.tsx +12 -4
- package/src/ui/components/primitives/detail.tsx +10 -13
- package/src/ui/components/primitives/dialog.tsx +138 -35
- package/src/ui/components/primitives/drawer.tsx +122 -23
- package/src/ui/components/primitives/empty.tsx +2 -1
- package/src/ui/components/primitives/field.tsx +38 -16
- package/src/ui/components/primitives/icon-tile.tsx +91 -0
- package/src/ui/components/primitives/item.tsx +2 -1
- package/src/ui/components/primitives/metric-card.tsx +98 -39
- package/src/ui/components/primitives/table.tsx +1 -1
- package/src/ui/docs/content/action-list.md +61 -52
- package/src/ui/docs/content/card.md +221 -30
- package/src/ui/docs/content/content.md +90 -8
- package/src/ui/docs/content/dialog.md +115 -7
- package/src/ui/docs/content/drawer.md +136 -9
- package/src/ui/docs/content/field.md +78 -30
- package/src/ui/docs/content/icon-tile.md +47 -0
- package/src/ui/docs/content/metric-card.md +57 -18
- package/src/ui/docs/content/page.md +8 -0
- package/src/ui/docs/content/pane.md +250 -0
- package/src/ui/docs/content/sidebar.md +22 -43
- package/src/ui/docs/content/skeleton.md +7 -3
- package/src/ui/docs/content/split.md +16 -21
- package/src/ui/docs/registry.tsx +14 -2
- package/src/ui/meta.ts +17 -5
- package/src/ui/react.tsx +31 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,100 @@ Depois de qualquer bump, rode os gates (`typecheck` · `test` · `opus check` ·
|
|
|
7
7
|
`opus copy --check` · `base copy check` · `manifest:check`) — eles apontam o que a
|
|
8
8
|
mudança cobra do seu código.
|
|
9
9
|
|
|
10
|
+
## 19.0.0 — 2026-09-24
|
|
11
|
+
|
|
12
|
+
Card, Dialog, Drawer e Pane passam a derivar a anatomia da mesma infraestrutura que Page e Content
|
|
13
|
+
já usavam. Cada superfície declara `layout="continuous" | "sectioned"` no seu componente raiz, e o
|
|
14
|
+
layout responde por um pacote: recuo das regiões, ritmo vertical, escala do título, posição das
|
|
15
|
+
ações, apresentação da mídia e divisores. O divisor passa a ser derivado da vizinhança das regiões —
|
|
16
|
+
um header seguido de corpo desenha a linha; um header sozinho, não.
|
|
17
|
+
|
|
18
|
+
Os headers passam a posicionar sozinhos o que antes o consumidor montava: mídia, título, descrição
|
|
19
|
+
(onde a família a tem), ações no extremo oposto e o botão de fechar depois delas. `Dialog`, `Drawer`
|
|
20
|
+
e `Pane` ganham slots de ações e de navegação; `Card` e `Pane` ganham slot de mídia; `Card` ganha
|
|
21
|
+
título com nível semântico.
|
|
22
|
+
|
|
23
|
+
A família `Pane` deixa de viver em dois módulos: raiz e slots agora moram juntos, com título,
|
|
24
|
+
mídia, ações, descrição e página própria na documentação.
|
|
25
|
+
|
|
26
|
+
`MetricCard` ganha ativação: com `onActivate`, `active` e `activationLabel`, a medida vira um
|
|
27
|
+
controle que aplica o recorte que ela resume — o card passa a emitir `aria-pressed` e a anunciar o
|
|
28
|
+
próprio nome. As três propriedades andam juntas pelo tipo; declarar `active` sem `onActivate` não
|
|
29
|
+
compila. A marcação de ativo é a borda na cor primária: antes era um anel desenhado para fora da
|
|
30
|
+
borda, que qualquer ancestral com `overflow-hidden` cortava. O anel de foco também passa a ser
|
|
31
|
+
interno.
|
|
32
|
+
|
|
33
|
+
Na mesma versão, a lista em `layout="fill"` troca a rolagem montada com um contêiner posicionado
|
|
34
|
+
por uma grade de duas linhas: os resultados ocupam a folga, o rodapé se mede pelo conteúdo, e o
|
|
35
|
+
contêiner da tabela abre mão da rolagem horizontal para não roubar do cabeçalho a referência do
|
|
36
|
+
`sticky`. O rodapé encosta na última linha e mantém a mesma altura com e sem paginador. O detalhe
|
|
37
|
+
aproxima o ícone do rótulo, na escala densa das superfícies produtivas.
|
|
38
|
+
|
|
39
|
+
### Breaking
|
|
40
|
+
|
|
41
|
+
**Headers validam seus slots.** `CardHeader`, `DialogHeader`, `DrawerHeader` e `PaneHeader` aceitam
|
|
42
|
+
apenas os slots da família e no máximo um de cada. Nó solto dentro do header passa a lançar erro com
|
|
43
|
+
a lista do que é aceito. Migração: envolva ações em `CardAction`, `DialogActions`, `DrawerActions`
|
|
44
|
+
ou `PaneActions`; navegação em `DialogNavigation` ou `DrawerNavigation`; ícone em `CardMedia`,
|
|
45
|
+
`DrawerMedia` ou `PaneMedia`. Descrição dentro do header de modal não tem slot e vai para o começo
|
|
46
|
+
do corpo, como as ADRs 0014 e 0018 já determinavam.
|
|
47
|
+
|
|
48
|
+
**`CardTitle` virou heading.** Era um `div` sem escala tipográfica; agora emite `h3` por padrão,
|
|
49
|
+
aceita `level` para a hierarquia correta e declara a escala pelo layout. Migração: remova o heading
|
|
50
|
+
que você embutia dentro dele e o `text-*` que aplicava por fora; passe `level` quando a página pedir
|
|
51
|
+
outra hierarquia.
|
|
52
|
+
|
|
53
|
+
**Divisores saem do consumidor.** `<CardHeader className="border-b">` e
|
|
54
|
+
`<CardFooter className="border-t">` deixam de ser necessários: use `layout="sectioned"` no `Card`.
|
|
55
|
+
As classes continuam funcionando, mas somam uma segunda linha sobre a derivada.
|
|
56
|
+
|
|
57
|
+
**`PaneHeader` e `PaneFooter` ganharam recuo próprio** e o `Pane` passa a usar `inset="none"`
|
|
58
|
+
sozinho quando contém slots estruturais. Migração: remova o `inset="none"` e o padding que a tela
|
|
59
|
+
aplicava no header; remova também o `border-b-0`/`border-t-0` de quem desligava o divisor para
|
|
60
|
+
redesenhá-lo à mão.
|
|
61
|
+
|
|
62
|
+
**O rodapé do `Drawer` recebeu a faixa sutil do `Dialog`.** A ausência era divergência não declarada
|
|
63
|
+
entre duas superfícies com a mesma anatomia; agora as duas usam o mesmo fundo. O título do Drawer
|
|
64
|
+
também perdeu o `text-foreground` que só ele tinha.
|
|
65
|
+
|
|
66
|
+
**Títulos de modal passam a truncar.** `DialogTitle` e `DrawerTitle` mantêm o nome da superfície em
|
|
67
|
+
uma linha, com reticências quando não cabe, em vez de empurrar o corpo para baixo. O
|
|
68
|
+
`mode="alert"` é a exceção: ali o título é centralizado e pode ocupar duas linhas, porque a
|
|
69
|
+
resposta obrigatória não tem corpo para empurrar. As telas que já
|
|
70
|
+
escreviam `truncate` podem remover a classe. Quando o nome precisar aparecer inteiro, ele pertence
|
|
71
|
+
ao começo do corpo, não ao cabeçalho (ADR 0018).
|
|
72
|
+
|
|
73
|
+
**Mídia com visual próprio.** Os slots de mídia aceitam `asChild`: uma marca que já se desenha —
|
|
74
|
+
um orbe, um logotipo — ocupa o slot sem ganhar a moldura por cima, e continua herdando a escala.
|
|
75
|
+
|
|
76
|
+
**O rodapé do `Drawer` mantém o `mt-auto`** que o `Dialog` não tem. A diferença passa a ser
|
|
77
|
+
declarada: o conteúdo lateral raramente preenche a altura da janela, e sem isso o rodapé subiria
|
|
78
|
+
atrás do corpo.
|
|
79
|
+
|
|
80
|
+
**Divisor não se desliga por classe.** `border-b-0` no consumidor não apaga a linha derivada — ela
|
|
81
|
+
vem de uma variante condicional, que vence uma classe simples. Uma superfície sem divisores é uma
|
|
82
|
+
superfície `continuous`: troque o layout no root em vez de apagar a borda região a região.
|
|
83
|
+
|
|
84
|
+
### Migração rápida
|
|
85
|
+
|
|
86
|
+
1. Rode a aplicação e siga os erros dos headers: cada um diz o slot que falta.
|
|
87
|
+
2. Remova `p-*`, `border-b`, `border-t`, `flex-row`, `justify-between` e `text-*` que existiam só
|
|
88
|
+
para corrigir o default da família.
|
|
89
|
+
3. Troque `<Card>` + classes de divisor por `<Card layout="sectioned">`.
|
|
90
|
+
4. Onde a tela remontava header com `div`, use os slots — eles agora posicionam sozinhos.
|
|
91
|
+
|
|
92
|
+
## 18.5.0 — 2026-09-22
|
|
93
|
+
|
|
94
|
+
`Content` passa a concentrar estrutura e apresentação de blocos com cabeçalho e corpo: aceita
|
|
95
|
+
`as="section" | "fieldset"`, `variant="page" | "section"` e moldura opcional com
|
|
96
|
+
`frameVariant="default" | "legend"`. O novo
|
|
97
|
+
`IconTile` mantém a moldura do ícone como composição explícita, permitindo usar o mesmo ícone com
|
|
98
|
+
ou sem esse apoio visual.
|
|
99
|
+
|
|
100
|
+
`ActionList` deixa de aplicar moldura por padrão. No layout `fill`, mantém o cabeçalho fixo sobre a
|
|
101
|
+
cor da superfície, com borda inferior durante a rolagem. Filtros avançados podem escolher o tratamento das seções sem
|
|
102
|
+
recriar o fieldset no consumidor. Labels de campos e detalhes compartilham a mesma escala tipográfica.
|
|
103
|
+
|
|
10
104
|
## 18.4.0 — 2026-09-21
|
|
11
105
|
|
|
12
106
|
`executions` acrescenta infraestrutura experimental de acompanhamento: snapshots versionados,
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# ADR 0020 — Molduras de ícone são compostas explicitamente
|
|
2
|
+
|
|
3
|
+
- **Status:** aceita.
|
|
4
|
+
- **Data:** 2026-09-18.
|
|
5
|
+
|
|
6
|
+
## Contexto
|
|
7
|
+
|
|
8
|
+
Várias interfaces apresentam um ícone dentro de uma pequena superfície quadrada. A mesma forma
|
|
9
|
+
aparecia incorporada em componentes como `FieldLegend`, `ItemMedia` e `EmptyMedia`, ou era
|
|
10
|
+
reconstruída pelo consumidor. Em `FieldLegend`, isso também vinculava a moldura à variante da
|
|
11
|
+
legenda: o consumidor conseguia escolher o ícone, mas não se ele deveria receber a superfície.
|
|
12
|
+
|
|
13
|
+
## Decisão
|
|
14
|
+
|
|
15
|
+
`IconTile` passa a representar a moldura visual não interativa de um ícone. Ele compartilha a
|
|
16
|
+
escala de tamanhos dos controles, declara a cor por `context` e oferece os tratamentos `subtle` e
|
|
17
|
+
`outline`.
|
|
18
|
+
|
|
19
|
+
Slots que aceitam conteúdo, como `FieldLegend.icon`, continuam genéricos. O consumidor passa um
|
|
20
|
+
ícone diretamente quando quer somente o glifo ou compõe `<IconTile><Icon /></IconTile>` quando a
|
|
21
|
+
moldura reforça a hierarquia. `ItemMedia variant="icon"` e `EmptyMedia variant="icon"` reutilizam
|
|
22
|
+
a mesma base visual porque essas variantes já prometem a moldura.
|
|
23
|
+
|
|
24
|
+
`IconTile` é somente apresentação. Quando o ícone aciona algo, deve-se usar `Button` com tamanho
|
|
25
|
+
de ícone e nome acessível.
|
|
26
|
+
|
|
27
|
+
## Consequências
|
|
28
|
+
|
|
29
|
+
- A presença da moldura deixa de ser uma regra implícita da legenda.
|
|
30
|
+
- Tamanho, arredondamento e preenchimento convergem entre consumidores.
|
|
31
|
+
- Um slot permanece flexível sem acumular propriedades como `iconFramed` ou `iconTile`.
|
|
32
|
+
- Migrações devem distinguir molduras decorativas de botões e indicadores semânticos.
|
|
33
|
+
|
|
34
|
+
## Alternativas descartadas
|
|
35
|
+
|
|
36
|
+
- **Adicionar uma propriedade à `FieldLegend`:** resolveria apenas uma superfície e ampliaria o
|
|
37
|
+
contrato toda vez que outra composição precisasse escolher a moldura.
|
|
38
|
+
- **Manter classes livres em cada consumidor:** preservaria divergências de escala e acabamento.
|
|
39
|
+
- **Usar `Button` desabilitado:** comunicaria interatividade onde não existe ação.
|
|
40
|
+
|
|
41
|
+
## Verificação
|
|
42
|
+
|
|
43
|
+
- teste estrutural cobre tamanho, variante e tamanho do glifo de `IconTile`;
|
|
44
|
+
- teste de `FieldLegend` confirma que o slot não adiciona moldura por conta própria;
|
|
45
|
+
- teste da API pública garante que o componente e seus metadados sejam exportados;
|
|
46
|
+
- documentação de `Field` mostra a composição explícita.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# ADR 0021 — Content reúne estrutura visual e elemento semântico
|
|
2
|
+
|
|
3
|
+
- **Status:** aceita.
|
|
4
|
+
- **Data:** 2026-09-19.
|
|
5
|
+
|
|
6
|
+
## Contexto
|
|
7
|
+
|
|
8
|
+
Regiões com título e corpo usam `Content`, mas grupos de formulário precisam reconstruir a mesma
|
|
9
|
+
estrutura com `FieldSet` e `FieldLegend`. Molduras aplicadas diretamente em `DetailGroup` também
|
|
10
|
+
separam visualmente o título dos dados que ele nomeia. Essa sobreposição aumenta a quantidade de
|
|
11
|
+
estruturas que a documentação e os consumidores precisam combinar.
|
|
12
|
+
|
|
13
|
+
## Decisão
|
|
14
|
+
|
|
15
|
+
`Content` continua responsável pela anatomia `Header + Body` e passa a aceitar:
|
|
16
|
+
|
|
17
|
+
- `framed`, que aplica a moldura à região inteira;
|
|
18
|
+
- `as="fieldset"`, que renderiza a raiz como `fieldset` e o título como `legend`.
|
|
19
|
+
- `frameVariant="legend"`, que integra visualmente a legenda à borda quando esse acabamento for
|
|
20
|
+
escolhido de forma explícita.
|
|
21
|
+
|
|
22
|
+
O default permanece `as="section"` sem moldura. `FieldGroup` organiza campos dentro do corpo e
|
|
23
|
+
`DetailGroup` preserva a semântica de lista de definições sem assumir a moldura da região.
|
|
24
|
+
`FieldSet` permanece disponível para compatibilidade e para composições que não precisam da
|
|
25
|
+
anatomia completa de `Content`.
|
|
26
|
+
|
|
27
|
+
No modo `fieldset`, a legenda aceita somente o título. Contador, descrição e ações não são filhos
|
|
28
|
+
válidos de `legend`; consumidores que precisarem dessas regiões devem mantê-las no corpo ou usar
|
|
29
|
+
uma `section`. A escolha de `fieldset` não define o acabamento visual: quando `framed` estiver
|
|
30
|
+
presente, a moldura é contínua por padrão. Os filtros avançados preservam a legenda integrada à
|
|
31
|
+
borda declarando `frameVariant="legend"`.
|
|
32
|
+
|
|
33
|
+
## Consequências
|
|
34
|
+
|
|
35
|
+
- A mesma API nomeia e delimita regiões de leitura e grupos de formulário.
|
|
36
|
+
- A escolha do elemento semântico não fica acoplada à presença da moldura.
|
|
37
|
+
- O recorte visual da borda deixa de ser consequência implícita do elemento semântico.
|
|
38
|
+
- A forma curta continua sendo o caminho comum, enquanto a composição explícita permanece
|
|
39
|
+
disponível nos dois elementos.
|
|
40
|
+
- A migração de `FieldSet` pode ser gradual e não exige remoção nesta versão.
|
|
41
|
+
|
|
42
|
+
## Alternativas consideradas
|
|
43
|
+
|
|
44
|
+
### Criar Panel
|
|
45
|
+
|
|
46
|
+
Separaria a moldura de `Content`, mas criaria outra raiz com header e body para documentar e
|
|
47
|
+
compor. Foi descartada porque `Content` já possui essa responsabilidade.
|
|
48
|
+
|
|
49
|
+
### Renomear FieldSet para FieldGroup
|
|
50
|
+
|
|
51
|
+
Produziria simetria nominal com `DetailGroup`, mas perderia a distinção entre a fronteira semântica
|
|
52
|
+
do formulário e o layout interno dos campos. Foi descartada porque `FieldGroup` já representa esse
|
|
53
|
+
layout.
|
|
54
|
+
|
|
55
|
+
### Adicionar um booleano fieldset
|
|
56
|
+
|
|
57
|
+
Permitiria alternar o elemento, mas trataria semântica como estado visual. Foi descartada em favor
|
|
58
|
+
de `as`, que explicita o elemento renderizado.
|
|
59
|
+
|
|
60
|
+
## Verificação
|
|
61
|
+
|
|
62
|
+
- Testes de UI confirmam `section` e heading como defaults.
|
|
63
|
+
- Testes de UI confirmam `fieldset`, `legend`, props nativas e ausência de heading nesse modo.
|
|
64
|
+
- Testes reprovam slots incompatíveis com a legenda.
|
|
65
|
+
- A documentação demonstra `Content + DetailGroup` e `Content as="fieldset" + FieldGroup`.
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
# ADR 0023 — Superfícies compartilham uma anatomia e dois layouts
|
|
2
|
+
|
|
3
|
+
- **Status:** aceita.
|
|
4
|
+
- **Data:** 2026-09-23.
|
|
5
|
+
- **Complementa:** ADRs 0005, 0014, 0018, 0019 e 0021.
|
|
6
|
+
|
|
7
|
+
## Contexto
|
|
8
|
+
|
|
9
|
+
A ADR 0005 estabeleceu a gramática `Root > Header + Body + Footer` para as superfícies da casa. O
|
|
10
|
+
vocabulário foi adotado, mas a implementação não: `Page` e `Content` derivam o header de uma
|
|
11
|
+
infraestrutura interna comum, enquanto `Card`, `Dialog`, `Drawer` e `Pane` desenham o seu à mão.
|
|
12
|
+
|
|
13
|
+
O efeito acumulado é mensurável no código de hoje:
|
|
14
|
+
|
|
15
|
+
- **Três escalas de padding para o mesmo papel.** `p-3` no chrome de Page, `p-4` nos slots de Dialog
|
|
16
|
+
e Drawer, `px-5` no Card. `Card` é a única com ritmo vertical assimétrico (`pt-5 pb-3` no header,
|
|
17
|
+
`pt-3 pb-5` no footer). `Content`, `PageBody` e `PaneBody` não têm padding nenhum.
|
|
18
|
+
- **`PaneHeader` sem padding e `PaneFooter` com `p-2`** — assimetria dentro da mesma família.
|
|
19
|
+
- **`CardTitle` é um `div` sem escala tipográfica**, único título do conjunto que não é heading e
|
|
20
|
+
não declara tamanho; herda o que vier do contexto.
|
|
21
|
+
- **Divisor automático em quatro famílias e manual no Card.** A documentação pública ensina o
|
|
22
|
+
consumidor a escrever `border-b` no `CardHeader` e `border-t` no `CardFooter`.
|
|
23
|
+
- **`Dialog` e `Drawer` não têm slot de ações no header.** Quem precisa injeta nós soltos ao lado do
|
|
24
|
+
título, como `Presentation` faz hoje.
|
|
25
|
+
- **A família `Pane` está partida**: a raiz mora em `split.tsx`, os slots em `sidebar.tsx`, a
|
|
26
|
+
documentação está na página da Sidebar e nenhum teste cobre a anatomia.
|
|
27
|
+
- **`Drawer` duplica `Dialog`** com três divergências não declaradas: footer sem fundo, header sem a
|
|
28
|
+
supressão condicional do divisor e título com `text-foreground` a mais.
|
|
29
|
+
- **Nenhuma suíte transversal** compara as famílias; cada divergência só aparece quando alguém
|
|
30
|
+
compõe as duas superfícies lado a lado.
|
|
31
|
+
|
|
32
|
+
A evidência que motivou a decisão veio da tela do Evento de Pós-vendas: para aproximar dois Cards do
|
|
33
|
+
recorte já usado por Dialog e Content, a tela precisou escrever `flex-row items-center gap-4
|
|
34
|
+
border-b p-4` no header, `p-4` no body, `text-sm` no título, um `<h3>` dentro do `CardTitle` e o
|
|
35
|
+
ícone dentro do texto. São cinco decisões estruturais tomadas pelo consumidor.
|
|
36
|
+
|
|
37
|
+
As forças em tensão:
|
|
38
|
+
|
|
39
|
+
- **Uniformidade × identidade da família.** Um modal, um card e um painel não precisam ter as mesmas
|
|
40
|
+
medidas; precisam ter a mesma gramática e o mesmo comportamento estrutural.
|
|
41
|
+
- **Compatibilidade × correção.** Alterar o default de uma família muda todas as telas que já a
|
|
42
|
+
usam. Preservar o default errado perpetua o defeito que motivou a decisão.
|
|
43
|
+
- **Uma API genérica × seis APIs específicas.** Um `<Surface>` público reduziria código e apagaria a
|
|
44
|
+
semântica de cada família; seis implementações independentes preservam a semântica e produzem a
|
|
45
|
+
divergência atual.
|
|
46
|
+
|
|
47
|
+
## Decisão
|
|
48
|
+
|
|
49
|
+
### 1. Uma infraestrutura interna, seis fachadas públicas
|
|
50
|
+
|
|
51
|
+
As telas continuam usando `CardHeader`, `DialogHeader`, `DrawerHeader`, `PaneHeader`,
|
|
52
|
+
`ContentHeader` e `PageHeader`. Não existe superfície genérica pública. `SurfaceHeader` e o novo
|
|
53
|
+
módulo de anatomia permanecem internos: a aplicação não os importa.
|
|
54
|
+
|
|
55
|
+
A anatomia compartilhada passa a viver em um único módulo interno, que responde por:
|
|
56
|
+
|
|
57
|
+
- as classes de cada região (`header`, `body`, `footer`) por família e layout;
|
|
58
|
+
- a escala visual do título, separada do nível semântico;
|
|
59
|
+
- a posição e o tamanho das ações;
|
|
60
|
+
- a apresentação da mídia;
|
|
61
|
+
- os divisores entre regiões adjacentes;
|
|
62
|
+
- o alinhamento horizontal comum entre as regiões da mesma superfície.
|
|
63
|
+
|
|
64
|
+
Nenhuma família reimplementa essas regras. Quando uma família precisa divergir, a divergência é um
|
|
65
|
+
valor declarado na tabela de anatomia, não uma classe escrita no componente.
|
|
66
|
+
|
|
67
|
+
### 2. O layout é do root e chega aos slots por contexto
|
|
68
|
+
|
|
69
|
+
`layout` é propriedade do componente raiz: `Card`, `DialogContent`, `DrawerContent` e `Pane`. Os
|
|
70
|
+
slots não recebem `layout`; eles leem o contexto da superfície que os hospeda. Um slot usado fora da
|
|
71
|
+
sua família continua sendo erro, como hoje.
|
|
72
|
+
|
|
73
|
+
`Content` e `Page` não expõem `layout` de anatomia. `Content` é contínuo por definição e `Page`
|
|
74
|
+
mantém `layout="flow" | "fill"`, que descreve ocupação de altura, não anatomia. São nomes
|
|
75
|
+
diferentes para coisas diferentes e permanecem assim.
|
|
76
|
+
|
|
77
|
+
### 3. O que cada layout significa
|
|
78
|
+
|
|
79
|
+
`continuous` e `sectioned` não são a ausência e a presença de uma borda. Cada um é um pacote:
|
|
80
|
+
|
|
81
|
+
| Aspecto | `continuous` | `sectioned` |
|
|
82
|
+
| --- | --- | --- |
|
|
83
|
+
| Divisores | nenhum | entre regiões adjacentes presentes |
|
|
84
|
+
| Ritmo vertical | header aproxima-se do corpo; o espaço separa | regiões simétricas; o divisor separa |
|
|
85
|
+
| Escala do título | a da família | a da família, um degrau mais contida quando a família tem duas |
|
|
86
|
+
| Fundo do footer | igual ao da superfície | sutil, quando a família declara |
|
|
87
|
+
| Alinhamento horizontal | idêntico entre header, body e footer | idêntico entre header, body e footer |
|
|
88
|
+
|
|
89
|
+
O alinhamento horizontal é invariante dos dois layouts: o recuo lateral de header, body e footer de
|
|
90
|
+
uma mesma superfície é sempre o mesmo valor. É o que a suíte transversal verifica.
|
|
91
|
+
|
|
92
|
+
### 4. Os divisores são derivados, não escritos
|
|
93
|
+
|
|
94
|
+
No layout `sectioned`, o divisor existe quando há duas regiões adjacentes. Um header seguido de nada
|
|
95
|
+
não desenha linha; um footer que é a única região também não. A regra é estrutural e vive no
|
|
96
|
+
componente, derivada da presença dos irmãos — não uma classe que o consumidor aplica.
|
|
97
|
+
|
|
98
|
+
A recomendação de escrever `border-b` e `border-t` no Card sai da documentação.
|
|
99
|
+
|
|
100
|
+
### 5. Defaults por família
|
|
101
|
+
|
|
102
|
+
Os defaults preservam o que cada família já entrega hoje, exceto onde o comportamento atual é o
|
|
103
|
+
defeito que esta decisão corrige:
|
|
104
|
+
|
|
105
|
+
| Família | Default | Por quê |
|
|
106
|
+
| --- | --- | --- |
|
|
107
|
+
| `Card` | `continuous` | é o que a família entrega hoje: sem divisores, ritmo assimétrico |
|
|
108
|
+
| `Dialog` (default) | `sectioned` | já tem divisores e faixas; o default nomeia o que existe |
|
|
109
|
+
| `Dialog` (`mode="alert"`) | composição própria | a resposta obrigatória mantém sua anatomia centrada |
|
|
110
|
+
| `Drawer` | `sectioned` | idem Dialog |
|
|
111
|
+
| `Pane` | `sectioned` | header e footer já têm divisor |
|
|
112
|
+
| `Content` | contínuo, sem opção | região semântica dentro de outra superfície |
|
|
113
|
+
| `Page` | contínuo, sem opção | a separação do chrome pertence ao shell |
|
|
114
|
+
|
|
115
|
+
Nenhum default muda de valor efetivo com esta ADR. O que muda é que o valor passa a ter nome,
|
|
116
|
+
implementação única e teste.
|
|
117
|
+
|
|
118
|
+
### 5.1. O que o inventário dos consumidores mostrou
|
|
119
|
+
|
|
120
|
+
A migração foi decidida sobre 374 usos reais das seis famílias na plataforma. O que eles revelam:
|
|
121
|
+
|
|
122
|
+
- **Nenhuma tela usa `CardDescription`, `ContentDescription`, `DialogDescription` ou equivalente.**
|
|
123
|
+
Toda descrição é um `<p className="text-sm text-muted-foreground">` escrito à mão. Quando esse
|
|
124
|
+
parágrafo está dentro de um `DialogHeader`, ele contraria a ADR 0018 e precisa ir para o corpo na
|
|
125
|
+
migração — não ganhar um slot novo no header.
|
|
126
|
+
- **`Card` é usado como caixa sem slots em várias telas**, com o padding escolhido por elas (`p-3`,
|
|
127
|
+
`p-5`, `px-3 py-2`). O root do Card continua sem padding próprio: quem não usa slots continua
|
|
128
|
+
decidindo. O padding pertence aos slots, como hoje.
|
|
129
|
+
- **Os dois `PaneHeader` do produto escrevem quase a mesma linha** (`flex h-[3.75rem] items-center
|
|
130
|
+
… p-3 … border-b-0`) em contextos diferentes. É a prova de que padding e divisor pertencem à
|
|
131
|
+
primitiva.
|
|
132
|
+
- **Um comentário no código do login documenta o defeito**: o `py-3` do `CardBody` "conta com um
|
|
133
|
+
CardFooter, que o login não tem". O ritmo do layout contínuo precisa reagir à ausência do rodapé.
|
|
134
|
+
- **Três testes de consumidor contradizem a unificação** e precisam de decisão explícita, não de
|
|
135
|
+
ajuste mecânico: um proíbe `p-4` no Card, outro exige `p-0` no `DrawerBody`, outro proíbe
|
|
136
|
+
`ContentHeader` em uma seção. Os dois primeiros fixam contornos; o terceiro fixa uma escolha de
|
|
137
|
+
composição que continua válida.
|
|
138
|
+
|
|
139
|
+
### 6. Slots por família
|
|
140
|
+
|
|
141
|
+
A anatomia é comum; o conjunto de slots continua sendo decisão de cada família, porque decisões
|
|
142
|
+
anteriores já os delimitaram:
|
|
143
|
+
|
|
144
|
+
| Slot | Card | Content | Page | Dialog | Drawer | Pane |
|
|
145
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
146
|
+
| Media | sim | não | não | sim (`DialogMedia`) | sim | sim |
|
|
147
|
+
| Title | sim | sim | sim | sim | sim | sim |
|
|
148
|
+
| Description | sim | sim | não (ADR 0014) | não (ADR 0018) | não (ADR 0018) | não |
|
|
149
|
+
| Meta | não | sim (contador) | não (ADR 0009) | não | não | não |
|
|
150
|
+
| Actions | sim | sim | sim | sim | sim | sim |
|
|
151
|
+
| Body | sim | sim | sim | sim | sim | sim |
|
|
152
|
+
| Footer | sim | não | sim | sim | sim | sim |
|
|
153
|
+
|
|
154
|
+
As ADRs 0009, 0014 e 0018 não são reabertas: Page, Dialog e Drawer continuam sem descrição no
|
|
155
|
+
header, e Page continua sem contador. `Dialog`, `Drawer` e `Pane` ganham o slot de ações que hoje
|
|
156
|
+
não têm — a ausência obrigava a injetar nós soltos ao lado do título, e o `Studio` a manter três
|
|
157
|
+
anatomias paralelas para a mesma entrada declarativa. O botão de fechar continua sendo o último
|
|
158
|
+
elemento da linha; as ações da tela ficam antes dele.
|
|
159
|
+
|
|
160
|
+
`Pane` ganha também título e mídia. Hoje ele não tem nenhum dos dois, e as duas telas que precisam
|
|
161
|
+
de um cabeçalho remontam a linha com `span`, incluindo o ícone dentro do texto.
|
|
162
|
+
|
|
163
|
+
`Content` permanece sem footer: é uma região nomeada dentro de outra superfície, e um rodapé
|
|
164
|
+
pertence à superfície que a hospeda.
|
|
165
|
+
|
|
166
|
+
### 7. Título: hierarquia e tamanho são independentes
|
|
167
|
+
|
|
168
|
+
O nível semântico vem de `level` (`1`–`6`) ou de `asChild`, e nunca do tamanho visual. O tamanho vem
|
|
169
|
+
da família e do layout. `CardTitle` deixa de ser um `div` sem escala e passa a declarar as duas
|
|
170
|
+
coisas, com `h3` como default — um card é uma seção dentro de uma página que já tem `h1`.
|
|
171
|
+
|
|
172
|
+
### 8. Mídia reutiliza a moldura existente
|
|
173
|
+
|
|
174
|
+
O slot de mídia usa `IconTile` ou a mesma infraestrutura visual dele. Nenhuma família cria outra
|
|
175
|
+
escala de molduras.
|
|
176
|
+
|
|
177
|
+
Quando a marca já tem visual próprio — um orbe, um logotipo —, `asChild` deixa o slot ceder o
|
|
178
|
+
elemento para ela: a escala continua vindo da mesma fonte, e a moldura sai de cena em vez de ser
|
|
179
|
+
desenhada por baixo.
|
|
180
|
+
|
|
181
|
+
`DialogMedia` é a exceção declarada: no modo de alerta a mídia não acompanha uma linha de título,
|
|
182
|
+
ela é o elemento de reconhecimento da resposta, e mantém a moldura maior e centrada. A exceção vale
|
|
183
|
+
só nesse modo, deriva da mesma infraestrutura das demais e é coberta por teste. Fora do alerta, o
|
|
184
|
+
`DialogMedia` é a mesma moldura das outras famílias.
|
|
185
|
+
|
|
186
|
+
### 7.1. O Pane aceita conteúdo livre; as demais não
|
|
187
|
+
|
|
188
|
+
`Card`, `Dialog`, `Drawer`, `Page` e `Content` sempre nomeiam alguma coisa: seus headers aceitam só
|
|
189
|
+
os slots da família e reprovam nó solto, com uma mensagem que lista o que é aceito.
|
|
190
|
+
|
|
191
|
+
`Pane` é a superfície genérica — a coluna que hospeda navegação, marca, busca ou chrome que não é
|
|
192
|
+
título nem ação. Seu header aceita conteúdo livre ao lado dos slots, e esse conteúdo ocupa o mesmo
|
|
193
|
+
espaço do heading, de modo que as ações continuam indo para o extremo oposto sem classe no
|
|
194
|
+
consumidor. A permissividade é dele, não uma brecha geral: forçar a marca de uma barra lateral a
|
|
195
|
+
virar "título" seria uniformidade sem semântica.
|
|
196
|
+
|
|
197
|
+
O conteúdo livre é renderizado **dentro** da região de heading, depois do título e da descrição,
|
|
198
|
+
qualquer que seja a ordem no JSX. Ele herda dali a largura que o heading absorve, o que é o que
|
|
199
|
+
permite às ações continuarem no extremo oposto — mas também significa que uma busca colocada no
|
|
200
|
+
header aparece **abaixo** do título, não ao lado dele. Um header que precise de algo antes do
|
|
201
|
+
título usa o slot de mídia; um que precise de algo ao lado usa o de ações.
|
|
202
|
+
|
|
203
|
+
### 8.1. Nível semântico em todas as fachadas
|
|
204
|
+
|
|
205
|
+
Todo título de superfície aceita `level`. Nos modais, onde a primitiva de acessibilidade emite o
|
|
206
|
+
elemento, o nível troca a tag do mesmo nó por composição, nunca aninha um heading dentro de outro, e
|
|
207
|
+
não altera a escala. Sem `level`, vale o `h2` que a primitiva já emitia.
|
|
208
|
+
|
|
209
|
+
### 9. `Pane` deixa de ser uma família partida
|
|
210
|
+
|
|
211
|
+
Raiz e slots passam a viver no mesmo módulo, com validação de pertencimento, documentação própria e
|
|
212
|
+
os mesmos testes das demais famílias. `split.tsx` continua dono do redimensionamento; `sidebar.tsx`
|
|
213
|
+
continua dono da navegação. A fronteira deixa de ser acidental e passa a ser declarada.
|
|
214
|
+
|
|
215
|
+
### 10. Compatibilidade
|
|
216
|
+
|
|
217
|
+
Aliases depreciados existentes (`CardContent`, `PaneContent`) seguem seu plano de remoção; esta
|
|
218
|
+
decisão não cria novos.
|
|
219
|
+
|
|
220
|
+
Classes de recuo, ritmo e escala que o consumidor já aplica continuam funcionando: `className` é
|
|
221
|
+
mesclado por último e vence. Uma tela que ainda escreve `p-4` no `CardHeader` não quebra, apenas
|
|
222
|
+
deixa de precisar.
|
|
223
|
+
|
|
224
|
+
**Os divisores são a exceção, e é uma exceção real.** Eles nascem de variantes condicionais
|
|
225
|
+
(`:not(:last-child)`), que têm especificidade maior que uma classe simples: `border-b-0` no
|
|
226
|
+
consumidor não desliga a linha derivada. Desligar o divisor de uma região isolada não é, portanto,
|
|
227
|
+
uma decisão do consumidor — é decisão da superfície, e se expressa trocando o layout dela para
|
|
228
|
+
`continuous`. A paleta de comandos é o caso dentro do próprio SDK: ela é contínua, e não um
|
|
229
|
+
seccionado com a primeira linha apagada.
|
|
230
|
+
|
|
231
|
+
Pela mesma razão, a regra que suprime o divisor entre um header e um rodapé adjacentes precisa
|
|
232
|
+
vencer a que o cria — não basta vir depois dela na folha, porque a ordem das variantes não é nossa.
|
|
233
|
+
|
|
234
|
+
## Consequências
|
|
235
|
+
|
|
236
|
+
- Uma mudança de espaçamento, divisor ou escala passa a ter um lugar só; hoje tem seis.
|
|
237
|
+
- Card ganha um modo que a aplicação já construía à mão, e a documentação deixa de ensinar o
|
|
238
|
+
contorno.
|
|
239
|
+
- Dialog e Drawer ganham ações no header; `Presentation` deixa de injetar nós soltos.
|
|
240
|
+
- Pane deixa de ser exceção não testada.
|
|
241
|
+
- Consumidores que aplicavam classes de correção podem removê-las; quem não remover continua
|
|
242
|
+
funcionando, o que permite migração incremental.
|
|
243
|
+
- O custo é concentrado: quase toda a superfície visual do SDK muda de implementação ao mesmo tempo,
|
|
244
|
+
e a rede de proteção precisa existir antes da migração, não depois.
|
|
245
|
+
- Uma família que precise divergir passa a precisar declarar a divergência na tabela e cobri-la por
|
|
246
|
+
teste. Isso é deliberado: torna a diferença uma decisão, não um acidente.
|
|
247
|
+
|
|
248
|
+
## Alternativas descartadas
|
|
249
|
+
|
|
250
|
+
### Manter cada família com seu header
|
|
251
|
+
|
|
252
|
+
Preservaria compatibilidade absoluta e evitaria o custo de migração. Foi descartada porque é o
|
|
253
|
+
estado atual: produziu três escalas de padding, um título sem escala, divisores manuais em uma
|
|
254
|
+
família e a documentação pública ensinando o contorno.
|
|
255
|
+
|
|
256
|
+
### Expor um `<Surface>` genérico
|
|
257
|
+
|
|
258
|
+
Reduziria o código a uma implementação e uma API. Foi descartada porque apagaria a semântica de cada
|
|
259
|
+
família — um card, um modal e um painel deixariam de se distinguir no código da tela — e porque o
|
|
260
|
+
ganho real está na infraestrutura interna, que este ADR unifica sem expor.
|
|
261
|
+
|
|
262
|
+
### Unificar também as medidas entre famílias
|
|
263
|
+
|
|
264
|
+
Um único recuo para todas as superfícies eliminaria a tabela de anatomia. Foi descartada porque as
|
|
265
|
+
famílias têm densidades legítimas diferentes: o chrome de página é mais compacto que um card, e um
|
|
266
|
+
modal tem largura controlada. O invariante que importa é o alinhamento dentro de cada superfície,
|
|
267
|
+
não a igualdade entre superfícies diferentes.
|
|
268
|
+
|
|
269
|
+
### Tratar `layout` como propriedade de cada slot
|
|
270
|
+
|
|
271
|
+
Permitiria um header seccionado com um corpo contínuo. Foi descartada porque o layout é a relação
|
|
272
|
+
entre as regiões: um divisor depende de quem está do outro lado. Propriedade por slot permitiria
|
|
273
|
+
compor superfícies incoerentes sem erro.
|
|
274
|
+
|
|
275
|
+
### Derivar os divisores por CSS no consumidor
|
|
276
|
+
|
|
277
|
+
Manteria o componente menor. Foi descartada porque devolve ao consumidor a decisão que esta ADR
|
|
278
|
+
retira dele, e porque a regra depende de presença de irmãos, que o componente conhece e a tela
|
|
279
|
+
não.
|
|
280
|
+
|
|
281
|
+
## Verificação
|
|
282
|
+
|
|
283
|
+
- Uma suíte transversal parametrizada percorre as famílias configuráveis e verifica, para cada
|
|
284
|
+
layout: alinhamento horizontal idêntico entre header, body e footer; divisor apenas entre regiões
|
|
285
|
+
adjacentes presentes; escala do título independente do nível semântico; header com e sem mídia,
|
|
286
|
+
descrição e ações; ações no extremo oposto sem classe no consumidor; body sem header; header sem
|
|
287
|
+
footer; footer com e sem ações.
|
|
288
|
+
- A mesma suíte verifica que `Page` e `Content` não expõem `sectioned` e que nenhuma família
|
|
289
|
+
reimplementa as classes de anatomia localmente.
|
|
290
|
+
- Testes de família continuam cobrindo o que é específico de cada uma.
|
|
291
|
+
- Exemplos vivos no catálogo apresentam a matriz de layouts e slots.
|
|
292
|
+
- `opus check` continua reprovando slots fora da família.
|
|
293
|
+
- Um teste de API pública reprova a exportação de `SurfaceHeader` ou do módulo de anatomia.
|