@softize/opus 12.9.0 → 12.11.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 +44 -0
- package/bin/lib/check.mjs +1103 -310
- package/bin/lib/copy.mjs +74 -6
- package/docs/adr/0003-dictionary-presentation-is-declared.md +3 -0
- package/docs/adr/0004-page-content-state-is-composed.md +65 -0
- package/docs/adr/0005-structural-surfaces-share-an-explicit-anatomy.md +97 -0
- package/docs/adr/0006-semantic-context-precedes-visual-variant.md +182 -0
- package/docs/code-style.md +6 -2
- package/package.json +1 -1
- package/registry/instructions/opus.md +5 -0
- package/registry/skills/build-opus-ui/SKILL.md +27 -16
- package/registry/skills/build-opus-ui/references/evaluations.md +16 -5
- package/registry/skills/build-opus-ui/references/ui-patterns.md +38 -15
- package/registry/skills/model-opus-dictionary/SKILL.md +4 -2
- package/registry/skills/model-opus-dictionary/references/evaluations.md +4 -3
- package/registry/templates/app/src/App.tsx +1 -1
- package/src/core/dictionary.ts +52 -14
- package/src/core/index.ts +10 -0
- package/src/core/ui-context.ts +29 -0
- package/src/schema/drivers/zod.ts +34 -19
- package/src/ui/components/patterns/action-form-card.tsx +18 -12
- package/src/ui/components/patterns/confirm.tsx +26 -3
- package/src/ui/components/patterns/content-header.tsx +335 -61
- package/src/ui/components/patterns/data-state.tsx +23 -10
- package/src/ui/components/patterns/form.tsx +1 -1
- package/src/ui/components/patterns/list.tsx +1096 -777
- package/src/ui/components/patterns/page-state.tsx +115 -0
- package/src/ui/components/patterns/page.tsx +231 -41
- package/src/ui/components/patterns/sidebar.tsx +354 -80
- package/src/ui/components/patterns/trigger.tsx +13 -9
- package/src/ui/components/patterns/view.tsx +7 -11
- package/src/ui/components/primitives/alert-dialog.tsx +7 -5
- package/src/ui/components/primitives/alert.tsx +298 -80
- package/src/ui/components/primitives/ask.tsx +2 -1
- package/src/ui/components/primitives/badge.tsx +91 -30
- package/src/ui/components/primitives/button.tsx +99 -60
- package/src/ui/components/primitives/calendar.tsx +39 -39
- package/src/ui/components/primitives/card.tsx +96 -23
- package/src/ui/components/primitives/detail.tsx +2 -2
- package/src/ui/components/primitives/dictionary-value.tsx +9 -14
- package/src/ui/components/primitives/dot.tsx +74 -21
- package/src/ui/components/primitives/drawer.tsx +33 -20
- package/src/ui/components/primitives/field.tsx +4 -4
- package/src/ui/components/primitives/item.tsx +137 -81
- package/src/ui/components/primitives/menu.tsx +11 -3
- package/src/ui/components/primitives/metric-card.tsx +133 -0
- package/src/ui/components/primitives/table.tsx +2 -2
- package/src/ui/components/primitives/tooltip.tsx +1 -1
- package/src/ui/docs/DocBrowser.tsx +3 -3
- package/src/ui/docs/changelog.tsx +1 -1
- package/src/ui/docs/content/action-form-card.md +1 -1
- package/src/ui/docs/content/alert-dialog.md +8 -8
- package/src/ui/docs/content/alert.md +54 -23
- package/src/ui/docs/content/badge.md +18 -19
- package/src/ui/docs/content/button.md +12 -9
- package/src/ui/docs/content/card.md +5 -5
- package/src/ui/docs/content/content.md +44 -0
- package/src/ui/docs/content/customization.md +2 -2
- package/src/ui/docs/content/detail.md +5 -2
- package/src/ui/docs/content/dialog.md +2 -2
- package/src/ui/docs/content/dictionary-value.md +11 -10
- package/src/ui/docs/content/dot.md +7 -7
- package/src/ui/docs/content/drawer.md +6 -3
- package/src/ui/docs/content/field.md +1 -1
- package/src/ui/docs/content/input-group.md +3 -2
- package/src/ui/docs/content/item.md +47 -21
- package/src/ui/docs/content/menu.md +5 -4
- package/src/ui/docs/content/metric-card.md +41 -0
- package/src/ui/docs/content/page-state.md +45 -0
- package/src/ui/docs/content/page.md +48 -10
- package/src/ui/docs/content/semantic-context.md +63 -0
- package/src/ui/docs/content/sidebar.md +4 -4
- package/src/ui/docs/content/skeleton.md +2 -2
- package/src/ui/docs/content/table.md +3 -3
- package/src/ui/docs/content/tokens.md +28 -0
- package/src/ui/docs/content/tooltip.md +15 -1
- package/src/ui/docs/doc-client.tsx +2 -2
- package/src/ui/docs/registry.tsx +596 -228
- package/src/ui/lib/semantic-context.ts +30 -0
- package/src/ui/meta.ts +292 -270
- package/src/ui/react.tsx +378 -111
- package/src/ui/theme.css +66 -0
|
@@ -9,11 +9,11 @@ aplica os defaults da apresentação declarada. O rótulo está sempre presente
|
|
|
9
9
|
value="pj"
|
|
10
10
|
/>
|
|
11
11
|
<DictionaryValue
|
|
12
|
-
dict={{ keys: ['prospect', 'customer'], entries: { prospect: { label: 'Prospect' }, customer: { label: 'Cliente',
|
|
12
|
+
dict={{ keys: ['prospect', 'customer'], entries: { prospect: { label: 'Prospect' }, customer: { label: 'Cliente', context: 'success' } }, presentation: 'stage' }}
|
|
13
13
|
value="customer"
|
|
14
14
|
/>
|
|
15
15
|
<DictionaryValue
|
|
16
|
-
dict={{ keys: ['open', 'blocked'], entries: { open: { label: 'Aberto',
|
|
16
|
+
dict={{ keys: ['open', 'blocked'], entries: { open: { label: 'Aberto', context: 'warning', icon: 'clock', description: 'Aguarda uma decisão da Fonte.' }, blocked: { label: 'Bloqueado', context: 'danger' } }, presentation: 'status' }}
|
|
17
17
|
value="open"
|
|
18
18
|
/>
|
|
19
19
|
<DictionaryValue
|
|
@@ -39,23 +39,23 @@ export const customerKindDict = t.dict(
|
|
|
39
39
|
export const customerStageDict = t.dict(
|
|
40
40
|
{
|
|
41
41
|
prospect: { label: 'Prospect', description: 'Relacionamento ainda em prospecção.' },
|
|
42
|
-
customer: { label: 'Cliente',
|
|
42
|
+
customer: { label: 'Cliente', context: 'success' },
|
|
43
43
|
},
|
|
44
44
|
{ doc: 'Estágio comercial atual da parte.', presentation: 'stage' },
|
|
45
45
|
)
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
| Papel | Quando | Forma |
|
|
48
|
+
| Papel | Quando | Forma | Contexto e variante |
|
|
49
49
|
|---|---|---|---|
|
|
50
|
-
| `classification` | Tipo, categoria, natureza — uma dimensão estável de comparação. | Badge | `outline`; ignora `
|
|
51
|
-
| `status` | Situação operacional que muda com o tempo. | Badge |
|
|
50
|
+
| `classification` | Tipo, categoria, natureza — uma dimensão estável de comparação. | Badge | `neutral` + `outline`; ignora `context`. |
|
|
51
|
+
| `status` | Situação operacional que muda com o tempo. | Badge | Contexto declarado pela entrada + `subtle`; `neutral` quando ausente. |
|
|
52
52
|
| `stage` | Etapa de um ciclo ou funil. | Badge | Igual a `status`. |
|
|
53
53
|
| `plain` ou ausente | Valor que só precisa ser legível. | Texto | — |
|
|
54
54
|
|
|
55
|
-
## Por entrada:
|
|
55
|
+
## Por entrada: contexto, ícone e descrição
|
|
56
56
|
|
|
57
|
-
- `
|
|
58
|
-
`t.dict` rejeita o
|
|
57
|
+
- `context` (`neutral` · `info` · `success` · `warning` · `danger`) só existe em status e estágio;
|
|
58
|
+
`t.dict` rejeita o contexto em classificação e em `plain`.
|
|
59
59
|
- `icon` é um nome do catálogo do Opus (`iconPickerIcons`). Nome fora do catálogo não renderiza
|
|
60
60
|
ícone; o renderer não inventa. Vocabulário próprio entra por `icons`.
|
|
61
61
|
- `description` é o texto curto para a pessoa e aparece em tooltip focável. Descrição igual ao
|
|
@@ -79,7 +79,7 @@ Os defaults vêm do dicionário; a tela sobrepõe só quando tem um motivo, e so
|
|
|
79
79
|
|
|
80
80
|
```tsx preview
|
|
81
81
|
<DictionaryValue
|
|
82
|
-
dict={{ keys: ['customer'], entries: { customer: { label: 'Cliente',
|
|
82
|
+
dict={{ keys: ['customer'], entries: { customer: { label: 'Cliente', context: 'success' } }, presentation: 'stage' }}
|
|
83
83
|
value="customer"
|
|
84
84
|
presentation="plain"
|
|
85
85
|
/>
|
|
@@ -113,6 +113,7 @@ uma sob a outra como texto secundário.
|
|
|
113
113
|
| `dict` | `DictType \| LogicalTypeMeta \| DictionaryDescriptor` | — | O dicionário (`t.dict`), a meta lida do schema ou um descritor normalizado. |
|
|
114
114
|
| `value` | `string \| null \| undefined` | — | O código. Vazio renderiza `fallback`. |
|
|
115
115
|
| `presentation` | `'classification' \| 'status' \| 'stage' \| 'plain'` | do dicionário | Sobrepõe o papel só nesta ocorrência. |
|
|
116
|
+
| `context` | contexto de `Badge` | pelo dicionário | Sobrepõe o significado semântico e promove texto a badge. |
|
|
116
117
|
| `variant` | variante de `Badge` | pelo papel | Sobrepõe a variante e promove texto a badge. |
|
|
117
118
|
| `icon` | `boolean \| ReactNode` | `true` | `false` esconde o ícone declarado; um nó substitui. |
|
|
118
119
|
| `icons` | `Record<string, LucideIcon>` | `iconPickerIcons` | Catálogo nome → componente. |
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
# Dot
|
|
2
2
|
|
|
3
|
-
Indicador visual compacto para estados que já têm contexto. O tamanho permanece fixo; `
|
|
4
|
-
seleciona
|
|
3
|
+
Indicador visual compacto para estados que já têm contexto. O tamanho permanece fixo; `context`
|
|
4
|
+
seleciona o significado e `variant` escolhe ponto sólido ou contornado. Quando a cor reforça significado, `label` fornece o nome
|
|
5
5
|
acessível. Sem `label`, o ponto é decorativo.
|
|
6
6
|
|
|
7
7
|
```tsx preview
|
|
8
8
|
<div className="flex items-center gap-4">
|
|
9
|
-
<Dot
|
|
10
|
-
<Dot
|
|
11
|
-
<Dot
|
|
12
|
-
<Dot
|
|
13
|
-
<Dot
|
|
9
|
+
<Dot context="success" label="Ativo" />
|
|
10
|
+
<Dot context="warning" label="Atenção" />
|
|
11
|
+
<Dot context="danger" label="Falhou" />
|
|
12
|
+
<Dot context="info" label="Publicado" />
|
|
13
|
+
<Dot context="neutral" aria-hidden />
|
|
14
14
|
</div>
|
|
15
15
|
```
|
|
16
16
|
|
|
@@ -14,7 +14,9 @@ side='right' desliza da borda direita. ESC, clique no overlay e o X fecham — s
|
|
|
14
14
|
<DrawerTitle>criarTicket</DrawerTitle>
|
|
15
15
|
<DrawerDescription>Ação · domínio Suporte</DrawerDescription>
|
|
16
16
|
</DrawerHeader>
|
|
17
|
-
<
|
|
17
|
+
<DrawerBody className="text-sm text-muted-foreground">
|
|
18
|
+
Campos, entrada/saída, arquivo…
|
|
19
|
+
</DrawerBody>
|
|
18
20
|
</DrawerContent>
|
|
19
21
|
</Drawer>
|
|
20
22
|
```
|
|
@@ -32,10 +34,10 @@ side aceita top|right|bottom|left. DrawerFooter ancora as ações no rodapé; Dr
|
|
|
32
34
|
<DrawerHeader>
|
|
33
35
|
<DrawerTitle>Editar workspace</DrawerTitle>
|
|
34
36
|
</DrawerHeader>
|
|
35
|
-
<
|
|
37
|
+
<DrawerBody className="space-y-2">
|
|
36
38
|
<Label htmlFor="ws-nome">Nome</Label>
|
|
37
39
|
<Input id="ws-nome" defaultValue="Empresa X" />
|
|
38
|
-
</
|
|
40
|
+
</DrawerBody>
|
|
39
41
|
<DrawerFooter>
|
|
40
42
|
<DrawerClose asChild>
|
|
41
43
|
<Button variant="ghost" size="sm">Cancelar</Button>
|
|
@@ -52,4 +54,5 @@ side aceita top|right|bottom|left. DrawerFooter ancora as ações no rodapé; Dr
|
|
|
52
54
|
|---|---|---|---|
|
|
53
55
|
| `DrawerContent.side` | `'top' \| 'right' \| 'bottom' \| 'left'` | `'right'` | Borda de onde o painel desliza. |
|
|
54
56
|
| `DrawerContent.showCloseButton` | `boolean` | `true` | Mostra o X de fechar no canto. Desligue pra painel que exige ação explícita. |
|
|
57
|
+
| `DrawerBody` | `HTMLAttributes<HTMLDivElement>` | Região flexível e rolável | Corpo principal entre header e footer. |
|
|
55
58
|
| `Drawer.open / onOpenChange` | `boolean / (open: boolean) => void` | | Modo controlado (Radix) — pra abrir/fechar por código (ex.: detalhe de um item selecionado). |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
## Campo vertical
|
|
2
2
|
|
|
3
|
-
A composição base: FieldLabel (htmlFor↔id), o controle e FieldDescription como ajuda — empilhados, com o respiro do Field.
|
|
3
|
+
A composição base: FieldLabel (htmlFor↔id), o controle e FieldDescription como ajuda — empilhados, com o respiro do Field. O rótulo usa a mesma hierarquia compacta de `DetailField`; quando o campo é inválido, muda explicitamente para o tom destrutivo.
|
|
4
4
|
|
|
5
5
|
```tsx preview col md
|
|
6
6
|
<Field>
|
|
@@ -60,7 +60,7 @@ Com InputGroupTextarea a moldura cresce; align=block-end empilha um rodapé de a
|
|
|
60
60
|
<Sparkles />
|
|
61
61
|
Título gerado pela IA
|
|
62
62
|
</InputGroupText>
|
|
63
|
-
<InputGroupButton size="sm" variant="
|
|
63
|
+
<InputGroupButton size="sm" context="primary" variant="solid" className="ml-auto" aria-label="Enviar brief">
|
|
64
64
|
Enviar
|
|
65
65
|
<ArrowUp />
|
|
66
66
|
</InputGroupButton>
|
|
@@ -74,5 +74,6 @@ Com InputGroupTextarea a moldura cresce; align=block-end empilha um rodapé de a
|
|
|
74
74
|
|---|---|---|---|
|
|
75
75
|
| `align` (InputGroupAddon) | `'inline-start' \| 'inline-end' \| 'block-start' \| 'block-end'` | `'inline-start'` | Onde o addon ancora: inline nas laterais; block empilha (vira coluna) acima ou abaixo — block exige a moldura alta de um InputGroupTextarea. |
|
|
76
76
|
| `size` (InputGroupButton) | `'xs' \| 'sm' \| 'icon-xs' \| 'icon-sm'` | `'xs'` | Tamanho do botão embutido. As variantes icon-* são quadradas, pra um botão só de ícone. |
|
|
77
|
-
| `
|
|
77
|
+
| `context` (InputGroupButton) | `'neutral' \| 'primary' \| 'danger'` | `'primary'` | Significado semântico herdado do Button. |
|
|
78
|
+
| `variant` (InputGroupButton) | `'solid' \| 'subtle' \| 'outline' \| 'ghost' \| 'link'` | `'ghost'` | Tratamento visual herdado do Button. O padrão ghost some na moldura; use `solid` para dar ênfase à ação. |
|
|
78
79
|
| `placeholder / defaultValue / value` (InputGroupInput, InputGroupTextarea) | `string` | | Props nativas do <input>/<textarea> repassadas ao controle. Controle como faria com Input/Textarea (value + onChange). |
|
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
## Básico
|
|
2
2
|
|
|
3
|
-
A composição completa
|
|
3
|
+
A composição completa usa `ItemMedia` à esquerda, `ItemHeader` (`ItemTitle` +
|
|
4
|
+
`ItemDescription`) no meio e `ItemActions` à direita. A mídia acompanha a altura útil do header,
|
|
5
|
+
mantendo ícone, imagem ou avatar centralizado. `variant="outline"` desenha a borda.
|
|
4
6
|
|
|
5
7
|
```tsx preview col
|
|
6
8
|
<Item variant="outline">
|
|
7
9
|
<ItemMedia variant="icon">
|
|
8
10
|
<FolderGit2 />
|
|
9
11
|
</ItemMedia>
|
|
10
|
-
<
|
|
12
|
+
<ItemHeader>
|
|
11
13
|
<ItemTitle>empresa-x-api</ItemTitle>
|
|
12
|
-
<ItemDescription>
|
|
13
|
-
|
|
14
|
+
<ItemDescription>
|
|
15
|
+
Repositório do backend do workspace Empresa X.
|
|
16
|
+
</ItemDescription>
|
|
17
|
+
</ItemHeader>
|
|
14
18
|
<ItemActions>
|
|
15
|
-
<Button size="sm" variant="outline">
|
|
19
|
+
<Button size="sm" variant="outline">
|
|
20
|
+
Abrir
|
|
21
|
+
</Button>
|
|
16
22
|
</ItemActions>
|
|
17
23
|
</Item>
|
|
18
24
|
```
|
|
@@ -27,12 +33,12 @@ A composição completa: ItemMedia à esquerda, ItemContent (ItemTitle + ItemDes
|
|
|
27
33
|
<ItemMedia variant="icon">
|
|
28
34
|
<GitBranch />
|
|
29
35
|
</ItemMedia>
|
|
30
|
-
<
|
|
36
|
+
<ItemHeader>
|
|
31
37
|
<ItemTitle>empresa-x-web</ItemTitle>
|
|
32
38
|
<ItemDescription>Front do workspace Empresa X.</ItemDescription>
|
|
33
|
-
</
|
|
39
|
+
</ItemHeader>
|
|
34
40
|
<ItemActions>
|
|
35
|
-
<Badge
|
|
41
|
+
<Badge context="success">Sincronizado</Badge>
|
|
36
42
|
</ItemActions>
|
|
37
43
|
</Item>
|
|
38
44
|
<ItemSeparator />
|
|
@@ -40,12 +46,12 @@ A composição completa: ItemMedia à esquerda, ItemContent (ItemTitle + ItemDes
|
|
|
40
46
|
<ItemMedia variant="icon">
|
|
41
47
|
<GitBranch />
|
|
42
48
|
</ItemMedia>
|
|
43
|
-
<
|
|
49
|
+
<ItemHeader>
|
|
44
50
|
<ItemTitle>empresa-x-api</ItemTitle>
|
|
45
51
|
<ItemDescription>Backend do workspace Empresa X.</ItemDescription>
|
|
46
|
-
</
|
|
52
|
+
</ItemHeader>
|
|
47
53
|
<ItemActions>
|
|
48
|
-
<Badge
|
|
54
|
+
<Badge context="warning">3 sessões</Badge>
|
|
49
55
|
</ItemActions>
|
|
50
56
|
</Item>
|
|
51
57
|
</ItemGroup>
|
|
@@ -63,10 +69,12 @@ asChild funde o Item num <a> — a linha inteira vira alvo (hover no fundo). siz
|
|
|
63
69
|
<AvatarFallback>AL</AvatarFallback>
|
|
64
70
|
</Avatar>
|
|
65
71
|
</ItemMedia>
|
|
66
|
-
<
|
|
72
|
+
<ItemHeader>
|
|
67
73
|
<ItemTitle>Empresa X</ItemTitle>
|
|
68
|
-
<ItemDescription>
|
|
69
|
-
|
|
74
|
+
<ItemDescription>
|
|
75
|
+
Workspace com 2 repositórios e 3 agentes.
|
|
76
|
+
</ItemDescription>
|
|
77
|
+
</ItemHeader>
|
|
70
78
|
<ItemActions>
|
|
71
79
|
<ChevronRight className="size-4 text-muted-foreground" />
|
|
72
80
|
</ItemActions>
|
|
@@ -74,12 +82,30 @@ asChild funde o Item num <a> — a linha inteira vira alvo (hover no fundo). siz
|
|
|
74
82
|
</Item>
|
|
75
83
|
```
|
|
76
84
|
|
|
85
|
+
## Corpo complementar
|
|
86
|
+
|
|
87
|
+
Use `ItemBody` quando a linha também apresenta um valor ou controle que não pertence ao título,
|
|
88
|
+
à descrição nem às ações.
|
|
89
|
+
|
|
90
|
+
```tsx preview col
|
|
91
|
+
<Item variant="outline">
|
|
92
|
+
<ItemHeader>
|
|
93
|
+
<ItemTitle>Nome</ItemTitle>
|
|
94
|
+
<ItemDescription>Como você aparece para as outras pessoas.</ItemDescription>
|
|
95
|
+
</ItemHeader>
|
|
96
|
+
<ItemBody>João</ItemBody>
|
|
97
|
+
</Item>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`ItemContent` permanece exportado somente para compatibilidade durante a versão 12. Código novo
|
|
101
|
+
usa `ItemHeader` ou `ItemBody` conforme o papel do conteúdo.
|
|
102
|
+
|
|
77
103
|
## Props
|
|
78
104
|
|
|
79
|
-
| Prop
|
|
80
|
-
|
|
81
|
-
| `variant` (ItemGroup) | `'plain' \| 'framed'`
|
|
82
|
-
| `variant` (Item)
|
|
83
|
-
| `size` (Item)
|
|
84
|
-
| `asChild` (Item)
|
|
85
|
-
| `variant` (ItemMedia) | `'default' \| 'icon' \| 'image'`
|
|
105
|
+
| Prop | Tipo | Default | Descrição |
|
|
106
|
+
| --------------------- | ----------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
107
|
+
| `variant` (ItemGroup) | `'plain' \| 'framed'` | `'plain'` | `framed` aplica moldura e superfície; `plain` mantém a composição livre. Os divisores são explícitos nos dois modos. |
|
|
108
|
+
| `variant` (Item) | `'default' \| 'outline' \| 'muted'` | `'default'` | O fundo da linha — default transparente, outline com borda, muted levemente tingido. |
|
|
109
|
+
| `size` (Item) | `'default' \| 'sm'` | `'default'` | O respiro interno — sm aperta o padding pra listas densas. |
|
|
110
|
+
| `asChild` (Item) | `boolean` | `false` | Funde o Item no filho (ex.: <a> ou <button>) — a linha inteira vira o alvo. |
|
|
111
|
+
| `variant` (ItemMedia) | `'default' \| 'icon' \| 'image'` | `'default'` | A moldura da mídia — `icon` tem largura estável e acompanha a altura do header; `image` recorta a imagem; `default` não adiciona fundo. |
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
## Menu de ações
|
|
2
2
|
|
|
3
|
-
Ícone à esquerda, atalho à direita (MenuShortcut), separador antes da zona perigosa e
|
|
3
|
+
Ícone à esquerda, atalho à direita (MenuShortcut), separador antes da zona perigosa e
|
|
4
|
+
`context="danger"` na ação que destrói.
|
|
4
5
|
|
|
5
6
|
```tsx preview
|
|
6
7
|
<Menu>
|
|
@@ -11,7 +12,7 @@
|
|
|
11
12
|
<MenuItem><Pencil /> Editar <MenuShortcut>⌘E</MenuShortcut></MenuItem>
|
|
12
13
|
<MenuItem><Copy /> Duplicar</MenuItem>
|
|
13
14
|
<MenuSeparator />
|
|
14
|
-
<MenuItem
|
|
15
|
+
<MenuItem context="danger"><Trash2 /> Excluir</MenuItem>
|
|
15
16
|
</MenuContent>
|
|
16
17
|
</Menu>
|
|
17
18
|
```
|
|
@@ -44,7 +45,7 @@ render(
|
|
|
44
45
|
<MenuItem><Pencil /> Renomear</MenuItem>
|
|
45
46
|
<MenuItem><Copy /> Duplicar</MenuItem>
|
|
46
47
|
<MenuSeparator />
|
|
47
|
-
<MenuItem
|
|
48
|
+
<MenuItem context="danger"><Trash2 /> Excluir</MenuItem>
|
|
48
49
|
</MenuContent>
|
|
49
50
|
</Menu>
|
|
50
51
|
</div>,
|
|
@@ -107,7 +108,7 @@ MenuSub aninha um nível; inset alinha itens sem ícone com os que têm.
|
|
|
107
108
|
|
|
108
109
|
| Prop | Tipo | Default | Descrição |
|
|
109
110
|
|---|---|---|---|
|
|
110
|
-
| `MenuItem.
|
|
111
|
+
| `MenuItem.context` | `'neutral' \| 'danger'` | `'neutral'` | `danger` sinaliza uma consequência perigosa. |
|
|
111
112
|
| `MenuItem.inset` | `boolean` | | Recuo à esquerda pra alinhar item sem ícone com os que têm. |
|
|
112
113
|
| `MenuContent.align / sideOffset` | `'start' \| 'center' \| 'end' / number` | `'center' / 4` | Alinhamento e distância em relação ao gatilho (Radix). |
|
|
113
114
|
| `MenuCheckboxItem.checked / onCheckedChange` | `boolean / (checked: boolean) => void` | | Estado do liga/desliga — controlado pelo consumidor. |
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
MetricCard apresenta uma medida resumida com hierarquia e espaçamento consistentes. Passe o
|
|
2
|
+
valor já calculado e formatado; busca de dados, recorte e regras de negócio permanecem no
|
|
3
|
+
consumidor.
|
|
4
|
+
|
|
5
|
+
```tsx preview col
|
|
6
|
+
<MetricCard
|
|
7
|
+
label="Total de cadastros"
|
|
8
|
+
value="286.317"
|
|
9
|
+
description="Prospects e clientes no cadastro global."
|
|
10
|
+
/>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Com ícone e ação
|
|
14
|
+
|
|
15
|
+
O ícone identifica visualmente a natureza da medida e é decorativo. `context="warning"` realça
|
|
16
|
+
somente o ícone; a superfície continua neutra. Use `action` apenas para uma ação diretamente
|
|
17
|
+
relacionada à medida. Um controle que explica o rótulo, como um tooltip, pertence a `labelAction`.
|
|
18
|
+
|
|
19
|
+
```tsx preview col
|
|
20
|
+
<MetricCard
|
|
21
|
+
label="Credencial"
|
|
22
|
+
value="Ausente"
|
|
23
|
+
description="Reconecte para autorizar novamente o acesso."
|
|
24
|
+
icon={<KeyRound />}
|
|
25
|
+
context="warning"
|
|
26
|
+
action={
|
|
27
|
+
<Button size="sm" variant="outline">
|
|
28
|
+
Reconectar
|
|
29
|
+
</Button>
|
|
30
|
+
}
|
|
31
|
+
/>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Carregamento
|
|
35
|
+
|
|
36
|
+
`loading` preserva a geometria do card com placeholders e marca a superfície como ocupada para
|
|
37
|
+
tecnologias assistivas. O contêiner da coleção continua responsável pela mensagem de carregamento.
|
|
38
|
+
|
|
39
|
+
```tsx preview col
|
|
40
|
+
<MetricCard loading />
|
|
41
|
+
```
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
## Estado integral da página
|
|
2
|
+
|
|
3
|
+
Use `PageState` quando carregamento, falha ou ausência substituírem todo o conteúdo principal. Na
|
|
4
|
+
forma curta ele pode ser escrito como filho direto de `Page`, que cria o `PageBody`; na forma
|
|
5
|
+
explícita, coloque-o dentro de `PageBody`. O cabeçalho continua visível e o estado recebe composição,
|
|
6
|
+
altura e semântica acessível consistentes.
|
|
7
|
+
|
|
8
|
+
```tsx preview col
|
|
9
|
+
<Page title="Relatório">
|
|
10
|
+
<PageState
|
|
11
|
+
status="error"
|
|
12
|
+
title="Não foi possível carregar o relatório"
|
|
13
|
+
description="Tente novamente. Se o problema continuar, volte mais tarde."
|
|
14
|
+
action={<Button variant="outline">Tentar novamente</Button>}
|
|
15
|
+
/>
|
|
16
|
+
</Page>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`loading` centraliza o `Spinner`; `error` compõe `Alert`; `empty` compõe `Empty`; e `ready` entrega
|
|
20
|
+
os filhos sem acrescentar uma superfície.
|
|
21
|
+
|
|
22
|
+
```tsx preview col
|
|
23
|
+
<Page title="Relatórios">
|
|
24
|
+
<PageState
|
|
25
|
+
status="empty"
|
|
26
|
+
title="Nenhum relatório"
|
|
27
|
+
description="Crie o primeiro relatório para começar."
|
|
28
|
+
action={<Button>Novo relatório</Button>}
|
|
29
|
+
/>
|
|
30
|
+
</Page>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Não use `PageState` para uma falha parcial. Se outra parte da página continua utilizável, mantenha o
|
|
34
|
+
estado na própria seção com `DataState`, `ActionView`, `ActionList` ou `Alert`.
|
|
35
|
+
|
|
36
|
+
## Props
|
|
37
|
+
|
|
38
|
+
| Prop | Tipo | Default | Descrição |
|
|
39
|
+
|---|---|---|---|
|
|
40
|
+
| `status` | `'loading' \| 'error' \| 'empty' \| 'ready'` | | Estado integral do conteúdo. |
|
|
41
|
+
| `title` | `ReactNode` | Texto seguro por estado | Situação reconhecível pela pessoa. |
|
|
42
|
+
| `description` | `ReactNode` | | Impacto ou próximo passo aplicável. |
|
|
43
|
+
| `icon` | `ReactNode` | Alerta no erro | Ícone decorativo do estado. |
|
|
44
|
+
| `action` | `ReactNode` | | Recuperação, seleção ou criação aplicável. |
|
|
45
|
+
| `children` | `ReactNode` | | Conteúdo renderizado somente em `ready`. |
|
|
@@ -1,11 +1,26 @@
|
|
|
1
1
|
## Esqueleto de página
|
|
2
2
|
|
|
3
3
|
Use `Page` para manter título, contexto, ações e conteúdo no mesmo ritmo visual nas telas do
|
|
4
|
-
back-office.
|
|
5
|
-
|
|
4
|
+
back-office. Em larguras amplas, as ações ficam no extremo oposto e acompanham a base do conjunto
|
|
5
|
+
formado pelo título e pela descrição; em larguras estreitas, passam para uma linha abaixo do
|
|
6
|
+
contexto. O container é centralizado e ocupa a largura disponível até `80rem` (`max-w-7xl`). Use
|
|
6
7
|
`className` somente quando a composição pedir explicitamente outro teto ou largura total. A área
|
|
7
8
|
abaixo do cabeçalho permanece livre para tabelas, cards ou outras composições.
|
|
8
9
|
|
|
10
|
+
A forma curta é o padrão para páginas comuns. Ela cria internamente `PageHeader` e `PageBody`;
|
|
11
|
+
portanto, não produz uma estrutura visual ou semântica diferente da forma explícita.
|
|
12
|
+
|
|
13
|
+
`Page` é o esqueleto de páginas e recursos delimitados. Ele também pode ocupar o painel principal
|
|
14
|
+
de um shell com sidebar; a navegação lateral não exige remover o teto nem reconstruir o cabeçalho.
|
|
15
|
+
Uma navegação contextual para outra página pode ocupar `actions`, e tabs ficam reservados a
|
|
16
|
+
recortes da mesma superfície. Canvas e outros workspaces espaciais imersivos podem usar um shell
|
|
17
|
+
próprio quando o cabeçalho reduzir a área útil ou duplicar controles persistentes da superfície.
|
|
18
|
+
|
|
19
|
+
Quando carregamento, falha ou ausência substituírem toda a área de conteúdo, use `PageState`. Na
|
|
20
|
+
forma curta ele pode ser escrito como filho direto, pois `Page` cria o `PageBody`; na composição
|
|
21
|
+
explícita, coloque-o dentro de `PageBody`. `Page` não recebe flags de dados: uma página pode agregar
|
|
22
|
+
fontes independentes e uma falha parcial não deve ocultar as demais seções.
|
|
23
|
+
|
|
9
24
|
```tsx preview col
|
|
10
25
|
render(
|
|
11
26
|
<div className="w-full overflow-hidden rounded-lg border border-border">
|
|
@@ -27,13 +42,36 @@ render(
|
|
|
27
42
|
)
|
|
28
43
|
```
|
|
29
44
|
|
|
45
|
+
## Composição explícita
|
|
46
|
+
|
|
47
|
+
Use os slots quando a página precisar compor o header diretamente. Não misture propriedades da
|
|
48
|
+
forma curta com `PageHeader` ou `PageBody`.
|
|
49
|
+
|
|
50
|
+
```tsx preview col
|
|
51
|
+
render(
|
|
52
|
+
<Page>
|
|
53
|
+
<PageHeader>
|
|
54
|
+
<PageTitle>Workspaces</PageTitle>
|
|
55
|
+
<PageMeta>3</PageMeta>
|
|
56
|
+
<PageDescription>Ambientes compartilhados pela equipe.</PageDescription>
|
|
57
|
+
<PageActions><Button><Plus /> Novo workspace</Button></PageActions>
|
|
58
|
+
</PageHeader>
|
|
59
|
+
<PageBody>
|
|
60
|
+
<div className="rounded-lg border border-dashed border-border p-10 text-center">
|
|
61
|
+
O conteúdo da página.
|
|
62
|
+
</div>
|
|
63
|
+
</PageBody>
|
|
64
|
+
</Page>,
|
|
65
|
+
)
|
|
66
|
+
```
|
|
67
|
+
|
|
30
68
|
## Props
|
|
31
69
|
|
|
32
|
-
| Prop
|
|
33
|
-
|
|
34
|
-
| `title`
|
|
35
|
-
| `count`
|
|
36
|
-
| `description` | `ReactNode` |
|
|
37
|
-
| `actions`
|
|
38
|
-
| `className`
|
|
39
|
-
| `children`
|
|
70
|
+
| Prop | Tipo | Default | Descrição |
|
|
71
|
+
| ------------- | ----------- | ----------- | ----------------------------------------------------------------------------------------------- |
|
|
72
|
+
| `title` | `string` | | O h1 da página. |
|
|
73
|
+
| `count` | `number` | | Total de itens ao lado do título (mono, esmaecido). |
|
|
74
|
+
| `description` | `ReactNode` | | Linha de contexto sob o título (ex.: "N no total · X ativos"). |
|
|
75
|
+
| `actions` | `ReactNode` | | Ações contextuais no extremo oposto do cabeçalho; em telas estreitas, ficam abaixo do contexto. |
|
|
76
|
+
| `className` | `string` | `max-w-7xl` | Classes do container para substituir o teto padrão de `80rem`. |
|
|
77
|
+
| `children` | `ReactNode` | | O body da página — espaçamento e diagramação são seus. |
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Contexto & Variante
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Contexto & Variante
|
|
6
|
+
|
|
7
|
+
Componentes semânticos separam significado de aparência. `context` responde por que o elemento
|
|
8
|
+
recebe destaque; `variant` escolhe como esse significado aparece. Essa ordem evita que nomes como
|
|
9
|
+
`success`, `warning` ou `danger` sejam tratados como decoração local.
|
|
10
|
+
|
|
11
|
+
## Contextos
|
|
12
|
+
|
|
13
|
+
| Contexto | Uso |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| `neutral` | Estado normal, inativo ou sem julgamento positivo ou negativo. |
|
|
16
|
+
| `primary` | Ação ou elemento de maior destaque no contexto atual. |
|
|
17
|
+
| `info` | Informação ou processo em andamento sem alerta. |
|
|
18
|
+
| `success` | Resultado positivo ou estado saudável. |
|
|
19
|
+
| `warning` | Condição que pede atenção, mas ainda não é uma falha. |
|
|
20
|
+
| `danger` | Falha, impedimento ou consequência perigosa. |
|
|
21
|
+
|
|
22
|
+
`light` e `dark` são temas, não contextos. `secondary` descreve hierarquia de ação em APIs antigas;
|
|
23
|
+
um estado sem destaque usa `neutral`. `destructive` continua sendo uma propriedade comportamental
|
|
24
|
+
de actions e confirmações; sua projeção visual usa `danger`.
|
|
25
|
+
|
|
26
|
+
## Variantes
|
|
27
|
+
|
|
28
|
+
`solid`, `subtle`, `outline`, `ghost` e `link` descrevem somente tratamento visual. Cada componente
|
|
29
|
+
aceita o subconjunto coerente com seu papel.
|
|
30
|
+
|
|
31
|
+
```tsx preview col
|
|
32
|
+
<div className="flex flex-wrap gap-2">
|
|
33
|
+
<Badge context="success" variant="solid">Concluído</Badge>
|
|
34
|
+
<Badge context="success" variant="subtle">Concluído</Badge>
|
|
35
|
+
<Badge context="success" variant="outline">Concluído</Badge>
|
|
36
|
+
</div>
|
|
37
|
+
<div className="flex flex-wrap gap-2">
|
|
38
|
+
<Button context="primary" variant="solid">Salvar</Button>
|
|
39
|
+
<Button context="neutral" variant="outline">Voltar</Button>
|
|
40
|
+
<Button context="danger" variant="ghost">Excluir</Button>
|
|
41
|
+
</div>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Dicionários
|
|
45
|
+
|
|
46
|
+
Status e estágios declaram `context` na entrada. A tela usa `DictionaryValue` ou `ActionList` e não
|
|
47
|
+
escolhe a aparência novamente.
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
const status = t.dict(
|
|
51
|
+
{
|
|
52
|
+
running: { label: 'Em andamento', context: 'info' },
|
|
53
|
+
completed: { label: 'Concluído', context: 'success' },
|
|
54
|
+
failed: { label: 'Falhou', context: 'danger' },
|
|
55
|
+
},
|
|
56
|
+
{ presentation: 'status' },
|
|
57
|
+
)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Compatibilidade
|
|
61
|
+
|
|
62
|
+
`tone` em dicionários e variantes semânticas antigas continuam aceitos durante a migração. Código
|
|
63
|
+
novo usa `context`; não misture o contrato antigo e o novo na mesma ocorrência.
|
|
@@ -9,13 +9,13 @@ render(
|
|
|
9
9
|
<Pane initialSize={32} minSize={20} inset="none">
|
|
10
10
|
<Sidebar divider={false} className="w-full">
|
|
11
11
|
<PaneHeader className="px-4 py-3 text-sm font-semibold">Projeto</PaneHeader>
|
|
12
|
-
<
|
|
12
|
+
<PaneBody>
|
|
13
13
|
<SidebarNav
|
|
14
14
|
groups={[{ label: 'Design', items: [{ id: 'preview', label: 'Preview' }, { id: 'code', label: 'Código' }] }]}
|
|
15
15
|
activeId="preview"
|
|
16
16
|
onSelect={() => {}}
|
|
17
17
|
/>
|
|
18
|
-
</
|
|
18
|
+
</PaneBody>
|
|
19
19
|
<PaneFooter><span className="text-xs text-muted-foreground">Configurações</span></PaneFooter>
|
|
20
20
|
</Sidebar>
|
|
21
21
|
</Pane>
|
|
@@ -27,7 +27,7 @@ render(
|
|
|
27
27
|
|
|
28
28
|
## Colapso
|
|
29
29
|
|
|
30
|
-
`collapsed` pertence à própria `Sidebar`; `SidebarItem`, `SidebarNav` e `ShellNav` adaptam-se automaticamente para botões `size-9` centralizados, ícones e tooltips. `PaneHeader`, `
|
|
30
|
+
`collapsed` pertence à própria `Sidebar`; `SidebarItem`, `SidebarNav` e `ShellNav` adaptam-se automaticamente para botões `size-9` centralizados, ícones e tooltips. `PaneHeader`, `PaneBody` e `PaneFooter` são os slots do pane: a aplicação mantém a identidade e ações que lhe pertencem sem atribuí-las artificialmente à sidebar.
|
|
31
31
|
|
|
32
32
|
`SidebarItem` também é a linha de árvores de navegação. `actions` e o controle formado por
|
|
33
33
|
`onToggle`/`expanded` são irmãos do botão principal, portanto menus e chevrons não criam
|
|
@@ -53,7 +53,7 @@ do peso médio e da cor atenuada, não de reduzir legibilidade.
|
|
|
53
53
|
```tsx
|
|
54
54
|
<Sidebar collapsed={collapsed}>
|
|
55
55
|
<PaneHeader><MySidebarHeader onToggle={() => setCollapsed(!collapsed)} /></PaneHeader>
|
|
56
|
-
<
|
|
56
|
+
<PaneBody><SidebarNav groups={groups} activeId={active} onSelect={go} /></PaneBody>
|
|
57
57
|
<PaneFooter><UserMenu /></PaneFooter>
|
|
58
58
|
</Sidebar>
|
|
59
59
|
```
|
|
@@ -22,10 +22,10 @@ O esqueleto reproduz o layout final do card — título, descrição, conteúdo
|
|
|
22
22
|
<Skeleton className="h-5 w-40" />
|
|
23
23
|
<Skeleton className="h-4 w-56" />
|
|
24
24
|
</CardHeader>
|
|
25
|
-
<
|
|
25
|
+
<CardBody className="space-y-2">
|
|
26
26
|
<Skeleton className="h-4 w-full" />
|
|
27
27
|
<Skeleton className="h-4 w-3/4" />
|
|
28
|
-
</
|
|
28
|
+
</CardBody>
|
|
29
29
|
<CardFooter className="gap-2">
|
|
30
30
|
<Skeleton className="h-8 w-28" />
|
|
31
31
|
<Skeleton className="h-8 w-24" />
|
|
@@ -17,19 +17,19 @@ A Table vem SEM borda externa — só as divisórias de linha (a última o Table
|
|
|
17
17
|
<TableRow>
|
|
18
18
|
<TableCell className="font-medium">Importar pedidos da transportadora</TableCell>
|
|
19
19
|
<TableCell>developer</TableCell>
|
|
20
|
-
<TableCell><Badge
|
|
20
|
+
<TableCell><Badge context="info">Em sessão</Badge></TableCell>
|
|
21
21
|
<TableCell className="text-right">42 min</TableCell>
|
|
22
22
|
</TableRow>
|
|
23
23
|
<TableRow>
|
|
24
24
|
<TableCell className="font-medium">Revisar contrato de rastreio</TableCell>
|
|
25
25
|
<TableCell>reviewer</TableCell>
|
|
26
|
-
<TableCell><Badge
|
|
26
|
+
<TableCell><Badge context="warning">Aguardando revisor</Badge></TableCell>
|
|
27
27
|
<TableCell className="text-right">18 min</TableCell>
|
|
28
28
|
</TableRow>
|
|
29
29
|
<TableRow>
|
|
30
30
|
<TableCell className="font-medium">Ajustar microcopy do painel</TableCell>
|
|
31
31
|
<TableCell>designer</TableCell>
|
|
32
|
-
<TableCell><Badge
|
|
32
|
+
<TableCell><Badge context="success">Concluída</Badge></TableCell>
|
|
33
33
|
<TableCell className="text-right">7 min</TableCell>
|
|
34
34
|
</TableRow>
|
|
35
35
|
</TableBody>
|
|
@@ -43,6 +43,34 @@ render(
|
|
|
43
43
|
)
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
+
## Famílias contextuais
|
|
47
|
+
|
|
48
|
+
Os tokens `primary`, `secondary` e `destructive` acima permanecem fundações de superfície. A API
|
|
49
|
+
dos componentes usa famílias contextuais para separar significado de tratamento visual: cada
|
|
50
|
+
família oferece sólido, foreground, superfície sutil, ênfase e borda. Use as props `context` e
|
|
51
|
+
`variant`; classes contextuais diretas ficam reservadas à implementação dos primitives.
|
|
52
|
+
|
|
53
|
+
```tsx preview
|
|
54
|
+
const CONTEXTS = ['neutral', 'primary', 'info', 'success', 'warning', 'danger']
|
|
55
|
+
render(
|
|
56
|
+
<div className="grid w-full gap-2 sm:grid-cols-2 lg:grid-cols-3">
|
|
57
|
+
{CONTEXTS.map((context) => (
|
|
58
|
+
<div
|
|
59
|
+
key={context}
|
|
60
|
+
className="rounded-md border px-3 py-2 text-sm"
|
|
61
|
+
style={{
|
|
62
|
+
backgroundColor: `var(--context-${context}-subtle)`,
|
|
63
|
+
borderColor: `var(--context-${context}-border)`,
|
|
64
|
+
color: `var(--context-${context}-emphasis)`,
|
|
65
|
+
}}
|
|
66
|
+
>
|
|
67
|
+
{context}
|
|
68
|
+
</div>
|
|
69
|
+
))}
|
|
70
|
+
</div>,
|
|
71
|
+
)
|
|
72
|
+
```
|
|
73
|
+
|
|
46
74
|
## Linhas e foco
|
|
47
75
|
|
|
48
76
|
> Bordas e o anel de foco também são tokens — nada de cinza hardcoded. A aresta de superfície
|