@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
@@ -0,0 +1,133 @@
1
+ import * as React from 'react'
2
+ import type { UiContext } from '../../../core/ui-context.ts'
3
+ import { cn } from '../../lib/cn.ts'
4
+ import { Card } from './card.tsx'
5
+ import { Skeleton } from './skeleton.tsx'
6
+
7
+ /** @deprecated Use `MetricCardContext`. */
8
+ export type MetricCardTone = 'default' | 'warning'
9
+ export type MetricCardContext = Exclude<UiContext, 'primary'>
10
+
11
+ const iconContextClasses: Record<MetricCardContext, string> = {
12
+ neutral: 'bg-context-neutral-subtle text-context-neutral-emphasis',
13
+ info: 'bg-context-info-subtle text-context-info-emphasis',
14
+ success: 'bg-context-success-subtle text-context-success-emphasis',
15
+ warning: 'bg-context-warning-subtle text-context-warning-emphasis',
16
+ danger: 'bg-context-danger-subtle text-context-danger-emphasis',
17
+ }
18
+
19
+ interface MetricCardBaseProps extends Omit<React.ComponentProps<typeof Card>, 'children'> {
20
+ /** Controle complementar exibido ao lado do rótulo, como uma explicação em tooltip. */
21
+ labelAction?: React.ReactNode
22
+ /** Contexto que explica recorte, proporção ou significado do valor. */
23
+ description?: React.ReactNode
24
+ /** Ícone decorativo que identifica a natureza da medida. */
25
+ icon?: React.ReactNode
26
+ /** Contexto semântico do ícone; não altera a superfície do card. */
27
+ context?: MetricCardContext
28
+ /** @deprecated Use `context`. Compatibilidade temporária da ADR 0006. */
29
+ tone?: MetricCardTone
30
+ /** Ação relacionada diretamente à medida. */
31
+ action?: React.ReactNode
32
+ }
33
+
34
+ export type MetricCardProps = MetricCardBaseProps &
35
+ (
36
+ | {
37
+ /** Nome curto da medida apresentada. */
38
+ label: React.ReactNode
39
+ /** Valor já formatado pelo consumidor. */
40
+ value: React.ReactNode
41
+ loading?: false
42
+ }
43
+ | {
44
+ /** O rótulo pode ser omitido porque o placeholder ocupa toda a composição. */
45
+ label?: React.ReactNode
46
+ /** O valor ainda não está disponível durante o carregamento. */
47
+ value?: React.ReactNode
48
+ /** Substitui o conteúdo por placeholders preservando a geometria do card. */
49
+ loading: true
50
+ }
51
+ )
52
+
53
+ /**
54
+ * MetricCard apresenta uma medida resumida. Formatação, cálculo e carregamento dos dados
55
+ * continuam sob responsabilidade do consumidor; o componente padroniza apenas a composição.
56
+ */
57
+ export function MetricCard({
58
+ label,
59
+ labelAction,
60
+ value,
61
+ description,
62
+ icon,
63
+ context,
64
+ tone,
65
+ action,
66
+ loading = false,
67
+ className,
68
+ ...props
69
+ }: MetricCardProps): React.ReactElement {
70
+ if (context !== undefined && tone !== undefined) {
71
+ throw new Error('MetricCard não permite combinar context com tone legado.')
72
+ }
73
+ const hasIcon = icon !== undefined && icon !== null
74
+ const resolvedContext = context ?? (tone === 'warning' ? 'warning' : 'neutral')
75
+
76
+ return (
77
+ <Card
78
+ data-slot="metric-card"
79
+ aria-busy={loading || undefined}
80
+ className={cn('min-w-0 p-4', className)}
81
+ {...props}
82
+ >
83
+ {loading ? (
84
+ <div className="space-y-3">
85
+ <Skeleton className="h-4 w-28" />
86
+ <Skeleton className="h-8 w-20" />
87
+ <Skeleton className="h-3 w-full" />
88
+ </div>
89
+ ) : (
90
+ <>
91
+ <div className="flex items-start justify-between gap-3">
92
+ <div className="flex min-w-0 items-center gap-1.5">
93
+ <div data-slot="metric-card-label" className="min-w-0 truncate text-sm font-medium text-muted-foreground">
94
+ {label}
95
+ </div>
96
+ {labelAction !== undefined && (
97
+ <div data-slot="metric-card-label-action" className="shrink-0">
98
+ {labelAction}
99
+ </div>
100
+ )}
101
+ </div>
102
+ {hasIcon && (
103
+ <span
104
+ data-slot="metric-card-icon"
105
+ data-context={resolvedContext}
106
+ aria-hidden="true"
107
+ className={cn(
108
+ 'flex size-8 shrink-0 items-center justify-center rounded-md [&_svg]:size-4',
109
+ iconContextClasses[resolvedContext],
110
+ )}
111
+ >
112
+ {icon}
113
+ </span>
114
+ )}
115
+ </div>
116
+ <div data-slot="metric-card-value" className="mt-3 text-2xl font-semibold tracking-tight tabular-nums">
117
+ {value}
118
+ </div>
119
+ {description !== undefined && (
120
+ <div data-slot="metric-card-description" className="mt-1 text-xs text-muted-foreground">
121
+ {description}
122
+ </div>
123
+ )}
124
+ {action !== undefined && (
125
+ <div data-slot="metric-card-action" className="mt-3">
126
+ {action}
127
+ </div>
128
+ )}
129
+ </>
130
+ )}
131
+ </Card>
132
+ )
133
+ }
@@ -78,7 +78,7 @@ function TableHead({ className, ...props }: React.ComponentProps<"th">) {
78
78
  <th
79
79
  data-slot="table-head"
80
80
  className={cn(
81
- "h-10 px-2 text-left align-middle font-medium whitespace-nowrap text-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[0.125rem]",
81
+ "h-10 px-3 text-left align-middle font-medium whitespace-nowrap text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[0.125rem]",
82
82
  className
83
83
  )}
84
84
  {...props}
@@ -91,7 +91,7 @@ function TableCell({ className, ...props }: React.ComponentProps<"td">) {
91
91
  <td
92
92
  data-slot="table-cell"
93
93
  className={cn(
94
- "p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[0.125rem]",
94
+ "px-3 py-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[0.125rem]",
95
95
  className
96
96
  )}
97
97
  {...props}
@@ -15,7 +15,7 @@
15
15
  import { useEffect } from 'react'
16
16
  import { DOC_SECTIONS, type DocSection, type DocEntry } from './registry'
17
17
  import { Pane, Split } from '../components/patterns/split.tsx'
18
- import { PaneContent, Sidebar, SidebarNav, type SidebarNavGroup } from '../components/patterns/sidebar.tsx'
18
+ import { PaneBody, Sidebar, SidebarNav, type SidebarNavGroup } from '../components/patterns/sidebar.tsx'
19
19
  import { navigate, usePathname } from '../router.ts'
20
20
 
21
21
  function findPage(sections: DocSection[], slug: string): DocEntry | undefined {
@@ -95,14 +95,14 @@ export function DocBrowser({
95
95
  <Split className="h-full">
96
96
  <Pane inset="none" className="w-56">
97
97
  <Sidebar className="w-full">
98
- <PaneContent>
98
+ <PaneBody>
99
99
  <SidebarNav
100
100
  groups={navGroups}
101
101
  activeId={page?.slug}
102
102
  navLabel="Navegação da documentação"
103
103
  onSelect={(slug) => go(`${basePath}/${slug}`)}
104
104
  />
105
- </PaneContent>
105
+ </PaneBody>
106
106
  </Sidebar>
107
107
  </Pane>
108
108
  <Pane key={page?.slug} grow inset="none" className="overflow-y-auto">
@@ -54,7 +54,7 @@ export function ChangelogView({ source }: { source: string }): React.ReactElemen
54
54
  <div className="flex flex-wrap items-baseline gap-x-3 gap-y-1">
55
55
  <h2 className="text-xl font-semibold tracking-tight">{v.version}</h2>
56
56
  {v.date !== undefined && <span className="text-sm text-muted-foreground">{v.date}</span>}
57
- {v.breaking !== undefined && <Badge variant="destructive">Breaking</Badge>}
57
+ {v.breaking !== undefined && <Badge context="danger" variant="solid">Breaking</Badge>}
58
58
  </div>
59
59
  {v.body.length > 0 && (
60
60
  <div className="mt-3">
@@ -20,5 +20,5 @@ faixa de modal). Pra estruturar uma seção da página como painel. O título é
20
20
  |---|---|---|---|
21
21
  | `title` | `string` | | Título do header (com divisor embaixo). Sem ele, o card começa direto no corpo. |
22
22
  | `description` | `string` | | Subtítulo opcional, abaixo do título. |
23
- | `action / defaultValues / fieldOptions / submitLabel / onSuccess / onCancel` | `— (iguais ao ActionForm)` | | O resto é o ActionForm — o card injeta o conteúdo (CardContent) e o rodapé (CardFooter) por baixo. |
23
+ | `action / defaultValues / fieldOptions / submitLabel / onSuccess / onCancel` | `— (iguais ao ActionForm)` | | O resto é o ActionForm — o card injeta o corpo (CardBody) e o rodapé (CardFooter) por baixo. |
24
24
  | `cardClassName` | `string` | | Classes da SUPERFÍCIE do card (ex.: largura). `className` vai pro `<form>`. |
@@ -38,15 +38,15 @@ Dialog, **não fecha clicando fora** — exige uma escolha.
38
38
  ## A estrutura e o botão destrutivo
39
39
 
40
40
  `AlertDialogHeader` (media/título/descrição) + `AlertDialogFooter` (as ações).
41
- `variant="destructive"` no `AlertDialogAction` (ele herda variant/size do Button) pinta o
42
- confirmar de vermelho. Uma confirmação destrutiva **binária** é só
43
- `dialog.confirm({ variant: 'destructive' })` — o exemplo abaixo é só pra mostrar a
44
- composição e onde o `variant` entra:
41
+ `context="danger"` no `AlertDialogAction` (ele herda contexto, variante e tamanho do Button)
42
+ comunica que a confirmação é perigosa. Uma confirmação destrutiva **binária** é só
43
+ `dialog.confirm({ context: 'danger' })` — o exemplo abaixo é só para mostrar a composição e
44
+ onde o `context` entra:
45
45
 
46
46
  ```tsx preview
47
47
  <AlertDialog>
48
48
  <AlertDialogTrigger asChild>
49
- <Button variant="destructive">Excluir repositório</Button>
49
+ <Button context="danger">Excluir repositório</Button>
50
50
  </AlertDialogTrigger>
51
51
  <AlertDialogContent>
52
52
  <AlertDialogHeader>
@@ -57,7 +57,7 @@ composição e onde o `variant` entra:
57
57
  </AlertDialogHeader>
58
58
  <AlertDialogFooter>
59
59
  <AlertDialogCancel>Cancelar</AlertDialogCancel>
60
- <AlertDialogAction variant="destructive">Excluir</AlertDialogAction>
60
+ <AlertDialogAction context="danger">Excluir</AlertDialogAction>
61
61
  </AlertDialogFooter>
62
62
  </AlertDialogContent>
63
63
  </AlertDialog>
@@ -69,5 +69,5 @@ composição e onde o `variant` entra:
69
69
  |---|---|---|---|
70
70
  | `open` (AlertDialog) | `boolean` | | Estado de aberto no modo controlado — pareie com onOpenChange. No padrão, o Trigger cuida disso. |
71
71
  | `onOpenChange` (AlertDialog) | `(open: boolean) => void` | | Chamado quando o diálogo abre ou fecha (Trigger, Cancel, Action ou Esc). |
72
- | `variant` (AlertDialogAction) | `'default' \| 'destructive' \| 'outline' \| 'secondary' \| 'ghost' \| 'link'` | `'default'` | Intenção do botão de confirmar (herdada do Button) — destructive pra ação perigosa. |
73
- | `variant` (AlertDialogCancel) | `'default' \| 'destructive' \| 'outline' \| 'secondary' \| 'ghost' \| 'link'` | `'ghost'` | Intenção do botão de cancelar (herdada do Button). |
72
+ | `context` (Action/Cancel) | `'neutral' \| 'primary' \| 'danger'` | `primary` / `neutral` | Significado semântico herdado do Button. |
73
+ | `variant` (Action/Cancel) | `'solid' \| 'subtle' \| 'outline' \| 'ghost' \| 'link'` | `solid` / `ghost` | Tratamento visual herdado do Button. |
@@ -5,19 +5,19 @@ Quase todo alert é ícone + título + uma frase — então isso é UMA linha: `
5
5
  ```tsx preview col
6
6
  <Alert icon={<Info />} title="Opus 2.8.0" description="Este workspace usa a versão pinada em opus.json." />
7
7
  <Alert
8
- variant="destructive"
8
+ context="danger"
9
9
  icon={<CircleAlert />}
10
10
  title="Sessão encerrada"
11
11
  description="O agente parou antes de concluir. Veja o detalhe no log da sessão."
12
12
  />
13
13
  <Alert
14
- variant="success"
14
+ context="success"
15
15
  icon={<CircleCheck />}
16
16
  title="Skill publicada"
17
17
  description="Os agentes do workspace já enxergam a nova versão."
18
18
  />
19
19
  <Alert
20
- variant="warning"
20
+ context="warning"
21
21
  icon={<CircleAlert />}
22
22
  title="Revisão necessária"
23
23
  description="Confira os dados antes de continuar."
@@ -26,51 +26,75 @@ Quase todo alert é ícone + título + uma frase — então isso é UMA linha: `
26
26
 
27
27
  ## Só a frase
28
28
 
29
- Título é opcional — o aviso de uma linha dispensa. `destructive` e `success` mantêm superfície tonalizada + borda (divergência da casa: o shadcn rebaixou destructive pra `bg-card`, nós preservamos o realce).
29
+ Título é opcional — o aviso de uma linha dispensa. O contexto continua visível por superfície,
30
+ borda e texto; o conteúdo comunica o significado sem depender somente da cor.
30
31
 
31
32
  ```tsx preview col
32
33
  <Alert description="Nenhuma sessão aberta neste repositório." />
33
- <Alert variant="success" icon={<CircleCheck />} description="Workspace Empresa X sincronizado." />
34
+ <Alert context="success" icon={<CircleCheck />} description="Workspace Empresa X sincronizado." />
34
35
  ```
35
36
 
36
- ## Ícone é opcional
37
+ ## Mídia é opcional
37
38
 
38
- Sem `icon` o alert é bloco comum; com ele, vira grid de duas colunas e o conteúdo se alinha ao lado. O ícone da prop recebe o slot `alert-icon` e um realce tonal coerente com a variante. A composição legada com um SVG direto continua abrindo a coluna do ícone. Texto solto como filho também vale (`<Alert>Sincronizado.</Alert>`) — cai no slot de descrição sozinho.
39
+ Sem `icon` o alert mantém somente a coluna de texto. Com ele, a forma curta materializa
40
+ `AlertMedia` à esquerda e `AlertHeader` à direita. A mídia tem largura estável e acompanha a
41
+ altura útil do header; com título e descrição de uma linha, a moldura termina junto do texto,
42
+ sem sobra inferior. Texto solto como filho também vale (`<Alert>Sincronizado.</Alert>`) e se torna
43
+ uma descrição.
39
44
 
40
45
  ```tsx preview col
41
46
  <Alert title="Sem provider próprio" description="As conversas usam o padrão do sistema." />
42
- <Alert variant="success">Workspace Empresa X sincronizado.</Alert>
47
+ <Alert context="success">Workspace Empresa X sincronizado.</Alert>
43
48
  ```
44
49
 
45
50
  ## Composição (conteúdo rico)
46
51
 
47
- Quando a descrição tem mais que uma frase — parágrafos, lista, um botão componha: `AlertTitle` e `AlertDescription` seguem exportados, e o espaço entre BLOCOS filhos vem de graça.
52
+ Quando a mensagem precisa de conteúdo rico, componha os slots da família. `AlertMedia`,
53
+ `AlertHeader` e `AlertActions` são filhos diretos de `Alert`; `AlertTitle` e
54
+ `AlertDescription` pertencem ao header.
48
55
 
49
56
  ```tsx preview col
50
- <Alert variant="destructive" icon={<CircleAlert />}>
51
- <AlertTitle>Não deu pra publicar</AlertTitle>
52
- <AlertDescription>
53
- <p>O registry recusou a versão 3.0.0 — ela já existe.</p>
54
- <p>Suba o patch e tente de novo.</p>
55
- </AlertDescription>
57
+ <Alert context="danger">
58
+ <AlertMedia>
59
+ <CircleAlert />
60
+ </AlertMedia>
61
+ <AlertHeader>
62
+ <AlertTitle>Não deu pra publicar</AlertTitle>
63
+ <AlertDescription>
64
+ <p>O registry recusou a versão 3.0.0 — ela já existe.</p>
65
+ <p>Suba o patch e tente de novo.</p>
66
+ </AlertDescription>
67
+ </AlertHeader>
68
+ <AlertActions>
69
+ <Button size="sm" variant="outline">
70
+ Tentar novamente
71
+ </Button>
72
+ </AlertActions>
56
73
  </Alert>
57
74
  ```
58
75
 
59
76
  Os dois modos convivem: com `title`/`description` preenchidos, `children` entra DEPOIS da frase — é onde vai a ação.
60
77
 
61
78
  ```tsx preview col
62
- <Alert icon={<CircleAlert />} title="Sessão presa" description="O ambiente não subiu no tempo esperado.">
63
- <Button size="sm" variant="outline">Reiniciar</Button>
79
+ <Alert
80
+ icon={<CircleAlert />}
81
+ title="Sessão presa"
82
+ description="O ambiente não subiu no tempo esperado."
83
+ >
84
+ <Button size="sm" variant="outline">
85
+ Reiniciar
86
+ </Button>
64
87
  </Alert>
65
88
  ```
66
89
 
67
90
  ## Props
68
91
 
69
- | Prop | Tipo | Default | Descrição |
70
- |---|---|---|---|
71
- | `title` | `React.ReactNode` | | Título do alert (a forma curta). Não é o atributo `title` do HTML — esse é tooltip nativo, banido na casa, e o componente não o aceita. |
72
- | `description` | `React.ReactNode` | | A frase. Sozinha, dispensa título. |
73
- | `icon` | `React.ReactNode` | | Ícone à esquerda; liga o layout de duas colunas e recebe realce tonal no slot `alert-icon`. Decorativo — quem nomeia é o título. |
74
- | `variant` | `'default' \| 'destructive' \| 'success' \| 'warning'` | `'default'` | O tom da mensagem e do realce do ícone. |
75
- | `children` | `React.ReactNode` | | Composição (`AlertTitle`/`AlertDescription`), texto cru, ou junto da forma curta — o que vem depois da frase. |
76
- | `style` | `React.CSSProperties` | | Ajuste excepcional das medidas internas; com `icon`, pode sobrescrever as colunas e os espaçamentos padrão. |
92
+ | Prop | Tipo | Default | Descrição |
93
+ | ------------- | ----------------------------------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------- |
94
+ | `title` | `React.ReactNode` | | Título do alert (a forma curta). Não é o atributo `title` do HTML — esse é tooltip nativo, banido na casa, e o componente não o aceita. |
95
+ | `description` | `React.ReactNode` | | A frase. Sozinha, dispensa título. |
96
+ | `icon` | `React.ReactNode` | | Ícone à esquerda; materializa `AlertMedia` com realce tonal. Decorativo — quem nomeia é o título. |
97
+ | `context` | `'neutral' \| 'info' \| 'success' \| 'warning' \| 'danger'` | `'neutral'` | O significado da mensagem e do realce do ícone. |
98
+ | `variant` | `'subtle' \| 'outline'` | `'subtle'` | O tratamento visual aplicado ao contexto. |
99
+ | `children` | `React.ReactNode` | | Composição explícita, texto cru ou, junto da forma curta, ações exibidas após a mensagem. |
100
+ | `style` | `React.CSSProperties` | | Ajuste excepcional da raiz; medidas escaláveis usam `rem`. |
@@ -1,27 +1,25 @@
1
- ## Intenção
1
+ ## Contexto e variante
2
2
 
3
- As quatro variantes do shadcn: categoria, papel, alerta. Badge é não-interativo — pra clique, use
4
- Button.
3
+ Badge é não-interativo — para clique, use Button. `context` comunica o significado; `variant`
4
+ escolhe entre preenchimento sólido, superfície sutil ou contorno.
5
5
 
6
6
  ```tsx preview
7
- <Badge>Agente</Badge>
8
- <Badge variant="secondary">Skill</Badge>
9
- <Badge variant="outline">Monorepo</Badge>
10
- <Badge variant="destructive">Arquivado</Badge>
7
+ <Badge context="neutral">Agente</Badge>
8
+ <Badge context="neutral" variant="outline">Monorepo</Badge>
9
+ <Badge context="danger" variant="solid">Arquivado</Badge>
11
10
  ```
12
11
 
13
- ## Tons de status da casa
12
+ ## Contextos de status
14
13
 
15
- success/warning/info/danger (fill tingido + borda translúcida) convenção da casa pra estado
16
- semântico, igual ao KindBadge do Maestro. Não-interativos. `danger` é o tom tonal da família;
17
- `destructive` continua sólido, pra alerta e ação. Valor de dicionário com papel declarado usa
18
- `DictionaryValue`, que escolhe o tom pela metadata em vez de repetir esta escolha em cada tela.
14
+ `success`, `warning`, `info` e `danger` usam `subtle` por padrão. Valor de dicionário com papel
15
+ declarado usa `DictionaryValue`, que escolhe o contexto pela metadata em vez de repetir esta
16
+ decisão em cada tela.
19
17
 
20
18
  ```tsx preview
21
- <Badge variant="success">Ativa</Badge>
22
- <Badge variant="warning">Aguardando revisor</Badge>
23
- <Badge variant="info">Em sessão</Badge>
24
- <Badge variant="danger">Bloqueada</Badge>
19
+ <Badge context="success">Ativa</Badge>
20
+ <Badge context="warning">Aguardando revisor</Badge>
21
+ <Badge context="info">Em sessão</Badge>
22
+ <Badge context="danger">Bloqueada</Badge>
25
23
  ```
26
24
 
27
25
  ## Com ícone
@@ -29,7 +27,7 @@ semântico, igual ao KindBadge do Maestro. Não-interativos. `danger` é o tom t
29
27
  Um svg filho ganha size-3 automaticamente — bom pra reforçar o estado sem crescer o rótulo.
30
28
 
31
29
  ```tsx preview
32
- <Badge variant="success"><CircleCheck /> Regressão verde</Badge>
30
+ <Badge context="success"><CircleCheck /> Regressão verde</Badge>
33
31
  ```
34
32
 
35
33
  ## Como link (asChild)
@@ -38,7 +36,7 @@ asChild renderiza o filho (Radix Slot) — um `<a>` com cara de badge, com hover
38
36
  variantes.
39
37
 
40
38
  ```tsx preview
41
- <Badge asChild variant="secondary">
39
+ <Badge asChild context="neutral" variant="solid">
42
40
  <a href="#" onClick={(e) => e.preventDefault()}>Empresa X</a>
43
41
  </Badge>
44
42
  ```
@@ -47,5 +45,6 @@ variantes.
47
45
 
48
46
  | Prop | Tipo | Default | Descrição |
49
47
  |---|---|---|---|
50
- | `variant` | `'default' \| 'secondary' \| 'destructive' \| 'outline' \| 'success' \| 'warning' \| 'info' \| 'danger'` | `'default'` | Intenção (default/secondary/destructive/outline) ou tom de status da casa (success/warning/info/danger). |
48
+ | `context` | `'neutral' \| 'primary' \| 'info' \| 'success' \| 'warning' \| 'danger'` | `'neutral'` | O significado ou destaque contextual. |
49
+ | `variant` | `'solid' \| 'subtle' \| 'outline'` | `'subtle'` | O tratamento visual aplicado ao contexto. |
51
50
  | `asChild` | `boolean` | `false` | Renderiza como o filho (Radix Slot) em vez de `<span>` — ex.: um `<a>` com cara de badge. |
@@ -1,15 +1,17 @@
1
- ## Variantes
1
+ ## Contexto e variante
2
2
 
3
- variant é intenção, não decoração: default pra ação principal (uma por tela), destructive
4
- quando destrói de verdade.
3
+ `context` declara a hierarquia ou o risco da ação; `variant` escolhe o tratamento visual. Use
4
+ `primary` para a ação principal, `neutral` para ações de apoio e `danger` quando a ação tiver uma
5
+ consequência perigosa. `default`, `secondary` e `destructive` permanecem apenas como aliases de
6
+ compatibilidade.
5
7
 
6
8
  ```tsx preview
7
9
  <Button>Criar workspace</Button>
8
- <Button variant="secondary">Duplicar</Button>
9
- <Button variant="outline">Ver prévia</Button>
10
- <Button variant="ghost">Cancelar</Button>
11
- <Button variant="destructive">Excluir</Button>
12
- <Button variant="link">Ver documentação</Button>
10
+ <Button context="neutral" variant="solid">Duplicar</Button>
11
+ <Button context="neutral" variant="outline">Ver prévia</Button>
12
+ <Button context="neutral" variant="ghost">Cancelar</Button>
13
+ <Button context="danger" variant="solid">Excluir</Button>
14
+ <Button context="primary" variant="link">Ver documentação</Button>
13
15
  ```
14
16
 
15
17
  ## Tamanhos
@@ -53,7 +55,8 @@ buttonVariants serve pro caso sem filho único.
53
55
 
54
56
  | Prop | Tipo | Default | Descrição |
55
57
  |---|---|---|---|
56
- | `variant` | `'default' \| 'secondary' \| 'outline' \| 'ghost' \| 'destructive' \| 'link'` | `'default'` | A intenção da ação define cor e peso visual. |
58
+ | `context` | `'neutral' \| 'primary' \| 'danger'` | `'primary'` | A hierarquia ou o risco comunicado pela ação. |
59
+ | `variant` | `'solid' \| 'subtle' \| 'outline' \| 'ghost' \| 'link'` | `'solid'` | O tratamento visual aplicado ao contexto. |
57
60
  | `size` | `'default' \| 'sm' \| 'lg' \| 'icon' \| 'icon-sm' \| 'icon-xs'` | `'default'` | O tamanho. Os icon* são quadrados (2.25/2/1.5rem) para botões só de ícone, com `aria-label`. |
58
61
  | `asChild` | `boolean` | `false` | Renderiza como o filho (Radix Slot) em vez de `<button>` — pra âncoras e afins. |
59
62
  | `busy` | `boolean` | `false` | Ação em andamento (depois do clique): mostra Spinner + desabilita. Não é "carregando" de conteúdo (que é Spinner/Skeleton num nível de página). |
@@ -18,9 +18,9 @@ Pra painel com estrutura: cada slot é dono do próprio padding (como o Dialog).
18
18
  <CardTitle>Empresa X</CardTitle>
19
19
  <CardDescription>2 repositórios · 3 agentes vinculados.</CardDescription>
20
20
  </CardHeader>
21
- <CardContent>
21
+ <CardBody>
22
22
  <p className="text-sm text-muted-foreground">Última sessão concluída há 2 horas.</p>
23
- </CardContent>
23
+ </CardBody>
24
24
  <CardFooter className="gap-2">
25
25
  <Button size="sm">Abrir sessão</Button>
26
26
  <Button size="sm" variant="outline">Ver repositórios</Button>
@@ -38,11 +38,11 @@ Pra painel com estrutura: cada slot é dono do próprio padding (como o Dialog).
38
38
  <CardTitle>Skill code-style</CardTitle>
39
39
  <CardDescription>Padrão softize de código e microcopy.</CardDescription>
40
40
  </CardHeader>
41
- <CardContent>
41
+ <CardBody>
42
42
  <p className="text-sm text-muted-foreground">Vinculada a 4 agentes neste workspace.</p>
43
- </CardContent>
43
+ </CardBody>
44
44
  <CardFooter className="justify-between border-t">
45
- <Badge variant="success">Publicada</Badge>
45
+ <Badge context="success">Publicada</Badge>
46
46
  <Button size="sm" variant="outline">Editar skill</Button>
47
47
  </CardFooter>
48
48
  </Card>
@@ -0,0 +1,44 @@
1
+ ## Região de conteúdo
2
+
3
+ Use `Content` para dar título e estrutura a uma região dentro de `PageBody`, `CardBody`,
4
+ `DialogBody` ou outra superfície. Ele renderiza uma `section` ligada ao próprio título e mantém o
5
+ espaçamento entre header e body. `ContentHeader` pertence sempre a essa estrutura; não o use solto.
6
+
7
+ No caso comum, prefira o shorthand:
8
+
9
+ ```tsx preview col
10
+ render(
11
+ <Content
12
+ title="Dispositivos conectados"
13
+ description="Sessões com acesso à sua conta."
14
+ actions={<Button variant="outline">Encerrar outras sessões</Button>}
15
+ >
16
+ <div className="rounded-lg border border-border p-4">MacBook Pro · ativo agora</div>
17
+ </Content>,
18
+ )
19
+ ```
20
+
21
+ ## Composição explícita
22
+
23
+ Use os slots quando o header precisar de composição própria. A árvore aceita exatamente um
24
+ `ContentHeader` e um `ContentBody` como filhos diretos. O header exige um `ContentTitle` e aceita
25
+ uma descrição, um metadado e uma região de ações.
26
+
27
+ ```tsx preview col
28
+ render(
29
+ <Content level={2}>
30
+ <ContentHeader>
31
+ <ContentTitle>Dispositivos conectados</ContentTitle>
32
+ <ContentMeta>3</ContentMeta>
33
+ <ContentDescription>Sessões com acesso à sua conta.</ContentDescription>
34
+ <ContentActions><Button variant="outline">Atualizar</Button></ContentActions>
35
+ </ContentHeader>
36
+ <ContentBody>
37
+ <div className="rounded-lg border border-border p-4">MacBook Pro · ativo agora</div>
38
+ </ContentBody>
39
+ </Content>,
40
+ )
41
+ ```
42
+
43
+ As duas formas geram os mesmos elementos, estilos e `data-slot`. `opus check` reprova slots fora
44
+ do pai correto, filhos estruturais indiretos e a mistura de shorthand com composição explícita.
@@ -59,7 +59,7 @@ de 16, sem transformar esse valor em uma regra da biblioteca.
59
59
  // Slots: só o que a tela pede.
60
60
  <Card>
61
61
  <CardHeader><CardTitle>Workspace</CardTitle></CardHeader>
62
- <CardContent>…</CardContent>
62
+ <CardBody>…</CardBody>
63
63
  </Card>
64
64
 
65
65
  // asChild: o filho VIRA o botão (sem forkar estilo).
@@ -110,7 +110,7 @@ const [open, setOpen] = useState(false)
110
110
  > do botão, a seta do tooltip — é identidade da casa, igual em todo projeto.
111
111
 
112
112
  ```tsx preview
113
- <Badge variant="success">Cabe nas alavancas</Badge>
113
+ <Badge context="success">Cabe nas alavancas</Badge>
114
114
  ```
115
115
 
116
116
  Se uma necessidade real não cabe nas alavancas (tokens · className · slots/asChild · props ·
@@ -9,7 +9,7 @@ para entrada e validação; use DetailField quando a pessoa apenas consulta um v
9
9
  label="Estágio"
10
10
  value={
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="prospect"
14
14
  />
15
15
  }
@@ -39,7 +39,10 @@ significado no domínio. `0` e `false` seguem como valores. Ver `EmptyValue`.
39
39
 
40
40
  `variant="framed"` adiciona a superfície e a borda externa. `dividers` desenha apenas as
41
41
  divisórias internas; as duas opções são independentes e podem ser combinadas. `orientation`
42
- define se a chave fica sobre o valor ou ao lado dele.
42
+ define se a chave fica sobre o valor ou ao lado dele. Na orientação horizontal, todos os valores
43
+ começam depois da mesma coluna de rótulo, com largura padrão de `7rem`. Quando a superfície exigir
44
+ mais espaço para os rótulos, ajuste a variável no grupo, por exemplo com
45
+ `className="[--detail-label-width:9rem]"`.
43
46
 
44
47
  ```tsx preview col
45
48
  <DetailGroup columns={2} orientation="horizontal" variant="framed" dividers>
@@ -33,7 +33,7 @@ showCloseButton={false} esconde o X: o usuário decide pelos botões — pra con
33
33
  ```tsx preview
34
34
  <Dialog>
35
35
  <DialogTrigger asChild>
36
- <Button variant="destructive">Excluir sessão</Button>
36
+ <Button context="danger">Excluir sessão</Button>
37
37
  </DialogTrigger>
38
38
  <DialogContent showCloseButton={false}>
39
39
  <DialogHeader>
@@ -45,7 +45,7 @@ showCloseButton={false} esconde o X: o usuário decide pelos botões — pra con
45
45
  <Button variant="ghost" size="sm">Cancelar</Button>
46
46
  </DialogClose>
47
47
  <DialogClose asChild>
48
- <Button variant="destructive" size="sm">Excluir</Button>
48
+ <Button context="danger" size="sm">Excluir</Button>
49
49
  </DialogClose>
50
50
  </DialogFooter>
51
51
  </DialogContent>