@softize/opus 17.2.0 → 18.0.1

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 (46) hide show
  1. package/CHANGELOG.md +62 -1
  2. package/bin/lib/check.mjs +212 -45
  3. package/docs/adr/0010-page-header-owns-page-chrome.md +2 -2
  4. package/docs/adr/0011-page-shell-coordinates-persistent-page-chrome.md +4 -0
  5. package/docs/adr/0013-presentation-is-a-portable-action-oriented-artifact.md +14 -2
  6. package/docs/adr/0015-action-size-follows-interaction-density.md +1 -1
  7. package/docs/adr/0016-list-collection-header-belongs-to-content.md +80 -0
  8. package/package.json +1 -1
  9. package/registry/skills/build-opus-ui/SKILL.md +30 -20
  10. package/registry/skills/build-opus-ui/references/evaluations.md +9 -3
  11. package/registry/skills/build-opus-ui/references/ui-patterns.md +30 -18
  12. package/src/core/presentation.ts +223 -24
  13. package/src/core/runtime.ts +3 -0
  14. package/src/core/types.ts +2 -0
  15. package/src/mcp/index.ts +1 -0
  16. package/src/ui/components/patterns/action-form-card.tsx +8 -1
  17. package/src/ui/components/patterns/confirm.tsx +194 -157
  18. package/src/ui/components/patterns/content-header.tsx +17 -2
  19. package/src/ui/components/patterns/form-dialog.tsx +28 -14
  20. package/src/ui/components/patterns/form.tsx +340 -222
  21. package/src/ui/components/patterns/list.tsx +43 -44
  22. package/src/ui/components/patterns/page-heading-context.tsx +34 -0
  23. package/src/ui/components/patterns/page-state.tsx +2 -0
  24. package/src/ui/components/patterns/page.tsx +165 -51
  25. package/src/ui/components/patterns/presentation.tsx +140 -84
  26. package/src/ui/components/patterns/surface-header.tsx +5 -6
  27. package/src/ui/components/patterns/trigger.tsx +113 -83
  28. package/src/ui/components/primitives/button.tsx +2 -2
  29. package/src/ui/components/primitives/chat.tsx +19 -5
  30. package/src/ui/components/primitives/control.ts +9 -3
  31. package/src/ui/components/primitives/dialog.tsx +16 -9
  32. package/src/ui/components/primitives/drawer.tsx +9 -6
  33. package/src/ui/docs/content/action-form-card.md +9 -8
  34. package/src/ui/docs/content/action-form-dialog.md +11 -12
  35. package/src/ui/docs/content/action-form.md +25 -25
  36. package/src/ui/docs/content/action-list.md +101 -70
  37. package/src/ui/docs/content/action-trigger.md +2 -2
  38. package/src/ui/docs/content/button.md +1 -1
  39. package/src/ui/docs/content/chat.md +4 -4
  40. package/src/ui/docs/content/content.md +29 -13
  41. package/src/ui/docs/content/dialog.md +27 -21
  42. package/src/ui/docs/content/drawer.md +8 -6
  43. package/src/ui/docs/content/page.md +43 -50
  44. package/src/ui/docs/content/presentation.md +39 -28
  45. package/src/ui/docs/doc-client.tsx +1 -1
  46. package/src/ui/meta.ts +4 -4
@@ -41,7 +41,7 @@ invalidação continuam sob responsabilidade de `ActionForm`.
41
41
  <DocBrowserActionProvider>
42
42
  <ActionForm
43
43
  action={docWorkspaceCreate}
44
- defaultValues={{ name: 'Empresa X', status: 'active' }}
44
+ defaultValues={{ name: "Empresa X", status: "active" }}
45
45
  submitLabel="Atualizar"
46
46
  onCancel={() => undefined}
47
47
  />
@@ -70,23 +70,23 @@ ciclo de vida que o pattern não cobre; validação e execução continuam iguai
70
70
 
71
71
  ## Propriedades de ActionForm
72
72
 
73
- | Propriedade | Tipo | Padrão | Descrição |
74
- |---|---|---|---|
75
- | `action` | `FormContract<TInput, TData>` | | O contrato da FormAction — dele saem campos (input Zod + fields), mensagens e invalidação de cache. |
76
- | `defaultValues` | `Partial<TInput>` | | Valores iniciais — o modo edição de um update/patch. |
77
- | `onSuccess` | `(data: TData) => void` | | Pós-sucesso (o toast e a invalidação de cache já aconteceram). |
78
- | `submitLabel / cancelLabel / onCancel` | `string / string / () => void` | `'Salvar' / 'Cancelar'` | Rodapé do form — o Cancelar só aparece com onCancel. |
79
- | `fieldOptions` | `Record<string, SelectOption[]>` | | Opções de runtime por campo (ex.: ids de skills) — sobrepõe as inferidas do z.enum. |
80
- | `className / body / footer` | `string / (node) => node / (node) => node` | | className = classes do `<form>`. body/footer = slots: recebem os campos / os botões e escolhem o invólucro — é como o ActionFormDialog injeta DialogBody/DialogFooter (scroll + faixa). |
81
- | `children` | `ReactNode` | | Modo composição: diagrame com `<ActionFormField name />`. Sem children, o automático monta todos os campos na ordem do contrato. |
73
+ | Propriedade | Tipo | Padrão | Descrição |
74
+ | ------------------------------------------------------ | ----------------------------------------------------- | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
75
+ | `action` | `FormContract<TInput, TData>` | | O contrato da FormAction — dele saem campos (input Zod + fields), mensagens e invalidação de cache. |
76
+ | `defaultValues` | `Partial<TInput>` | | Valores iniciais — o modo edição de um update/patch. |
77
+ | `onSuccess` | `(data: TData) => void` | | Pós-sucesso (o toast e a invalidação de cache já aconteceram). |
78
+ | `submitLabel / cancelLabel / cancelVariant / onCancel` | `string / string / 'ghost' \| 'outline' / () => void` | `'Salvar' / 'Cancelar' / 'ghost'` | Rodapé do form — o Cancelar só aparece com onCancel. Use `outline` quando ele dividir o footer com a ação principal. |
79
+ | `fieldOptions` | `Record<string, SelectOption[]>` | | Opções de runtime por campo (ex.: ids de skills) — sobrepõe as inferidas do z.enum. |
80
+ | `className / body / footer` | `string / (node) => node / (node) => node` | | className = classes do `<form>`. body/footer = slots: recebem os campos / os botões e escolhem o invólucro — é como o ActionFormDialog injeta DialogBody/DialogFooter (scroll + faixa). |
81
+ | `children` | `ReactNode` | | Modo composição: diagrame com `<ActionFormField name />`. Sem children, o automático monta todos os campos na ordem do contrato. |
82
82
 
83
83
  ## Propriedades de ActionFormField
84
84
 
85
- | Propriedade | Tipo | Descrição |
86
- |---|---|---|
87
- | `name` | `string` | O campo do contrato (chave em `fields`/schema). Fora do schema, não renderiza. |
88
- | `options` | `SelectOption[]` | Opções por id de runtime — sobrepõe o fieldOptions do form e o z.enum. |
89
- | `className` | `string` | Classes do invólucro (ex.: `col-span-2` em uma grid). |
85
+ | Propriedade | Tipo | Descrição |
86
+ | ----------- | ---------------- | ------------------------------------------------------------------------------ |
87
+ | `name` | `string` | O campo do contrato (chave em `fields`/schema). Fora do schema, não renderiza. |
88
+ | `options` | `SelectOption[]` | Opções por id de runtime — sobrepõe o fieldOptions do form e o z.enum. |
89
+ | `className` | `string` | Classes do invólucro (ex.: `col-span-2` em uma grid). |
90
90
 
91
91
  ## Ajuda na label
92
92
 
@@ -110,9 +110,9 @@ lugar:
110
110
 
111
111
  ### Propriedades de LabelHelp
112
112
 
113
- | Propriedade | Tipo | Descrição |
114
- |---|---|---|
115
- | `help` | `string` | O texto do tooltip. Vazio ou ausente, o ícone não renderiza. |
113
+ | Propriedade | Tipo | Descrição |
114
+ | ----------- | -------- | ------------------------------------------------------------ |
115
+ | `help` | `string` | O texto do tooltip. Vazio ou ausente, o ícone não renderiza. |
116
116
 
117
117
  ## Origem das opções de seleção
118
118
 
@@ -137,25 +137,25 @@ Quando nenhum widget atender ao campo, use `useActionFormContext` no modo de com
137
137
  próprio participa da mesma validação e execução sem abandonar o `ActionForm`:
138
138
 
139
139
  ```tsx
140
- import { useActionFormContext } from '@softize/opus/ui/react'
140
+ import { useActionFormContext } from "@softize/opus/ui/react";
141
141
 
142
142
  function PermissionGrid() {
143
- const { form } = useActionFormContext()
144
- const perms = (form.watch('permissions') as string[] | undefined) ?? []
143
+ const { form } = useActionFormContext();
144
+ const perms = (form.watch("permissions") as string[] | undefined) ?? [];
145
145
  const toggle = (p: string) =>
146
146
  form.setValue(
147
- 'permissions',
147
+ "permissions",
148
148
  perms.includes(p) ? perms.filter((x) => x !== p) : [...perms, p],
149
149
  { shouldValidate: true, shouldDirty: true },
150
- )
151
- const error = form.formState.errors['permissions']
150
+ );
151
+ const error = form.formState.errors["permissions"];
152
152
  // …sua grade; zod-resolver, erro inline e submit continuam do contrato.
153
153
  }
154
154
 
155
155
  <ActionForm action={roleUpdate}>
156
156
  <ActionFormField name="name" />
157
157
  <PermissionGrid />
158
- </ActionForm>
158
+ </ActionForm>;
159
159
  ```
160
160
 
161
161
  O contexto também expõe `shape` (Zod por campo), `fields` (FieldSpec) e `fieldOptions`.
@@ -7,34 +7,43 @@ os filtros aplicados permanecem visíveis como chips removíveis.
7
7
 
8
8
  A barra se adapta ao espaço disponível. A busca cede largura primeiro e filtros que deixam de caber
9
9
  migram para o modal; a linha só quebra quando nenhum controle restante puder ceder espaço.
10
- `toolbarActions` acrescenta ações do consumidor ao fim da barra. Recarregar e exibição formam um
11
- `ButtonGroup` espaçado; as ações do consumidor vêm depois, com um intervalo maior para preservar a
12
- hierarquia entre ferramentas e a ação principal.
10
+ `toolbarActions` acrescenta operações ligadas ao recorte atual no fim da barra. Recarregar e
11
+ exibição formam um `ButtonGroup` espaçado; as ações do consumidor vêm depois, com um intervalo
12
+ maior para preservar a hierarquia entre controles e operações do recorte.
13
13
 
14
14
  ```tsx preview col
15
15
  render(
16
16
  <DocBrowserActionProvider>
17
17
  <div className="w-full">
18
- <ActionList action={docWorkspaceList} input={{}} emptyMessage="Nenhum workspace." />
18
+ <ActionList
19
+ action={docWorkspaceList}
20
+ input={{}}
21
+ emptyMessage="Nenhum workspace."
22
+ />
19
23
  </div>
20
24
  </DocBrowserActionProvider>,
21
- )
25
+ );
22
26
  ```
23
27
 
24
- Uma ação icon-only mantém o nome no tooltip e no `aria-label`:
28
+ ## Coleção nomeada
29
+
30
+ Em uma página, envolva a listagem com `Content`. O título e a criação ocupam extremos opostos do
31
+ cabeçalho da coleção; busca, filtros e atualização permanecem na toolbar do `ActionList`.
25
32
 
26
33
  ```tsx
27
- <ActionList
28
- action={workspaceList}
29
- input={{}}
30
- toolbarActions={
31
- <Button context="primary" size="icon" aria-label="Novo workspace">
32
- <Plus />
33
- </Button>
34
- }
35
- />
34
+ <Content
35
+ title="Workspaces"
36
+ level={1}
37
+ variant="page"
38
+ actions={<Button>Criar workspace</Button>}
39
+ >
40
+ <ActionList action={workspaceList} input={{}} />
41
+ </Content>
36
42
  ```
37
43
 
44
+ Use `toolbarActions` somente quando a operação depender do recorte visível, como exportar os
45
+ resultados filtrados. Uma ação icon-only nessa região mantém o nome no tooltip e no `aria-label`.
46
+
38
47
  ## Barra de filtros compartilhada
39
48
 
40
49
  `ActionFilterBar` expõe a mesma linguagem declarativa de busca, filtros e período para
@@ -89,7 +98,11 @@ render(
89
98
  input={{}}
90
99
  cells={{
91
100
  name: (w) => (
92
- <a href="#" onClick={(e) => e.preventDefault()} className="font-medium underline-offset-4 hover:underline">
101
+ <a
102
+ href="#"
103
+ onClick={(e) => e.preventDefault()}
104
+ className="font-medium underline-offset-4 hover:underline"
105
+ >
93
106
  {w.name}
94
107
  </a>
95
108
  ),
@@ -97,7 +110,7 @@ render(
97
110
  />
98
111
  </div>
99
112
  </DocBrowserActionProvider>,
100
- )
113
+ );
101
114
  ```
102
115
 
103
116
  ## Período obrigatório
@@ -109,10 +122,10 @@ como `?period=2026-07-01..2026-07-07`, e chega ao handler pelos parâmetros `fro
109
122
 
110
123
  ```tsx
111
124
  periods: [
112
- { value: 'today', label: 'Hoje' },
113
- { value: 'last7', label: 'Últimos 7 dias' },
114
- { value: 'thisMonth', label: 'Este mês' },
115
- ]
125
+ { value: "today", label: "Hoje" },
126
+ { value: "last7", label: "Últimos 7 dias" },
127
+ { value: "thisMonth", label: "Este mês" },
128
+ ];
116
129
  // Presets computáveis: today · yesterday · last7 · last30 · thisMonth · lastMonth.
117
130
  // presetRange('last7') → { from: 'YYYY-MM-DD', to: 'YYYY-MM-DD' } — o mesmo cálculo do pattern.
118
131
  ```
@@ -133,13 +146,19 @@ apenas essa seleção. Com `confirm`, o componente pede confirmação antes de e
133
146
  seleção é limpa e a consulta é refeita.
134
147
 
135
148
  ```tsx
136
- <ActionList action={runList} input={{}}
137
- batch={[{
138
- label: 'Reprocessar',
139
- can: (run) => run.status === 'failed',
140
- confirm: { title: 'Reprocessar as execuções?' },
141
- run: async (runs) => { await Promise.all(runs.map((r) => api.retry(r.id))) },
142
- }]}
149
+ <ActionList
150
+ action={runList}
151
+ input={{}}
152
+ batch={[
153
+ {
154
+ label: "Reprocessar",
155
+ can: (run) => run.status === "failed",
156
+ confirm: { title: "Reprocessar as execuções?" },
157
+ run: async (runs) => {
158
+ await Promise.all(runs.map((r) => api.retry(r.id)));
159
+ },
160
+ },
161
+ ]}
143
162
  />
144
163
  ```
145
164
 
@@ -159,17 +178,25 @@ render(
159
178
  input={{}}
160
179
  views={{
161
180
  gallery: {
162
- label: 'Galeria',
181
+ label: "Galeria",
163
182
  icon: <LayoutGrid />,
164
183
  render: (workspaces) => (
165
184
  <div className="grid grid-cols-2 gap-3">
166
185
  {workspaces.map((w) => (
167
- <div key={w.id} className="rounded-lg border border-border p-3">
186
+ <div
187
+ key={w.id}
188
+ className="rounded-lg border border-border p-3"
189
+ >
168
190
  <div className="flex items-center gap-2">
169
191
  <span className="text-sm font-semibold">{w.name}</span>
170
- <DictionaryValue dict={docWorkspaceStatus} value={w.status} />
192
+ <DictionaryValue
193
+ dict={docWorkspaceStatus}
194
+ value={w.status}
195
+ />
171
196
  </div>
172
- <p className="mt-1 text-xs text-muted-foreground">{w.agents} agentes.</p>
197
+ <p className="mt-1 text-xs text-muted-foreground">
198
+ {w.agents} agentes.
199
+ </p>
173
200
  </div>
174
201
  ))}
175
202
  </div>
@@ -179,7 +206,7 @@ render(
179
206
  />
180
207
  </div>
181
208
  </DocBrowserActionProvider>,
182
- )
209
+ );
183
210
  ```
184
211
 
185
212
  ## Filtros dependentes e opções remotas
@@ -217,7 +244,9 @@ podem ser serializados na query string pelos helpers do componente:
217
244
  action={contract}
218
245
  input={{}}
219
246
  state={listParamsToState(params, contract)}
220
- onStateChange={(next) => navigate(`/rota${listStateToParams(next, contract)}`)}
247
+ onStateChange={(next) =>
248
+ navigate(`/rota${listStateToParams(next, contract)}`)
249
+ }
221
250
  />
222
251
  ```
223
252
 
@@ -241,7 +270,9 @@ render(
241
270
  <span className="text-sm font-semibold">{w.name}</span>
242
271
  <DictionaryValue dict={docWorkspaceStatus} value={w.status} />
243
272
  </div>
244
- <p className="mt-1 text-xs text-muted-foreground">{w.agents} agentes.</p>
273
+ <p className="mt-1 text-xs text-muted-foreground">
274
+ {w.agents} agentes.
275
+ </p>
245
276
  </div>
246
277
  ))}
247
278
  </div>
@@ -249,31 +280,31 @@ render(
249
280
  </ActionList>
250
281
  </div>
251
282
  </DocBrowserActionProvider>,
252
- )
283
+ );
253
284
  ```
254
285
 
255
286
  ## Propriedades de ActionFilterBar
256
287
 
257
- | Propriedade | Tipo | Padrão | Descrição |
258
- |---|---|---|---|
259
- | `action` | `Pick<ListAction, 'filters' \| 'text' \| 'periods'>` | | Declara busca, filtros e períodos disponíveis. |
260
- | `state` | `ActionFilterState` | | Estado atual da barra. |
261
- | `onStateChange` | `(next) => void` | | Recebe o estado completo depois de cada alteração. |
262
- | `filterOptions` | `Record<string, SelectOption[]>` | | Fornece opções de runtime para filtros select e lookup. |
263
- | `onRefresh` | `() => Promise<void> \| void` | | Exibe a ação de recarregar e executa a consulta do consumidor. |
264
- | `refreshing` | `boolean` | `false` | Desabilita e anima a ação de recarregar durante a consulta. |
265
- | `controls` | `ReactNode` | | Controles auxiliares agrupados com recarregar em um `ButtonGroup` espaçado. |
266
- | `actions` | `ReactNode` | | Ações do consumidor exibidas depois do grupo de controles, com um intervalo maior. |
288
+ | Propriedade | Tipo | Padrão | Descrição |
289
+ | --------------- | ---------------------------------------------------- | ------- | ---------------------------------------------------------------------------------- |
290
+ | `action` | `Pick<ListAction, 'filters' \| 'text' \| 'periods'>` | | Declara busca, filtros e períodos disponíveis. |
291
+ | `state` | `ActionFilterState` | | Estado atual da barra. |
292
+ | `onStateChange` | `(next) => void` | | Recebe o estado completo depois de cada alteração. |
293
+ | `filterOptions` | `Record<string, SelectOption[]>` | | Fornece opções de runtime para filtros select e lookup. |
294
+ | `onRefresh` | `() => Promise<void> \| void` | | Exibe a ação de recarregar e executa a consulta do consumidor. |
295
+ | `refreshing` | `boolean` | `false` | Desabilita e anima a ação de recarregar durante a consulta. |
296
+ | `controls` | `ReactNode` | | Controles auxiliares agrupados com recarregar em um `ButtonGroup` espaçado. |
297
+ | `actions` | `ReactNode` | | Ações do consumidor exibidas depois do grupo de controles, com um intervalo maior. |
267
298
 
268
299
  ## Declaração na ListAction
269
300
 
270
- | Chave | O que declara |
271
- |---|---|
272
- | `columns` | `{ key, label, type?, sortable?, fit?, hidden?, dateFormat?, dictionary?, empty? }` — a tabela. `hidden` fica fora (base do futuro column picker); `dictionary` nomeia o dicionário do provider; `empty` dá o significado da ausência. |
301
+ | Chave | O que declara |
302
+ | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
303
+ | `columns` | `{ key, label, type?, sortable?, fit?, hidden?, dateFormat?, dictionary?, empty? }` — a tabela. `hidden` fica fora (base do futuro column picker); `dictionary` nomeia o dicionário do provider; `empty` dá o significado da ausência. |
273
304
  | `filters` | `{ [nome]: { label, type, options?, multiple?, advanced?, depends?, … } }` — a toolbar. `advanced` vai para o modal; `depends` desabilita/cascateia; options `kind: 'lookup'` busca em uma action. Valor aplicado entra no input com o mesmo nome. |
274
- | `text` | `{ fields }` — liga a busca; convenção: param `q` no input. Com período/filtros no contrato ela fica à direita; sendo a ÚNICA forma de recorte, abre a linha. |
275
- | `sort` | `{ fields, default }` — ordenação inicial; header ordenável escreve `sort: 'chave:dir'`. |
276
- | `periods` | `{ value, label }[]` — o controle de período (presets + Personalizado com calendário); materializa em `from`/`to` no input. |
305
+ | `text` | `{ fields }` — liga a busca; convenção: param `q` no input. Com período/filtros no contrato ela fica à direita; sendo a ÚNICA forma de recorte, abre a linha. |
306
+ | `sort` | `{ fields, default }` — ordenação inicial; header ordenável escreve `sort: 'chave:dir'`. |
307
+ | `periods` | `{ value, label }[]` — o controle de período (presets + Personalizado com calendário); materializa em `from`/`to` no input. |
277
308
 
278
309
  ## useListAction
279
310
 
@@ -285,21 +316,21 @@ chamar a action.
285
316
 
286
317
  ## Propriedades de ActionList
287
318
 
288
- | Propriedade | Tipo | Padrão | Descrição |
289
- |---|---|---|---|
290
- | `action` | `ListAction<TInput, TItem>` | | A ListAction do Opus (kind list, output = item, paginate cursor). |
291
- | `input` | `TInput` | | O ESCOPO BASE (ex.: { workspaceId }) — a toolbar soma por cima, nunca sobrescreve. |
292
- | `cells` | `Record<string, (item) => ReactNode>` | | Células custom por cima das colunas do contrato (chave = column.key). |
293
- | `filterOptions` | `Record<string, SelectOption[]>` | | Opções de runtime para os filtros select/lookup (chave = nome do filtro). |
294
- | `columns` | `ActionListColumn<TItem>[]` | | Tabela EXPLÍCITA — sobrepõe as colunas do contrato (escape hatch). |
295
- | `children` | `(items, refetch) => ReactNode` | | Modo composição: layout livre; a toolbar segue. Tem precedência sobre columns. |
296
- | `batch` | `ActionListBatchAction[]` | | Ações em lote: `{ label, can?, run, confirm?, destructive? }` — liga a multi-seleção. |
297
- | `rowId` | `(item) => string` | `item.id` | Identidade da linha para seleção. |
298
- | `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. |
299
- | `state / onStateChange` | `ActionListState / (next) => void` | interno | Estado da toolbar controlado — para quem embala sincronizar com a URL. |
300
- | `emptyMessage` | `string` | `'Nenhum resultado.'` | Frase do estado vazio (o `Empty` do `DataState`). |
301
- | `errorMessage` | `string` | `'Não foi possível carregar.'` | Título do aviso de erro; a ação de tentar de novo refaz a consulta. |
302
- | `retryLabel` | `string` | `'Tentar de novo'` | Nome acessível e tooltip da ação de recuperação. |
303
- | `onRowClick` | `(item: TItem) => void` | | Clique na linha (ex.: navegar para o detalhe) — só na tabela. |
304
- | `toolbarActions` | `ReactNode` | | Ações do consumidor no fim da barra, separadas do grupo de controles. |
305
- | `rowActions` | `(item: TItem) => ReactNode` | | Ações por linha (coluna final, à direita). A partir de duas, entram num grupo com intervalo compacto; uma só fica solta, sem grupo. Cliques ali não disparam o `onRowClick`. |
319
+ | Propriedade | Tipo | Padrão | Descrição |
320
+ | ----------------------- | ------------------------------------- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
321
+ | `action` | `ListAction<TInput, TItem>` | | A ListAction do Opus (kind list, output = item, paginate cursor). |
322
+ | `input` | `TInput` | | O ESCOPO BASE (ex.: { workspaceId }) — a toolbar soma por cima, nunca sobrescreve. |
323
+ | `cells` | `Record<string, (item) => ReactNode>` | | Células custom por cima das colunas do contrato (chave = column.key). |
324
+ | `filterOptions` | `Record<string, SelectOption[]>` | | Opções de runtime para os filtros select/lookup (chave = nome do filtro). |
325
+ | `columns` | `ActionListColumn<TItem>[]` | | Tabela EXPLÍCITA — sobrepõe as colunas do contrato (escape hatch). |
326
+ | `children` | `(items, refetch) => ReactNode` | | Modo composição: layout livre; a toolbar segue. Tem precedência sobre columns. |
327
+ | `batch` | `ActionListBatchAction[]` | | Ações em lote: `{ label, can?, run, confirm?, destructive? }` — liga a multi-seleção. |
328
+ | `rowId` | `(item) => string` | `item.id` | Identidade da linha para seleção. |
329
+ | `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. |
330
+ | `state / onStateChange` | `ActionListState / (next) => void` | interno | Estado da toolbar controlado — para quem embala sincronizar com a URL. |
331
+ | `emptyMessage` | `string` | `'Nenhum resultado.'` | Frase do estado vazio (o `Empty` do `DataState`). |
332
+ | `errorMessage` | `string` | `'Não foi possível carregar.'` | Título do aviso de erro; a ação de tentar de novo refaz a consulta. |
333
+ | `retryLabel` | `string` | `'Tentar de novo'` | Nome acessível e tooltip da ação de recuperação. |
334
+ | `onRowClick` | `(item: TItem) => void` | | Clique na linha (ex.: navegar para o detalhe) — só na tabela. |
335
+ | `toolbarActions` | `ReactNode` | | Operações ligadas ao recorte atual, no fim da barra e separadas do grupo de controles. |
336
+ | `rowActions` | `(item: TItem) => ReactNode` | | Ações por linha (coluna final, à direita). A partir de duas, entram num grupo com intervalo compacto; uma só fica solta, sem grupo. Cliques ali não disparam o `onRowClick`. |
@@ -39,7 +39,7 @@ confirm: {
39
39
 
40
40
  Com `icon`, o botão exibe somente o ícone no quadrado `icon-xs` da escala (1.5rem, a ação que mora
41
41
  dentro de uma linha ou card) e usa `label`, ou `action.label`, no tooltip e no nome acessível. Uma
42
- composição que peça mais presença, como a barra do `PageHeader`, declara `size="icon-sm"`. O clique
42
+ composição que peça mais presença, como a barra do `PageHeader`, declara `size="icon"`. O clique
43
43
  não aciona o item clicável ao redor. `itemLabel` identifica o registro na mensagem de confirmação.
44
44
 
45
45
  ```tsx
@@ -79,7 +79,7 @@ um atalho, um arrastar, um item de menu.
79
79
  | `action` | `SimpleContract<TInput, TData>` | | A SimpleAction do Opus — label, messages e confirm vêm do contrato. |
80
80
  | `input` | `TInput` | | O que a action recebe — geralmente { id }. |
81
81
  | `label` | `string` | `action.label` | Sobrepõe o texto do botão. |
82
- | `context / variant / size` | `do Button` | gatilho: `primary`/`solid`; `danger` quando a action é `destructive`; no modo ícone, `ghost` e `neutral` (ou `danger` se destrutiva) / `default` | Visual do gatilho; `context` explícito vence. O botão de confirmar é sempre `solid`: `danger` quando a action é `destructive`, senão a prop `context` do gatilho (ou `primary`). |
82
+ | `context / variant / size` | `do Button` | gatilho: `primary`/`solid`; `danger` quando a action é `destructive`; no modo ícone, `ghost` e `neutral` (ou `danger` se destrutiva) / `icon-xs` | Visual do gatilho; `context` explícito vence. O botão de confirmar é sempre `solid`: `danger` quando a action é `destructive`, senão a prop `context` do gatilho (ou `primary`). |
83
83
  | `confirm` | `{ title, description?, actionLabel?, cancelLabel? }` | | Confirmação via prop — sobrepõe o ConfirmSpec do contrato. |
84
84
  | `onSuccess` | `(data: TData) => void` | | Pós-sucesso (cache já invalidado pelo action.invalidates). |
85
85
  | `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. |
@@ -35,7 +35,7 @@ há texto visível. O glifo dentro do controle acompanha o tamanho (0.875rem em
35
35
  Escolha o tamanho pela região, não pela importância visual: `variant` e `context` resolvem a
36
36
  hierarquia da ação. Headers de Page e footers de Dialog/Drawer usam `default`; ações operacionais
37
37
  de seção, toolbar e coleção usam `sm`; ações dentro de linha ou célula usam `xs` ou `icon-xs`.
38
- Controles de chrome, como voltar e fechar, usam `icon-sm`. Assim a mesma decisão mantém a mesma
38
+ Controles de chrome, como voltar e fechar, usam `icon`. Assim a mesma decisão mantém a mesma
39
39
  altura mesmo quando uma superfície troca uma ação secundária por uma primária.
40
40
 
41
41
  ```tsx preview
@@ -93,9 +93,9 @@ componente ao trocar de conversa. O rótulo do indicador é customizável por
93
93
  `humanizeTool={(name) => '…'}`.
94
94
 
95
95
  Itens controlados podem carregar `id` e `createdAt`. O Chat os projeta no elemento da
96
- mensagem e oferece `renderMessageActions` para ações contextuais discretas. O slot não
97
- busca nem conhece detalhes: o app deve carregar informação complementar somente após a
98
- interação da pessoa e aplicar novamente sua autorização.
96
+ mensagem e oferece `renderMessageActions` para ações contextuais discretas tanto nas mensagens
97
+ enviadas quanto nas recebidas. O slot não busca nem conhece detalhes: o app deve carregar
98
+ informação complementar somente após a interação da pessoa e aplicar novamente sua autorização.
99
99
 
100
100
  ## Propriedades de Chat
101
101
 
@@ -105,7 +105,7 @@ interação da pessoa e aplicar novamente sua autorização.
105
105
  | `messages` | `ChatTranscriptItem[]` | | Modo controlado: o transcript vem do app; com ele, `onSend`, `busy` e `activity` assumem. |
106
106
  | `onSend` | `(text: string) => void \| boolean \| Promise<void \| boolean>` | | Modo controlado: recebe o texto enviado; retornar `false` devolve o texto ao composer. |
107
107
  | `busy` | `boolean` | | Modo controlado: trava o composer enquanto o turno corre. |
108
- | `activity` | `string \| null` | `undefined` | Indicador vivo: `null` mostra “Pensando…”, string mostra o rótulo; `undefined` esconde. |
108
+ | `activity` | `string \| null` | `undefined` | Indicador vivo: `null` mostra “Pensando…”, string mostra o rótulo, `''` centraliza somente os pontos; `undefined` esconde. |
109
109
  | `notice` | `ReactNode` | | Aviso do app acima do composer (credencial, agente desatualizado…). |
110
110
  | `composerActions` | `ReactNode` | | Seletores discretos na barra do composer (agente, app, escopo). |
111
111
  | `composerClassName` | `string` | | Ajusta o contêiner externo do composer sem alcançar o DOM interno. |
@@ -13,9 +13,11 @@ render(
13
13
  description="Sessões com acesso à sua conta."
14
14
  actions={<Button variant="outline">Encerrar outras sessões</Button>}
15
15
  >
16
- <div className="rounded-lg border border-border p-4">MacBook Para o · ativo agora</div>
16
+ <div className="rounded-lg border border-border p-4">
17
+ MacBook Para o · ativo agora
18
+ </div>
17
19
  </Content>,
18
- )
20
+ );
19
21
  ```
20
22
 
21
23
  ## Composição explícita
@@ -32,28 +34,42 @@ render(
32
34
  <ContentTitle>Dispositivos conectados</ContentTitle>
33
35
  <ContentMeta>3</ContentMeta>
34
36
  <ContentDescription>Sessões com acesso à sua conta.</ContentDescription>
35
- <ContentActions><Button variant="outline">Atualizar</Button></ContentActions>
37
+ <ContentActions>
38
+ <Button variant="outline">Atualizar</Button>
39
+ </ContentActions>
36
40
  </ContentHeader>
37
41
  <ContentBody>
38
- <div className="rounded-lg border border-border p-4">MacBook Para o · ativo agora</div>
42
+ <div className="rounded-lg border border-border p-4">
43
+ MacBook Para o · ativo agora
44
+ </div>
39
45
  </ContentBody>
40
46
  </Content>,
41
- )
47
+ );
42
48
  ```
43
49
 
44
50
  As duas formas geram os mesmos elementos, estilos e `data-slot`. `opus check` reprova slots fora
45
51
  do pai correto, filhos estruturais indiretos e a mistura de shorthand com composição explícita.
46
52
 
53
+ ## Coleção nomeada
54
+
55
+ Quando a região principal da página é uma coleção, `Content` nomeia e governa essa coleção, enquanto
56
+ `ActionList` executa a consulta. Coloque criar, importar e outras ações sobre o conjunto completo em
57
+ `ContentActions`. Busca, filtros, atualização e operações dependentes do recorte atual permanecem na
58
+ toolbar da lista. Essa divisão aproxima cada comando do objeto que ele afeta sem criar uma família
59
+ paralela de componentes `List*`. Na variante `page`, as ações ficam no extremo oposto ao título. A
60
+ criação usa um botão textual `default`, sem ícone, nomeado `Criar recurso`; o diálogo aberto pelo
61
+ gatilho repete esse título e a edição usa `Editar recurso`.
62
+
47
63
  ## Propriedades de Content
48
64
 
49
- | Propriedade | Tipo | Padrão | Descrição |
50
- |---|---|---|---|
51
- | `title` | `ReactNode` | | Forma curta: título da região (vira o heading ligado à `section`). |
52
- | `count` | `number` | | Forma curta: total de itens ao lado do título da região. |
53
- | `description` | `ReactNode` | | Forma curta: frase de apoio sob o título. |
54
- | `actions` | `ReactNode` | | Forma curta: ações alinhadas à direita do header. |
55
- | `level` | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | herdado | Nível semântico do heading, independente do destaque visual. |
56
- | `variant` | `'page' \| 'section'` | `'section'` | Hierarquia visual: `page` reproduz o cabeçalho de `Page`; `section` é a região dentro de uma superfície. |
65
+ | Propriedade | Tipo | Padrão | Descrição |
66
+ | ------------- | ---------------------------- | ----------- | -------------------------------------------------------------------------------------------------------- |
67
+ | `title` | `ReactNode` | | Forma curta: título da região (vira o heading ligado à `section`). |
68
+ | `count` | `number` | | Forma curta: total de itens ao lado do título da região. |
69
+ | `description` | `ReactNode` | | Forma curta: frase de apoio sob o título. |
70
+ | `actions` | `ReactNode` | | Forma curta: ações sobre a região inteira; em `page`, ficam no extremo oposto ao título. |
71
+ | `level` | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | herdado | Nível semântico do heading, independente do destaque visual. |
72
+ | `variant` | `'page' \| 'section'` | `'section'` | Hierarquia visual: `page` reproduz o cabeçalho de `Page`; `section` é a região dentro de uma superfície. |
57
73
 
58
74
  Na composição explícita, `ContentHeader` recebe `ContentTitle`, `ContentMeta`, `ContentDescription` e
59
75
  `ContentActions`, e `ContentBody` recebe o conteúdo; nenhum desses slots aceita `title` ou `level`
@@ -21,27 +21,31 @@ borda semântica, raio `xl` e elevação para permanecer distinta da página.
21
21
  ```tsx preview
22
22
  <Dialog>
23
23
  <DialogTrigger asChild>
24
- <Button variant="outline">Novo workspace</Button>
24
+ <Button>Criar workspace</Button>
25
25
  </DialogTrigger>
26
26
  <DialogContent>
27
27
  <DialogHeader>
28
- <DialogTitle>Novo workspace</DialogTitle>
28
+ <DialogTitle>Criar workspace</DialogTitle>
29
29
  </DialogHeader>
30
30
  <DialogBody className="space-y-2">
31
31
  <Label htmlFor="workspace-name">Nome</Label>
32
32
  <Input id="workspace-name" placeholder="Ex.: Empresa X" />
33
33
  </DialogBody>
34
34
  <DialogFooter>
35
- <DialogClose asChild>
36
- <Button variant="ghost">Cancelar</Button>
37
- </DialogClose>
38
- <Button>Criar workspace</Button>
35
+ <ButtonGroup mode="spaced" distribution="equal">
36
+ <DialogClose asChild>
37
+ <Button variant="outline">Cancelar</Button>
38
+ </DialogClose>
39
+ <Button>Criar workspace</Button>
40
+ </ButtonGroup>
39
41
  </DialogFooter>
40
42
  </DialogContent>
41
43
  </Dialog>
42
44
  ```
43
45
 
44
46
  O corpo cresce até o limite da janela e passa a rolar; cabeçalho e rodapé permanecem visíveis.
47
+ O `ButtonGroup` divide o espaço entre decisões equivalentes no rodapé. O `DialogFooter` organiza
48
+ a faixa, mas não decide a distribuição nem a aparência dos botões.
45
49
  `DialogClose` encerra o modal sem exigir estado controlado. Use `open` e `onOpenChange` quando outra
46
50
  parte da interface também precisar controlar a abertura.
47
51
 
@@ -207,16 +211,18 @@ function ResponseDialogDemo() {
207
211
  O contrato possui alterações que ainda não foram salvas.
208
212
  </p>
209
213
  </DialogBody>
210
- <DialogFooter className="grid-cols-3">
211
- <DialogClose result="continue" initialFocus asChild>
212
- <Button variant="ghost">Continuar editando</Button>
213
- </DialogClose>
214
- <DialogClose result="discard" asChild>
215
- <Button variant="outline">Descartar</Button>
216
- </DialogClose>
217
- <DialogClose result="save" asChild>
218
- <Button>Salvar e fechar</Button>
219
- </DialogClose>
214
+ <DialogFooter>
215
+ <ButtonGroup mode="spaced" distribution="equal">
216
+ <DialogClose result="continue" initialFocus asChild>
217
+ <Button variant="outline">Continuar editando</Button>
218
+ </DialogClose>
219
+ <DialogClose result="discard" asChild>
220
+ <Button variant="outline">Descartar</Button>
221
+ </DialogClose>
222
+ <DialogClose result="save" asChild>
223
+ <Button>Salvar e fechar</Button>
224
+ </DialogClose>
225
+ </ButtonGroup>
220
226
  </DialogFooter>
221
227
  </DialogContent>
222
228
  </Dialog>
@@ -286,10 +292,10 @@ botão. O texto informa o efeito real, como `Excluir`, `Revogar acesso` ou `Ence
286
292
 
287
293
  ## Propriedades de DialogFooter
288
294
 
289
- | Propriedade | Tipo | Padrão | Descrição |
290
- | ----------- | ----------- | ------ | ---------------------------------------------------------------- |
291
- | `className` | `string` | | Ajusta a faixa do modo padrão ou a grade de respostas do alerta. |
292
- | `children` | `ReactNode` | | Ações secundárias e principal. |
295
+ | Propriedade | Tipo | Padrão | Descrição |
296
+ | ----------- | ----------- | ------ | ------------------------------------------- |
297
+ | `className` | `string` | | Ajusta a faixa do modo padrão ou do alerta. |
298
+ | `children` | `ReactNode` | | Ações secundárias e principal. |
293
299
 
294
300
  ## Propriedades de DialogTitle
295
301
 
@@ -380,5 +386,5 @@ imperativa.
380
386
  | `label` | `ReactNode` | | Rótulo que descreve o resultado da ação. |
381
387
  | `initialFocus` | `boolean` | `false` | Marca a única saída segura que recebe o foco inicial. |
382
388
  | `context` | `ButtonContext` | Última ação: `primary`; demais: `neutral` | Define o significado semântico. |
383
- | `variant` | `ButtonVariant` | Última ação: `solid`; demais: `ghost` | Define o tratamento visual. |
389
+ | `variant` | `ButtonVariant` | Última ação: `solid`; demais: `outline` | Define o tratamento visual. |
384
390
  | `disabled` | `boolean` | `false` | Impede a escolha desta ação. |
@@ -25,8 +25,8 @@ entra pela direita e pode ser fechado por Esc, pelo overlay ou pelo botão de fe
25
25
 
26
26
  `side` aceita `top`, `right`, `bottom` e `left`. `DrawerFooter` mantém as ações no rodapé;
27
27
  `DrawerClose` fecha o painel sem exigir controle manual de estado. Como no `Dialog`, o cabeçalho
28
- recebe um divisor inferior e o rodapé usa divisor superior sobre fundo sutil. O corpo preserva o
29
- mesmo alinhamento horizontal entre título, conteúdo e ações.
28
+ recebe um divisor inferior e o rodapé usa um divisor superior. O corpo preserva o mesmo alinhamento
29
+ horizontal entre título, conteúdo e ações.
30
30
 
31
31
  ```tsx preview
32
32
  <Drawer>
@@ -42,10 +42,12 @@ mesmo alinhamento horizontal entre título, conteúdo e ações.
42
42
  <Input id="ws-nome" defaultValue="Empresa X" />
43
43
  </DrawerBody>
44
44
  <DrawerFooter>
45
- <DrawerClose asChild>
46
- <Button variant="ghost">Cancelar</Button>
47
- </DrawerClose>
48
- <Button>Salvar alterações</Button>
45
+ <ButtonGroup mode="spaced" distribution="equal">
46
+ <DrawerClose asChild>
47
+ <Button variant="outline">Cancelar</Button>
48
+ </DrawerClose>
49
+ <Button>Salvar</Button>
50
+ </ButtonGroup>
49
51
  </DrawerFooter>
50
52
  </DrawerContent>
51
53
  </Drawer>