@softize/opus 12.10.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.
Files changed (76) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/bin/lib/check.mjs +1103 -310
  3. package/bin/lib/copy.mjs +11 -0
  4. package/docs/adr/0003-dictionary-presentation-is-declared.md +3 -0
  5. package/docs/adr/0004-page-content-state-is-composed.md +65 -0
  6. package/docs/adr/0005-structural-surfaces-share-an-explicit-anatomy.md +97 -0
  7. package/docs/adr/0006-semantic-context-precedes-visual-variant.md +182 -0
  8. package/package.json +1 -1
  9. package/registry/instructions/opus.md +5 -0
  10. package/registry/skills/build-opus-ui/SKILL.md +27 -16
  11. package/registry/skills/build-opus-ui/references/evaluations.md +16 -5
  12. package/registry/skills/build-opus-ui/references/ui-patterns.md +38 -15
  13. package/registry/skills/model-opus-dictionary/SKILL.md +4 -2
  14. package/registry/skills/model-opus-dictionary/references/evaluations.md +4 -3
  15. package/registry/templates/app/src/App.tsx +1 -1
  16. package/src/core/dictionary.ts +52 -14
  17. package/src/core/index.ts +10 -0
  18. package/src/core/ui-context.ts +29 -0
  19. package/src/schema/drivers/zod.ts +17 -8
  20. package/src/ui/components/patterns/action-form-card.tsx +18 -12
  21. package/src/ui/components/patterns/confirm.tsx +26 -3
  22. package/src/ui/components/patterns/content-header.tsx +335 -61
  23. package/src/ui/components/patterns/data-state.tsx +23 -10
  24. package/src/ui/components/patterns/list.tsx +1096 -777
  25. package/src/ui/components/patterns/page-state.tsx +115 -0
  26. package/src/ui/components/patterns/page.tsx +231 -41
  27. package/src/ui/components/patterns/sidebar.tsx +354 -80
  28. package/src/ui/components/patterns/trigger.tsx +13 -9
  29. package/src/ui/components/patterns/view.tsx +7 -11
  30. package/src/ui/components/primitives/alert-dialog.tsx +7 -5
  31. package/src/ui/components/primitives/alert.tsx +298 -110
  32. package/src/ui/components/primitives/ask.tsx +2 -1
  33. package/src/ui/components/primitives/badge.tsx +91 -30
  34. package/src/ui/components/primitives/button.tsx +99 -60
  35. package/src/ui/components/primitives/calendar.tsx +39 -39
  36. package/src/ui/components/primitives/card.tsx +96 -23
  37. package/src/ui/components/primitives/detail.tsx +2 -2
  38. package/src/ui/components/primitives/dictionary-value.tsx +9 -14
  39. package/src/ui/components/primitives/dot.tsx +74 -21
  40. package/src/ui/components/primitives/drawer.tsx +33 -20
  41. package/src/ui/components/primitives/item.tsx +137 -81
  42. package/src/ui/components/primitives/menu.tsx +11 -3
  43. package/src/ui/components/primitives/metric-card.tsx +133 -0
  44. package/src/ui/components/primitives/table.tsx +2 -2
  45. package/src/ui/docs/DocBrowser.tsx +3 -3
  46. package/src/ui/docs/changelog.tsx +1 -1
  47. package/src/ui/docs/content/action-form-card.md +1 -1
  48. package/src/ui/docs/content/alert-dialog.md +8 -8
  49. package/src/ui/docs/content/alert.md +49 -25
  50. package/src/ui/docs/content/badge.md +18 -19
  51. package/src/ui/docs/content/button.md +12 -9
  52. package/src/ui/docs/content/card.md +5 -5
  53. package/src/ui/docs/content/content.md +44 -0
  54. package/src/ui/docs/content/customization.md +2 -2
  55. package/src/ui/docs/content/detail.md +5 -2
  56. package/src/ui/docs/content/dialog.md +2 -2
  57. package/src/ui/docs/content/dictionary-value.md +11 -10
  58. package/src/ui/docs/content/dot.md +7 -7
  59. package/src/ui/docs/content/drawer.md +6 -3
  60. package/src/ui/docs/content/input-group.md +3 -2
  61. package/src/ui/docs/content/item.md +47 -21
  62. package/src/ui/docs/content/menu.md +5 -4
  63. package/src/ui/docs/content/metric-card.md +41 -0
  64. package/src/ui/docs/content/page-state.md +45 -0
  65. package/src/ui/docs/content/page.md +48 -10
  66. package/src/ui/docs/content/semantic-context.md +63 -0
  67. package/src/ui/docs/content/sidebar.md +4 -4
  68. package/src/ui/docs/content/skeleton.md +2 -2
  69. package/src/ui/docs/content/table.md +3 -3
  70. package/src/ui/docs/content/tokens.md +28 -0
  71. package/src/ui/docs/doc-client.tsx +2 -2
  72. package/src/ui/docs/registry.tsx +596 -228
  73. package/src/ui/lib/semantic-context.ts +30 -0
  74. package/src/ui/meta.ts +292 -270
  75. package/src/ui/react.tsx +378 -111
  76. 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', tone: 'success' } }, presentation: 'stage' }}
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', tone: 'warning', icon: 'clock', description: 'Aguarda uma decisão da Fonte.' }, blocked: { label: 'Bloqueado', tone: 'danger' } }, presentation: 'status' }}
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', tone: 'success' },
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 | Variante |
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 `tone`. |
51
- | `status` | Situação operacional que muda com o tempo. | Badge | Tonal pelo `tone` da entrada; `neutral` sem tom. Nunca `outline`. |
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: tom, ícone e descrição
55
+ ## Por entrada: contexto, ícone e descrição
56
56
 
57
- - `tone` (`neutral` · `info` · `success` · `warning` · `danger`) só existe em status e estágio;
58
- `t.dict` rejeita o tom em classificação e em `plain`.
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', tone: 'success' } }, presentation: 'stage' }}
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; `variant`
4
- seleciona somente a intenção semântica. Quando a cor carrega significado, `label` fornece o nome
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 variant="success" label="Ativo" />
10
- <Dot variant="warning" label="Atenção" />
11
- <Dot variant="destructive" label="Falhou" />
12
- <Dot variant="info" label="Publicado" />
13
- <Dot variant="secondary" aria-hidden />
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
- <div className="px-4 text-sm text-muted-foreground">Campos, entrada/saída, arquivo…</div>
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
- <div className="space-y-2 px-4">
37
+ <DrawerBody className="space-y-2">
36
38
  <Label htmlFor="ws-nome">Nome</Label>
37
39
  <Input id="ws-nome" defaultValue="Empresa X" />
38
- </div>
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). |
@@ -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="default" className="ml-auto" aria-label="Enviar brief">
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
- | `variant` (InputGroupButton) | `'default' \| 'secondary' \| 'outline' \| 'ghost' \| 'destructive' \| 'link'` | `'ghost'` | Intenção do botão (herda do Button). O padrão ghost some na moldura; use default pra a ação primária. |
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: ItemMedia à esquerda, ItemContent (ItemTitle + ItemDescription) ocupando o meio e ItemActions à direita. variant=outline desenha a borda.
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
- <ItemContent>
12
+ <ItemHeader>
11
13
  <ItemTitle>empresa-x-api</ItemTitle>
12
- <ItemDescription>Repositório do backend do workspace Empresa X.</ItemDescription>
13
- </ItemContent>
14
+ <ItemDescription>
15
+ Repositório do backend do workspace Empresa X.
16
+ </ItemDescription>
17
+ </ItemHeader>
14
18
  <ItemActions>
15
- <Button size="sm" variant="outline">Abrir</Button>
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
- <ItemContent>
36
+ <ItemHeader>
31
37
  <ItemTitle>empresa-x-web</ItemTitle>
32
38
  <ItemDescription>Front do workspace Empresa X.</ItemDescription>
33
- </ItemContent>
39
+ </ItemHeader>
34
40
  <ItemActions>
35
- <Badge variant="success">sincronizado</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
- <ItemContent>
49
+ <ItemHeader>
44
50
  <ItemTitle>empresa-x-api</ItemTitle>
45
51
  <ItemDescription>Backend do workspace Empresa X.</ItemDescription>
46
- </ItemContent>
52
+ </ItemHeader>
47
53
  <ItemActions>
48
- <Badge variant="warning">3 sessões</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
- <ItemContent>
72
+ <ItemHeader>
67
73
  <ItemTitle>Empresa X</ItemTitle>
68
- <ItemDescription>Workspace com 2 repositórios e 3 agentes.</ItemDescription>
69
- </ItemContent>
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 | Tipo | Default | Descrição |
80
- |---|---|---|---|
81
- | `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. |
82
- | `variant` (Item) | `'default' \| 'outline' \| 'muted'` | `'default'` | O fundo da linha — default transparente, outline com borda, muted levemente tingido. |
83
- | `size` (Item) | `'default' \| 'sm'` | `'default'` | O respiro interno — sm aperta o padding pra listas densas. |
84
- | `asChild` (Item) | `boolean` | `false` | Funde o Item no filho (ex.: <a> ou <button>) — a linha inteira vira o alvo. |
85
- | `variant` (ItemMedia) | `'default' \| 'icon' \| 'image'` | `'default'` | A moldura da mídia — icon caixa quadrada com borda; image recorta a imagem; default sem moldura. |
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 variant destructive na ação que destrói.
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 variant="destructive"><Trash2 /> Excluir</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 variant="destructive"><Trash2 /> Excluir</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.variant` | `'default' \| 'destructive'` | `'default'` | destructive tonaliza texto e foco em vermelho — só pra ação que destrói. |
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. As ações ficam à direita e acompanham a base do conjunto formado pelo título e pela
5
- descrição. O container é centralizado e ocupa a largura disponível até `72rem` (`max-w-6xl`). Use
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 | Tipo | Default | Descrição |
33
- |---|---|---|---|
34
- | `title` | `string` | | O h1 da página. |
35
- | `count` | `number` | | Total de itens ao lado do título (mono, esmaecido). |
36
- | `description` | `ReactNode` | | Linha de contexto sob o título (ex.: "N no total · X ativos"). |
37
- | `actions` | `ReactNode` | | Ações à direita do cabeçalho, alinhadas à base do título e da descrição. |
38
- | `className` | `string` | `max-w-6xl` | Classes do container para substituir o teto padrão de `72rem`. |
39
- | `children` | `ReactNode` | | O conteúdo — espaçamento e diagramação são seus. |
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
- <PaneContent>
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
- </PaneContent>
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`, `PaneContent` 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.
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
- <PaneContent><SidebarNav groups={groups} activeId={active} onSelect={go} /></PaneContent>
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
- <CardContent className="space-y-2">
25
+ <CardBody className="space-y-2">
26
26
  <Skeleton className="h-4 w-full" />
27
27
  <Skeleton className="h-4 w-3/4" />
28
- </CardContent>
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 variant="info">Em sessão</Badge></TableCell>
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 variant="warning">Aguardando revisor</Badge></TableCell>
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 variant="success">Concluída</Badge></TableCell>
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
@@ -36,8 +36,8 @@ export const docWorkspaceStatus: LogicalTypeMeta = {
36
36
  params: {
37
37
  keys: [...STATUS],
38
38
  entries: {
39
- active: { label: 'Ativo', tone: 'success', description: 'Agentes em operação para o cliente.' },
40
- onboarding: { label: 'Onboarding', tone: 'warning' },
39
+ active: { label: 'Ativo', context: 'success', description: 'Agentes em operação para o cliente.' },
40
+ onboarding: { label: 'Onboarding', context: 'warning' },
41
41
  paused: { label: 'Pausado' },
42
42
  },
43
43
  presentation: 'status',