@softize/opus 13.1.0 → 14.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/bin/lib/copy.mjs +276 -6
  3. package/docs/code-style.md +4 -1
  4. package/package.json +1 -1
  5. package/registry/instructions/opus.md +3 -3
  6. package/registry/templates/app/src/App.tsx +11 -6
  7. package/src/core/types.ts +3 -4
  8. package/src/ui/components/patterns/action-list-dialog.tsx +10 -3
  9. package/src/ui/components/patterns/confirm.tsx +2 -31
  10. package/src/ui/components/patterns/content-header.tsx +42 -141
  11. package/src/ui/components/patterns/data-state.tsx +42 -68
  12. package/src/ui/components/patterns/form.tsx +15 -15
  13. package/src/ui/components/patterns/list.tsx +14 -8
  14. package/src/ui/components/patterns/page-state.tsx +39 -51
  15. package/src/ui/components/patterns/page.tsx +18 -53
  16. package/src/ui/components/patterns/state-surface.tsx +148 -0
  17. package/src/ui/components/patterns/surface-header.tsx +119 -0
  18. package/src/ui/components/patterns/trigger.tsx +7 -9
  19. package/src/ui/components/patterns/view.tsx +14 -16
  20. package/src/ui/components/primitives/alert.tsx +1 -33
  21. package/src/ui/components/primitives/avatar.tsx +15 -5
  22. package/src/ui/components/primitives/badge.tsx +2 -43
  23. package/src/ui/components/primitives/button.tsx +31 -35
  24. package/src/ui/components/primitives/control.ts +60 -0
  25. package/src/ui/components/primitives/dot.tsx +1 -30
  26. package/src/ui/components/primitives/input-group.tsx +11 -8
  27. package/src/ui/components/primitives/item.tsx +3 -1
  28. package/src/ui/components/primitives/menu.tsx +1 -7
  29. package/src/ui/components/primitives/pagination.tsx +16 -8
  30. package/src/ui/components/primitives/select.tsx +2 -2
  31. package/src/ui/components/primitives/spinner.tsx +13 -16
  32. package/src/ui/components/primitives/switch.tsx +4 -1
  33. package/src/ui/components/primitives/tabs.tsx +5 -3
  34. package/src/ui/components/primitives/toggle.tsx +9 -4
  35. package/src/ui/docs/content/action-form.md +26 -0
  36. package/src/ui/docs/content/action-list-dialog.md +2 -2
  37. package/src/ui/docs/content/action-list.md +3 -1
  38. package/src/ui/docs/content/action-trigger.md +4 -4
  39. package/src/ui/docs/content/action-view.md +3 -2
  40. package/src/ui/docs/content/avatar.md +7 -3
  41. package/src/ui/docs/content/button.md +30 -14
  42. package/src/ui/docs/content/communication.md +36 -0
  43. package/src/ui/docs/content/content.md +5 -4
  44. package/src/ui/docs/content/data-state.md +17 -13
  45. package/src/ui/docs/content/dialog.md +1 -4
  46. package/src/ui/docs/content/input.md +1 -1
  47. package/src/ui/docs/content/item.md +1 -1
  48. package/src/ui/docs/content/page.md +12 -4
  49. package/src/ui/docs/content/pagination.md +11 -9
  50. package/src/ui/docs/content/semantic-context.md +3 -2
  51. package/src/ui/docs/content/sidebar.md +2 -42
  52. package/src/ui/docs/content/spinner.md +9 -6
  53. package/src/ui/docs/content/switch.md +1 -1
  54. package/src/ui/docs/content/tabs.md +1 -1
  55. package/src/ui/docs/content/toggle.md +1 -1
  56. package/src/ui/drivers/react.tsx +1 -6
  57. package/src/ui/meta.ts +5 -5
  58. package/src/ui/react.tsx +8 -16
  59. package/src/ui/components/patterns/shell-nav.tsx +0 -154
@@ -3,11 +3,13 @@ import { cva, type VariantProps } from "class-variance-authority"
3
3
  import { Tabs as TabsPrimitive } from "radix-ui"
4
4
 
5
5
  import { cn } from '../../lib/cn.ts'
6
+ import { controlHeight, type ControlSize } from './control.ts'
6
7
 
7
8
  // Divergência da casa (ejetado): o Tabs ganhou `size` — Button e Select têm `sm`, o Tabs
8
9
  // não tinha, e uma fileira densa ficava com o segmento 0.25rem mais alto
9
- // que os irmãos. O tamanho flui pra TabsList via contexto (a altura é dela).
10
- type TabsSize = "default" | "sm"
10
+ // que os irmãos. O tamanho flui pra TabsList via contexto (a altura é dela) e a medida é a
11
+ // da escala de control.ts.
12
+ type TabsSize = Extract<ControlSize, "default" | "sm">
11
13
  const TabsSizeContext = React.createContext<TabsSize>("default")
12
14
 
13
15
  function Tabs({
@@ -64,7 +66,7 @@ function TabsList({
64
66
  data-slot="tabs-list"
65
67
  data-variant={variant}
66
68
  data-size={size}
67
- className={cn(tabsListVariants({ variant }), size === "sm" ? "h-8" : "h-9", className)}
69
+ className={cn(tabsListVariants({ variant }), controlHeight[size], className)}
68
70
  {...props}
69
71
  />
70
72
  )
@@ -3,9 +3,13 @@ import { cva, type VariantProps } from "class-variance-authority"
3
3
  import { Toggle as TogglePrimitive } from "radix-ui"
4
4
 
5
5
  import { cn } from '../../lib/cn.ts'
6
+ import { controlHeight, focusRing } from './control.ts'
6
7
 
7
8
  const toggleVariants = cva(
8
- "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none hover:bg-muted hover:text-muted-foreground focus-visible:border-ring focus-visible:ring-[0.1875rem] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-context-danger aria-invalid:ring-context-danger/20 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground dark:aria-invalid:ring-context-danger/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
9
+ [
10
+ "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none hover:bg-muted hover:text-muted-foreground disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-context-danger aria-invalid:ring-context-danger/20 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground dark:aria-invalid:ring-context-danger/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
11
+ focusRing,
12
+ ],
9
13
  {
10
14
  variants: {
11
15
  variant: {
@@ -13,10 +17,11 @@ const toggleVariants = cva(
13
17
  outline:
14
18
  "border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
15
19
  },
20
+ // Alturas da escala de control.ts (`sm` = o mesmo h-8 de Button e Select).
16
21
  size: {
17
- default: "h-9 min-w-9 px-2",
18
- sm: "h-8 min-w-8 px-1.5",
19
- lg: "h-10 min-w-10 px-2.5",
22
+ default: `${controlHeight.default} min-w-9 px-2`,
23
+ sm: `${controlHeight.sm} min-w-8 px-1.5`,
24
+ lg: `${controlHeight.lg} min-w-10 px-2.5`,
20
25
  },
21
26
  shape: {
22
27
  default: '',
@@ -88,6 +88,32 @@ ciclo de vida que o pattern não cobre; validação e execução continuam iguai
88
88
  | `options` | `SelectOption[]` | Opções por id de runtime — sobrepõe o fieldOptions do form e o z.enum. |
89
89
  | `className` | `string` | Classes do invólucro (ex.: `col-span-2` em uma grid). |
90
90
 
91
+ ## Ajuda na label
92
+
93
+ `FieldSpec.help` aparece como um ícone junto à label, com o texto num tooltip. Escreva ali o
94
+ critério, o efeito ou a limitação que a label não diz; ajuda que só repete a label deve ser omitida.
95
+ Não existe texto auxiliar sob o campo: quem precisa de uma frase permanente abaixo do controle usa
96
+ `FieldDescription` na composição própria.
97
+
98
+ `LabelHelp` é esse mesmo ícone, exportado para um formulário sem contrato mostrar a ajuda no mesmo
99
+ lugar:
100
+
101
+ ```tsx preview col md
102
+ <Field>
103
+ <FieldLabel htmlFor="staff" className="items-center gap-1.5">
104
+ <span>Staff</span>
105
+ <LabelHelp help="Tem acesso ao back-office e a todos os workspaces." />
106
+ </FieldLabel>
107
+ <Input id="staff" placeholder="Empresa X" />
108
+ </Field>
109
+ ```
110
+
111
+ ### Propriedades de LabelHelp
112
+
113
+ | Propriedade | Tipo | Descrição |
114
+ |---|---|---|
115
+ | `help` | `string` | O texto do tooltip. Vazio ou ausente, o ícone não renderiza. |
116
+
91
117
  ## Origem das opções de seleção
92
118
 
93
119
  As opções seguem esta precedência: `options` no campo, `fieldOptions` no formulário, `options` no
@@ -23,7 +23,7 @@ render(
23
23
  <Plus /> Workspace
24
24
  </Button>
25
25
  }
26
- emptyText="Nenhum workspace."
26
+ emptyMessage="Nenhum workspace."
27
27
  >
28
28
  {(workspaces) => (
29
29
  <div className="space-y-2">
@@ -69,5 +69,5 @@ corpo do modal.
69
69
  | `actions` | `ReactNode` | | Ação à direita da toolbar — em geral o botão de criar. |
70
70
  | `empty` | `(items) => boolean` | `items.length === 0` | Sobrepõe o vazio derivado. |
71
71
  | `loading` | `boolean` | | Carga extra agregada à do fetch (query irmã). |
72
- | `emptyText / errorText` | `string` | | Textos dos estados (DataState). |
72
+ | `emptyMessage / errorMessage / retryLabel` | `string` | | Textos dos estados, repassados ao `ActionList` (e dele ao `DataState`). |
73
73
  | `className` | `string` | `sm:max-w-3xl` | Largura do DialogContent. |
@@ -267,6 +267,8 @@ chamar a action.
267
267
  | `rowId` | `(item) => string` | `item.id` | Identidade da linha para seleção. |
268
268
  | `pageSize` | `number` | `50` | Itens por página padrão (vira `limit`/`page` no input; handler devolve `total`). O usuário troca no popover de exibição. |
269
269
  | `state / onStateChange` | `ActionListState / (next) => void` | interno | Estado da toolbar controlado — para quem embala sincronizar com a URL. |
270
- | `emptyMessage` | `string` | `'Nenhum resultado.'` | O texto do estado vazio. |
270
+ | `emptyMessage` | `string` | `'Nenhum resultado.'` | Frase do estado vazio (o `Empty` do `DataState`). |
271
+ | `errorMessage` | `string` | `'Não foi possível carregar.'` | Título do aviso de erro; o botão de tentar de novo refaz a consulta. |
272
+ | `retryLabel` | `string` | `'Tentar de novo'` | Rótulo do botão de recuperação. |
271
273
  | `onRowClick` | `(item: TItem) => void` | | Clique na linha (ex.: navegar para o detalhe) — só na tabela. |
272
274
  | `rowActions` | `(item: TItem) => ReactNode` | | Ações por linha (coluna final, à direita) — apresentação de quem chama, como `cells`; cliques ali não disparam o `onRowClick`. |
@@ -37,9 +37,9 @@ confirm: {
37
37
 
38
38
  ## Ação somente com ícone
39
39
 
40
- Com `icon`, o botão exibe somente o ícone e usa `label`, ou `action.label`, no tooltip e no nome
41
- acessível. O clique não aciona o item clicável ao redor. `itemLabel` identifica o registro na
42
- mensagem de confirmação.
40
+ Com `icon`, o botão exibe somente o ícone no quadrado `icon-sm` da escala (1.75rem, a ação que mora
41
+ dentro de uma linha ou card) e usa `label`, ou `action.label`, no tooltip e no nome acessível. O clique
42
+ não aciona o item clicável ao redor. `itemLabel` identifica o registro na mensagem de confirmação.
43
43
 
44
44
  ```tsx
45
45
  <ActionTrigger
@@ -83,4 +83,4 @@ um atalho, um arrastar, um item de menu.
83
83
  | `onSuccess` | `(data: TData) => void` | | Pós-sucesso (cache já invalidado pelo action.invalidates). |
84
84
  | `icon` | `React.ReactNode` | | Torna o botão icon-only: rótulo no tooltip e no `aria-label`, clique que não vaza para o item. |
85
85
  | `itemLabel` | `string` | | Nome do alvo na pergunta (sai entre aspas, em destaque, antes da mensagem do contrato). |
86
- | `className` | `string` | | Classes do botão (ex.: apertar o tamanho em uma linha densa). |
86
+ | `className` | `string` | | Classes do botão. O tamanho vem de `size` (escala única); com `icon` é sempre `icon-sm`. |
@@ -21,7 +21,7 @@ render(
21
21
  <div className="space-y-1.5">
22
22
  <div className="flex items-center gap-2">
23
23
  <h3 className="text-sm font-semibold">{ws.name}</h3>
24
- <Badge variant={ws.status === 'active' ? 'success' : 'warning'}>{ws.status}</Badge>
24
+ <Badge context={ws.status === 'active' ? 'success' : 'warning'}>{ws.status}</Badge>
25
25
  </div>
26
26
  <p className="text-sm text-muted-foreground">
27
27
  Cliente {ws.client} · {ws.agents} agentes vinculados.
@@ -50,5 +50,6 @@ mais de uma região da tela ou quando o carregamento precisa ser orquestrado por
50
50
  | `children` | `(data: TData, refetch) => ReactNode` | | Conteúdo apresentado quando os dados estão disponíveis. `refetch` permite recarregar por código. |
51
51
  | `render` | `(data: TData, refetch) => ReactNode` | | Alias de compatibilidade de `children`; `children` tem precedência. |
52
52
  | `loading` | `ReactNode \| boolean` | `3 skeletons` | Sobrescreve o carregamento: um nó próprio, `true` para o padrão ou `false` para não renderizar nada enquanto carrega. |
53
- | `empty` | `ReactNode` | `nada` | Sobrescreve o estado vazio (200 sem dado). |
53
+ | `empty` | `ReactNode` | `emptyMessage` ou nada | Sobrescreve o estado vazio (200 sem dado). |
54
+ | `emptyMessage` | `string` | | Atalho do vazio: a frase na mesma superfície de `DataState` (`Empty` com moldura sólida). |
54
55
  | `error` | `(err, retry) => ReactNode` | `"Não foi possível carregar" + Tentar de novo` | Sobrescreve o estado de erro padrão. Sem ele, a frase do servidor só aparece quando é legível pela pessoa (`conflict`, `validation`, `not_found`, `authorization`, `authentication`); código técnico nunca vira título, e o botão de tentar de novo some quando o problema é de permissão ou sessão. |
@@ -28,10 +28,14 @@ para agente.
28
28
 
29
29
  ## Tamanhos
30
30
 
31
- size sm/default/lg o fallback acompanha o tamanho. sm para listas densas, lg para cabeçalho de
32
- workspace.
31
+ `size` usa a escala única dos controles: o avatar de uma linha mede o mesmo que o controle ao lado
32
+ (`sm` 2rem, `default` 2.25rem, `lg` 2.5rem); `xs` (1.5rem) é a lista densa. O fallback e o selo
33
+ acompanham o tamanho.
33
34
 
34
35
  ```tsx preview
36
+ <Avatar size="xs">
37
+ <AvatarFallback>AL</AvatarFallback>
38
+ </Avatar>
35
39
  <Avatar size="sm">
36
40
  <AvatarFallback>AL</AvatarFallback>
37
41
  </Avatar>
@@ -86,7 +90,7 @@ excedente — os membros do workspace Empresa X.
86
90
 
87
91
  | Propriedade | Tipo | Padrão | Descrição |
88
92
  |---|---|---|---|
89
- | `size` | `'sm' \| 'default' \| 'lg'` | `'default'` | Diâmetro do retrato; o fallback e o selo acompanham a escala. |
93
+ | `size` | `'xs' \| 'sm' \| 'default' \| 'lg'` | `'default'` | Diâmetro do retrato na escala única dos controles (1.5 · 2 · 2.25 · 2.5rem); o fallback e o selo acompanham. |
90
94
 
91
95
  ## Propriedades de AvatarImage
92
96
 
@@ -2,8 +2,7 @@
2
2
 
3
3
  `context` declara a hierarquia ou o risco da ação; `variant` escolhe o tratamento visual. Use
4
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
+ consequência perigosa.
7
6
 
8
7
  ```tsx preview
9
8
  <Button>Criar workspace</Button>
@@ -16,28 +15,45 @@ compatibilidade.
16
15
 
17
16
  ## Tamanhos
18
17
 
19
- O tamanho icon exige `aria-label`, porque não texto visível. Os botões só-ícone vêm em três tamanhos: `icon` (2.25rem), `icon-sm` (2rem, para uma fileira densa como o cabeçalho) e `icon-xs` (1.5rem, para uma ação dentro de um campo, como o `trailing` de Input ou Select).
18
+ `size` usa a escala única dos controles: o mesmo nome tem a mesma medida em `Button`, `Select`,
19
+ `Tabs`, `Toggle`, `Switch`, `Avatar`, `Spinner` e nos botões embutidos. Os tamanhos de texto dão a
20
+ altura da linha; os `icon-*` são quadrados para botões só de ícone, que exigem `aria-label` porque não
21
+ há texto visível. O glifo dentro do controle acompanha o tamanho (0.875rem em `xs` e `sm`, 1rem em
22
+ `default`, 1.25rem em `lg`), a menos que o ícone traga um `size-*` próprio.
23
+
24
+ | Nome | Medida | Uso |
25
+ |---|---|---|
26
+ | `xs` | 1.5rem | Ação dentro de um campo (`InputGroupButton`). |
27
+ | `sm` | 2rem | Toolbar e cabeçalho densos, ao lado de `Select` e `Tabs` `sm`. |
28
+ | `default` | 2.25rem | A linha padrão, a mesma de `Input`. |
29
+ | `lg` | 2.5rem | Chamada principal com mais área de toque. |
30
+ | `icon-xs` | 1.5rem | Ação só de ícone dentro de um campo, como o `trailing` de Input ou Select. |
31
+ | `icon-sm` | 1.75rem | Ação só de ícone dentro de uma linha ou card; é o quadrado de `ActionTrigger` com `icon` e das setas do pager de `ActionList`. |
32
+ | `icon` | 2.25rem | Ação só de ícone na linha padrão. |
33
+ | `icon-lg` | 2.5rem | Ação só de ícone ao lado de um `lg`. |
20
34
 
21
35
  ```tsx preview
36
+ <Button size="xs">Mínimo</Button>
22
37
  <Button size="sm">Pequeno</Button>
23
38
  <Button>Padrão</Button>
24
39
  <Button size="lg">Grande</Button>
25
- <Button size="icon" aria-label="Novo"><Plus /></Button>
26
- <Button size="icon-sm" variant="ghost" aria-label="Novo"><Plus /></Button>
27
40
  <Button size="icon-xs" variant="ghost" aria-label="Novo"><Plus /></Button>
41
+ <Button size="icon-sm" variant="ghost" aria-label="Novo"><Plus /></Button>
42
+ <Button size="icon" aria-label="Novo"><Plus /></Button>
43
+ <Button size="icon-lg" aria-label="Novo"><Plus /></Button>
28
44
  ```
29
45
 
30
46
  ## Estados
31
47
 
32
- busy = ação em andamento (DEPOIS do clique): o Spinner e o disabled vêm do botão. Com icon, o
33
- Spinner TROCA o ícone (não soma). Não confunda com carregar conteúdo (ANTES) — isso é Spinner
34
- centralizado/Skeleton em um nível de página.
48
+ busy = ação em andamento (DEPOIS do clique): o Spinner e o disabled vêm do botão. `icon` recebe o
49
+ elemento do ícone e o dimensiona pelo `size`; com icon, o Spinner TROCA o ícone (não soma). Não
50
+ confunda com carregar conteúdo (ANTES) — isso é `DataState` ou `Skeleton` em um nível de página.
35
51
 
36
52
  ```tsx preview
37
53
  <Button disabled>Desabilitado</Button>
38
54
  <Button busy>Salvando…</Button>
39
- <Button icon={Plus}>Novo</Button>
40
- <Button icon={Plus} busy>Novo</Button>
55
+ <Button icon={<Plus />}>Novo</Button>
56
+ <Button icon={<Plus />} busy>Novo</Button>
41
57
  ```
42
58
 
43
59
  ## Como outro elemento (asChild)
@@ -57,10 +73,10 @@ buttonVariants serve para o caso sem filho único.
57
73
  |---|---|---|---|
58
74
  | `context` | `'neutral' \| 'primary' \| 'danger'` | `'primary'` | A hierarquia ou o risco comunicado pela ação. |
59
75
  | `variant` | `'solid' \| 'subtle' \| 'outline' \| 'ghost' \| 'link'` | `'solid'` | O tratamento visual aplicado ao contexto. |
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`. |
76
+ | `size` | `'xs' \| 'sm' \| 'default' \| 'lg' \| 'icon-xs' \| 'icon-sm' \| 'icon' \| 'icon-lg'` | `'default'` | A medida na escala única dos controles (tabela acima). Os `icon-*` são quadrados para botões só de ícone, com `aria-label`. |
61
77
  | `asChild` | `boolean` | `false` | Renderiza como o filho (Radix Slot) em vez de `<button>` — para âncoras e afins. |
62
78
  | `busy` | `boolean` | `false` | Ação em andamento (depois do clique): mostra Spinner + desabilita. Não é "carregando" de conteúdo (que é Spinner/Skeleton em um nível de página). |
63
- | `icon` | `React.ElementType` | | Ícone à esquerda (ex.: icon={Plus}). No busy é trocado pelo Spinner — não soma. |
79
+ | `icon` | `React.ReactNode` | | Ícone à esquerda, como nó (ex.: `icon={<Plus />}`); o glifo segue o `size`. No busy é trocado pelo Spinner — não soma. |
64
80
 
65
81
  ## ButtonGroup
66
82
 
@@ -83,7 +99,7 @@ variações.
83
99
 
84
100
  ```tsx preview
85
101
  <ButtonGroup>
86
- <Button icon={Play}>Rodar agente developer</Button>
102
+ <Button icon={<Play />}>Rodar agente developer</Button>
87
103
  <ButtonGroupSeparator />
88
104
  <Button size="icon" aria-label="Mais opções">
89
105
  <ChevronDown />
@@ -102,7 +118,7 @@ semântica de outro elemento, como `label`.
102
118
  <GitBranch />
103
119
  empresa-x-api
104
120
  </ButtonGroupText>
105
- <Button variant="outline" icon={RotateCw}>Sincronizar</Button>
121
+ <Button variant="outline" icon={<RotateCw />}>Sincronizar</Button>
106
122
  </ButtonGroup>
107
123
  ```
108
124
 
@@ -50,6 +50,42 @@ frase. Não copie as regras da Base para cá.
50
50
  | placeholder de campo ou busca | `placeholder` |
51
51
  | `Select.emptyText` e grupos de opção | `empty-state` e `heading` |
52
52
  | confirmação local do `ActionTrigger` | `title`, `dialog-body` e `button` |
53
+ | `dialog.alert/confirm/prompt/choose` — `title`, `description`/`body`, `action`/`cancel`, `actions[].label` | `title`, `dialog-body`, `button` (e `placeholder` no `prompt`) |
54
+ | filhos de `TooltipContent` | `label` |
55
+ | `LabelHelp.help` | `helper-text` |
56
+ | `emptyMessage`, `errorMessage`, `retryLabel` de `DataState`, `PageState`, `ActionList`, `ActionListDialog` e `ActionView` | `empty-state`, `error`, `button` |
57
+
58
+ O tooltip entra como `label` porque nomeia um controle icon-only: é um fragmento curto, sem
59
+ ponto final, e a Base não define um papel próprio para tooltip. Texto de ajuda que precisa de
60
+ frase completa pertence ao `help` de um campo (contrato ou `LabelHelp`), não ao tooltip.
61
+
62
+ ## Alcance do inventário além das props
63
+
64
+ Quem vê o gate reprovar ou precisa de uma dispensa deve saber o que o extrator alcança sem
65
+ declaração adicional:
66
+
67
+ - **Diálogos imperativos.** `dialog.confirm({ title, description, action })` e as demais
68
+ respostas (`alert`, `prompt`, `choose`) entram no inventário como se fossem props de um
69
+ `ActionFormDialog`; o campo do diagnóstico é `dialog.confirm().title`,
70
+ `dialog.choose().actions[0].label` etc. Constantes locais resolvem normalmente. Uma função
71
+ que devolve template (`description: describe(nome)`) gera o diagnóstico `content` habitual e
72
+ se declara em `copy.dynamic` como `message-template`. Opções construídas em tempo de execução
73
+ ou mutadas depois de declaradas reprovam como estrutura, porque nenhum texto pode ser
74
+ atribuído a elas.
75
+ - **`LabelHelp` como filho.** `<FieldLabel>Nome<LabelHelp help="…" /></FieldLabel>` inventaria
76
+ `Nome` como label e a ajuda como helper-text; o ícone não torna o label opaco. Um wrapper
77
+ local com o mesmo nome continua opaco, porque o extrator não infere o que ele renderiza.
78
+ - **Rótulos em array literal local.** Quando um componente mapeado lê `item.label` (ou a chave
79
+ destruturada) dentro de `map`, `forEach`, `filter` ou `for…of` sobre um array literal do
80
+ mesmo arquivo, cada literal do array vira um texto na linha em que foi escrito, em vez de um
81
+ diagnóstico sobre a prop. `filter`, `slice`, `toSorted` e `toReversed` podem ficar entre o
82
+ array e a iteração. O limite é deliberado: o array precisa ser literal e declarado no mesmo
83
+ arquivo; o elemento só pode ser lido (`item.chave`), nunca passado adiante, espalhado ou
84
+ atribuído; o callback não pode receber o próprio array como terceiro parâmetro; e nenhum
85
+ elemento pode ter a chave dinâmica. Arrays importados, derivados por spread ou por
86
+ concatenação continuam gerando o diagnóstico da prop.
87
+ - **Elementos JSX genéricos.** `<ActionList<Input, Row> emptyMessage="…">` extrai igual a
88
+ `<ActionList emptyMessage="…">`.
53
89
 
54
90
  Copy visual e nome acessível são superfícies cumulativas. Um `aria-label` estático nomeia
55
91
  um controle icon-only, mas não torna aceitável nem invisível ao gate um texto visual opaco.
@@ -22,7 +22,8 @@ render(
22
22
 
23
23
  Use os slots quando o header precisar de composição própria. A árvore aceita exatamente um
24
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.
25
+ uma descrição, um contador (`ContentMeta`) e uma região de ações — a mesma anatomia de `PageHeader`,
26
+ com os mesmos slots e o mesmo layout; só o nível do heading e a hierarquia visual mudam.
26
27
 
27
28
  ```tsx preview col
28
29
  render(
@@ -48,12 +49,12 @@ do pai correto, filhos estruturais indiretos e a mistura de shorthand com compos
48
49
  | Propriedade | Tipo | Padrão | Descrição |
49
50
  |---|---|---|---|
50
51
  | `title` | `ReactNode` | | Forma curta: título da região (vira o heading ligado à `section`). |
51
- | `meta` | `ReactNode` | | Forma curta: complemento ao lado do título, como uma contagem. |
52
+ | `count` | `number` | | Forma curta: total de itens ao lado do título o mesmo `count` de `Page`. |
52
53
  | `description` | `ReactNode` | | Forma curta: frase de apoio sob o título. |
53
54
  | `actions` | `ReactNode` | | Forma curta: ações alinhadas à direita do header. |
54
55
  | `level` | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | herdado | Nível semântico do heading, independente do destaque visual. |
55
- | `variant` | `'page' \| 'section'` | `'section'` | Hierarquia visual; `page` permanece por compatibilidade da série 12. |
56
+ | `variant` | `'page' \| 'section'` | `'section'` | Hierarquia visual: `page` reproduz o cabeçalho de `Page`; `section` é a região dentro de uma superfície. |
56
57
 
57
58
  Na composição explícita, `ContentHeader` recebe `ContentTitle`, `ContentMeta`, `ContentDescription` e
58
59
  `ContentActions`, e `ContentBody` recebe o conteúdo; nenhum desses slots aceita `title` ou `level`
59
- próprios — a hierarquia é declarada em `Content`.
60
+ próprios — a hierarquia é declarada em `Content`. `ContentHeader` não existe fora de `Content`.
@@ -1,19 +1,21 @@
1
1
  ## Estados
2
2
 
3
- Use `DataState` para apresentar carregamento, erro, vazio e conteúdo de uma mesma consulta. O
4
- carregamento usa `Spinner`; o vazio preserva uma moldura sólida; e o erro apresenta uma mensagem
5
- segura, sem expor detalhes técnicos. Para uma ação em andamento depois do clique, use `busy` em
6
- `Button`.
3
+ Use `DataState` para apresentar carregamento, erro, vazio e conteúdo de uma mesma consulta. As
4
+ superfícies são as mesmas de `PageState`, na escala de uma seção: o carregamento é um contêiner
5
+ `role="status"` com o `Spinner`; o vazio compõe `Empty` com moldura sólida e `emptyMessage` como
6
+ título; o erro é um `Alert` de contexto `danger` com uma mensagem segura, sem expor detalhes
7
+ técnicos, e o botão de recuperação quando há `onRetry`. Para uma ação em andamento depois do
8
+ clique, use `busy` em `Button`.
7
9
 
8
10
  ```tsx preview col
9
11
  <div className="w-full space-y-3">
10
12
  <DataState loading>
11
13
  <div />
12
14
  </DataState>
13
- <DataState empty emptyText="Nenhum papel.">
15
+ <DataState empty emptyMessage="Nenhum papel.">
14
16
  <div />
15
17
  </DataState>
16
- <DataState error={{ message: 'detalhe técnico fica no console' }}>
18
+ <DataState error={{ message: 'detalhe técnico fica no console' }} onRetry={() => undefined}>
17
19
  <div />
18
20
  </DataState>
19
21
  </div>
@@ -22,13 +24,13 @@ segura, sem expor detalhes técnicos. Para uma ação em andamento depois do cli
22
24
  ## Dentro de uma tabela
23
25
 
24
26
  Em uma tabela já emoldurada, passe `colSpan` para ocupar uma linha inteira dentro de `<tbody>`. A
25
- tabela continua responsável pela borda, evitando uma segunda moldura no estado vazio. Para uma
26
- região disponível para criação ou vínculo, use `Empty`.
27
+ tabela continua responsável pela borda: o `Empty` dentro dela vem sem moldura. Para uma região
28
+ disponível para criação ou vínculo, use `Empty` diretamente, com a moldura tracejada.
27
29
 
28
30
  ```tsx preview col
29
31
  <table className="w-full overflow-hidden rounded-lg border border-border text-sm">
30
32
  <tbody>
31
- <DataState empty emptyText="Nenhum usuário." colSpan={3}>
33
+ <DataState empty emptyMessage="Nenhum usuário." colSpan={3}>
32
34
  <tr>
33
35
  <td />
34
36
  </tr>
@@ -42,8 +44,10 @@ região disponível para criação ou vínculo, use `Empty`.
42
44
  | Propriedade | Tipo | Padrão | Descrição |
43
45
  |---|---|---|---|
44
46
  | `loading` | `boolean` | | Carregando (antes do conteúdo) — mostra o Spinner centralizado. |
45
- | `empty` | `boolean` | | Sem itens — mostra o emptyText. |
46
- | `emptyText` | `string` | | Texto do vazio (pt-BR, ex.: "Nenhum papel."). |
47
- | `error` | `{ message?: string } \| null` | | Erro da carga — aviso calmo. A mensagem técnica não vai para tela (use errorText). |
48
- | `errorText` | `string` | `'Não foi possível carregar.'` | Aviso de erro, orientado ao usuário. |
47
+ | `empty` | `boolean` | | Sem itens — compõe `Empty` com o `emptyMessage`. |
48
+ | `emptyMessage` | `string` | `'Nada por aqui.'` | Frase do vazio (ex.: "Nenhum papel."). |
49
+ | `error` | `{ message?: string } \| null` | | Erro da carga — aviso calmo. A mensagem técnica não vai para tela (use `errorMessage`). |
50
+ | `errorMessage` | `string` | `'Não foi possível carregar.'` | Título do aviso de erro, orientado à pessoa. |
51
+ | `onRetry` | `() => void \| Promise<void>` | | Recuperação: mostra o botão de tentar de novo no estado de erro. |
52
+ | `retryLabel` | `string` | `'Tentar de novo'` | Rótulo do botão de recuperação. |
49
53
  | `colSpan` | `number` | | Em tabela: renderiza o estado como `<tr><td colSpan>` (cabe direto no tbody). |
@@ -305,7 +305,7 @@ avançada; ele recebe `children` e `container` conforme o portal do Radix.
305
305
  ## Propriedades de DialogHost
306
306
 
307
307
  `DialogHost` não recebe propriedades. Monte uma instância no shell para atender toda a API
308
- imperativa. `ConfirmHost` permanece como alias de migração.
308
+ imperativa.
309
309
 
310
310
  ## Opções de dialog.alert
311
311
 
@@ -363,6 +363,3 @@ imperativa. `ConfirmHost` permanece como alias de migração.
363
363
  | `context` | `ButtonContext` | Última ação: `primary`; demais: `neutral` | Define o significado semântico. |
364
364
  | `variant` | `ButtonVariant` | Última ação: `solid`; demais: `ghost` | Define o tratamento visual. |
365
365
  | `disabled` | `boolean` | `false` | Impede a escolha desta ação. |
366
-
367
- `confirm()` e `ConfirmHost` continuam disponíveis apenas como aliases de migração para
368
- `dialog.confirm()` e `DialogHost`.
@@ -166,7 +166,7 @@ Com `InputGroupTextarea`, um addon em `block-end` forma uma região de ações a
166
166
 
167
167
  | Propriedade | Tipo | Padrão | Descrição |
168
168
  |---|---|---|---|
169
- | `size` | `'xs' \| 'sm' \| 'icon-xs' \| 'icon-sm'` | `'xs'` | Tamanho do botão embutido; as variantes `icon-*` são quadradas. |
169
+ | `size` | `'xs' \| 'sm' \| 'icon-xs' \| 'icon-sm'` | `'xs'` | O subconjunto da escala única que cabe num campo (1.5 · 2 · 1.5 · 1.75rem); os `icon-*` são quadrados. |
170
170
  | `context` | `'neutral' \| 'primary' \| 'danger'` | `'primary'` | Contexto semântico herdado de Button. |
171
171
  | `variant` | `'solid' \| 'subtle' \| 'outline' \| 'ghost' \| 'link'` | `'ghost'` | Tratamento visual; use `solid` quando a ação precisar de ênfase. |
172
172
 
@@ -105,7 +105,7 @@ usa `ItemHeader` ou `ItemBody` conforme o papel do conteúdo.
105
105
  | Propriedade | Tipo | Padrão | Descrição |
106
106
  |---|---|---|---|
107
107
  | `variant` | `'default' \| 'outline' \| 'muted'` | `'default'` | O fundo da linha: transparente, com borda ou levemente tingido. |
108
- | `size` | `'default' \| 'sm'` | `'default'` | O respiro interno; `sm` atende listas densas. |
108
+ | `size` | `'default' \| 'sm'` | `'default'` | O respiro interno, com os nomes da escala única; `sm` atende listas densas. |
109
109
  | `asChild` | `boolean` | `false` | Funde o Item no filho para que a linha inteira assuma sua semântica. |
110
110
 
111
111
  ## Propriedades de ItemGroup
@@ -8,7 +8,9 @@ contexto. O container é centralizado e ocupa a largura disponível até `80rem`
8
8
  abaixo do cabeçalho permanece livre para tabelas, cards ou outras composições.
9
9
 
10
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.
11
+ portanto, não produz uma estrutura visual ou semântica diferente da forma explícita. O cabeçalho é a
12
+ mesma anatomia de `Content` (título, contador, descrição e ações): o que muda entre os dois é o nível
13
+ do heading e a hierarquia visual.
12
14
 
13
15
  `Page` é o esqueleto de páginas e recursos delimitados. Ele também pode ocupar o painel principal
14
16
  de um shell com sidebar; a navegação lateral não exige remover o teto nem reconstruir o cabeçalho.
@@ -90,8 +92,10 @@ altura e semântica acessível consistentes.
90
92
  </Page>
91
93
  ```
92
94
 
93
- `loading` centraliza o `Spinner`; `error` compõe `Alert`; `empty` compõe `Empty`; e `ready` entrega
94
- os filhos sem acrescentar uma superfície.
95
+ `loading` centraliza o `Spinner` num contêiner `role="status"`; `error` compõe `Alert` com o botão
96
+ de recuperação quando há `onRetry`; `empty` compõe `Empty`; e `ready` entrega os filhos sem
97
+ acrescentar uma superfície. São as mesmas superfícies de `DataState`, na escala da página: `title`
98
+ e `description` nomeiam a situação e, sem `title`, valem `errorMessage` e `emptyMessage`.
95
99
 
96
100
  ```tsx preview col
97
101
  <Page title="Relatórios">
@@ -126,5 +130,9 @@ estado na própria seção com `DataState`, `ActionView`, `ActionList` ou `Alert
126
130
  | `title` | `ReactNode` | Texto seguro por estado | Situação reconhecível pela pessoa. |
127
131
  | `description` | `ReactNode` | | Impacto ou próximo passo aplicável. |
128
132
  | `icon` | `ReactNode` | Alerta no erro | Ícone decorativo do estado. |
129
- | `action` | `ReactNode` | | Recuperação, seleção ou criação aplicável. |
133
+ | `action` | `ReactNode` | | Seleção ou criação aplicável ao estado. |
134
+ | `emptyMessage` | `string` | `'Nada por aqui'` | Título do vazio quando `title` não é informado. |
135
+ | `errorMessage` | `string` | `'Não foi possível carregar esta página'` | Título do erro quando `title` não é informado. |
136
+ | `onRetry` | `() => void \| Promise<void>` | | Recuperação do erro: acrescenta o botão de tentar de novo ao lado de `action`. |
137
+ | `retryLabel` | `string` | `'Tentar de novo'` | Rótulo do botão de recuperação. |
130
138
  | `children` | `ReactNode` | | Conteúdo renderizado somente em `ready`. |
@@ -29,21 +29,21 @@ rodapé.
29
29
 
30
30
  ## Números longos
31
31
 
32
- A largura mínima é quadrada e cresce conforme o conteúdo. Na escala densa de um rodapé, ajuste por
33
- `className` (`h-7 min-w-7 text-xs` nos números; `size-7` mais `iconClassName="size-3.5"` nas
34
- setas com `iconOnly`).
32
+ A largura mínima é quadrada e cresce conforme o conteúdo. Na escala densa de um rodapé, as setas e a
33
+ elipse usam `size="icon-sm"` (o quadrado de 1.75rem da escala única, com o glifo de 0.875rem); os
34
+ números, que não têm um degrau de 1.75rem na escala, ajustam por `className` (`h-7 min-w-7 text-xs`).
35
35
 
36
36
  ```tsx preview
37
37
  <Pagination>
38
38
  <PaginationContent className="gap-0.5">
39
39
  <PaginationItem>
40
- <PaginationPrevious href="#" iconOnly className="size-7" iconClassName="size-3.5" />
40
+ <PaginationPrevious href="#" iconOnly size="icon-sm" />
41
41
  </PaginationItem>
42
42
  <PaginationItem>
43
43
  <PaginationLink href="#" page={1} className="h-7 min-w-7 text-xs" />
44
44
  </PaginationItem>
45
45
  <PaginationItem>
46
- <PaginationEllipsis className="size-7" />
46
+ <PaginationEllipsis size="icon-sm" />
47
47
  </PaginationItem>
48
48
  <PaginationItem>
49
49
  <PaginationLink href="#" page={5726} className="h-7 min-w-7 text-xs" />
@@ -52,7 +52,7 @@ setas com `iconOnly`).
52
52
  <PaginationLink href="#" page={5727} isActive className="h-7 min-w-7 text-xs" />
53
53
  </PaginationItem>
54
54
  <PaginationItem>
55
- <PaginationNext href="#" iconOnly className="size-7" iconClassName="size-3.5" />
55
+ <PaginationNext href="#" iconOnly size="icon-sm" />
56
56
  </PaginationItem>
57
57
  </PaginationContent>
58
58
  </Pagination>
@@ -61,7 +61,8 @@ setas com `iconOnly`).
61
61
  ## Com elipse
62
62
 
63
63
  `PaginationEllipsis` marca, de forma decorativa e com `aria-hidden`, uma sequência de páginas que
64
- não cabe na barra.
64
+ não cabe na barra. `size` aceita os quadrados da escala (`icon-xs`, `icon-sm`, `icon`, `icon-lg`) para
65
+ parear com as setas.
65
66
 
66
67
  ```tsx preview
67
68
  <Pagination>
@@ -130,7 +131,7 @@ render(
130
131
  | `page` | `number` | | Número usado como conteúdo, quando `children` não é informado, e no nome acessível “Página N”. |
131
132
  | `isActive` | `boolean` | `false` | Marca a página atual com `aria-current="page"` e tratamento `outline`. |
132
133
  | `href` | `string` | | Renderiza um `<a>`. Sem `href`, o componente usa `<button>` e aceita `onClick` e `disabled`. |
133
- | `size` | `'default' \| 'sm' \| 'lg' \| 'icon' \| 'icon-sm' \| 'icon-xs'` | `'default'` | Escala herdada de `Button`. A largura mínima cresce para acomodar números longos. |
134
+ | `size` | `ButtonProps['size']` | `'default'` | A escala única, via `Button`. A largura mínima cresce para acomodar números longos; os `icon-*` são quadrados. |
134
135
 
135
136
  ## Propriedades de PaginationPrevious e PaginationNext
136
137
 
@@ -138,4 +139,5 @@ render(
138
139
  |---|---|---|---|
139
140
  | `label` | `string` | `'Página anterior'` ou `'Próxima página'` | Nome acessível e texto visível da ação. |
140
141
  | `iconOnly` | `boolean` | `false` | Exibe somente a seta; `label` continua disponível para leitura assistiva. |
141
- | `iconClassName` | `string` | | Classes aplicadas ao ícone da seta. |
142
+ | `size` | `ButtonProps['size']` | `'default'`; `'icon'` com `iconOnly` | A escala única; `icon-sm` para o rodapé denso. |
143
+ | `iconClassName` | `string` | | Classes aplicadas ao ícone da seta, quando o glifo da escala não servir. |
@@ -59,5 +59,6 @@ const status = t.dict(
59
59
 
60
60
  ## Compatibilidade
61
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.
62
+ `tone` em dicionários continua aceito durante a migração; código novo usa `context`. As variantes
63
+ semânticas antigas dos componentes (`default`, `secondary`, `destructive`, `success`, `warning`,
64
+ `info`) saíram na 14.0.0: `variant` só descreve tratamento visual e o significado é sempre `context`.
@@ -61,7 +61,7 @@ uma sidebar fixa, redimensionável ou recolhida.
61
61
  | `PaneHeader` | Mantém identidade, contexto ou ações no topo. |
62
62
  | `PaneBody` | Ocupa o espaço restante e concentra a rolagem vertical. |
63
63
  | `PaneFooter` | Mantém ações persistentes no rodapé. |
64
- | `SidebarNav` ou `ShellNav` | Apresenta e controla os destinos de navegação. |
64
+ | `SidebarNav` | Apresenta e controla os destinos de navegação. |
65
65
 
66
66
  `PaneContent` permanece como alias temporário de `PaneBody` durante a versão 12. Código novo usa
67
67
  `PaneBody`.
@@ -134,26 +134,9 @@ render(
134
134
  Esse é o padrão usado pelo `DocBrowser`: seções são rótulos, grupos nomeados são nós expansíveis e
135
135
  páginas são folhas. Um grupo começa aberto e volta a abrir quando contém a página ativa.
136
136
 
137
- `ShellNav` continua disponível para uma navegação plana com cabeçalho próprio e grupos ancorados no
138
- rodapé, mas está descontinuado: código novo usa `SidebarNav`, que cobre o mesmo caso integrado à
139
- `Sidebar`. Ele gerencia a rolagem internamente e se adapta quando estiver dentro de uma `Sidebar`
140
- recolhida.
141
-
142
- ```tsx
143
- <Sidebar collapsed={collapsed}>
144
- <ShellNav
145
- heading={<ShellNavHeading title="Relatórios" action={<CreateReportButton />} />}
146
- groups={reportGroups}
147
- footer={settingsGroups}
148
- activeId={activeId}
149
- onSelect={navigateToReport}
150
- />
151
- </Sidebar>
152
- ```
153
-
154
137
  ## Recolher a coluna
155
138
 
156
- `collapsed` pertence à `Sidebar`. Nesse estado, `SidebarItem`, `SidebarNav` e `ShellNav` mantêm
139
+ `collapsed` pertence à `Sidebar`. Nesse estado, `SidebarItem` e `SidebarNav` mantêm
157
140
  somente os ícones e expõem os rótulos em tooltips. Por isso, todo destino que aparece no modo
158
141
  recolhido precisa de um ícone reconhecível e de um `label` completo.
159
142
 
@@ -329,26 +312,3 @@ O driver de drag-and-drop continua externo e fornece seus atributos por `dragPro
329
312
  |---|---|---|---|
330
313
  | `className` | `string` | | Ajusta o grupo sem substituir o recuo, o espaçamento e a guia vertical padrão. |
331
314
  | `children` | `ReactNode` | | Itens ou grupos que descendem do nó anterior. |
332
-
333
- ## Propriedades de ShellNav
334
-
335
- | Propriedade | Tipo | Padrão | Descrição |
336
- |---|---|---|---|
337
- | `groups` | `ShellNavGroup[]` | | Grupos planos exibidos na região rolável. Grupos vazios são omitidos. |
338
- | `activeId` | `string` | | Identificador do destino atual. |
339
- | `onSelect` | `(id: string) => void` | | Recebe o identificador selecionado. |
340
- | `heading` | `ReactNode` | | Conteúdo fixo acima da navegação; fica oculto no modo recolhido. |
341
- | `footer` | `ShellNavGroup[]` | | Grupos ancorados abaixo da região rolável. |
342
- | `navLabel` | `string` | `'Navegação'` | Nome acessível da landmark `nav`. |
343
- | `className` | `string` | | Ajusta a coluna da navegação. A largura vem do contêiner. |
344
-
345
- Cada `ShellNavGroup` recebe `label` opcional e `items`. Um `ShellNavItem` declara `id`, `label`,
346
- `icon`, `badge` e `disabled`.
347
-
348
- ## Propriedades de ShellNavHeading
349
-
350
- | Propriedade | Tipo | Padrão | Descrição |
351
- |---|---|---|---|
352
- | `title` | `ReactNode` | | Título da navegação. |
353
- | `action` | `ReactNode` | | Ação relacionada apresentada no extremo oposto. |
354
- | `className` | `string` | | Ajusta a faixa de cabeçalho. |