@softize/opus 19.0.0 → 20.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +239 -0
- package/bin/cli.mjs +5 -0
- package/bin/lib/check.mjs +198 -10
- package/bin/lib/copy.mjs +29 -0
- package/bin/lib/mcp.mjs +1 -1
- package/docs/adr/0020-icon-tiles-are-explicitly-composed.md +8 -0
- package/docs/adr/0021-content-carries-structure-and-semantic-element.md +7 -0
- package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +19 -1
- package/docs/adr/0024-fill-height-propagates-by-context.md +118 -0
- package/docs/adr/0025-content-frame-treatment-does-not-change-semantics.md +134 -0
- package/docs/data-products.md +15 -9
- package/docs/protocol.md +3 -4
- package/docs/releasing.md +2 -2
- package/package.json +2 -2
- package/registry/skills/create-opus-action/scripts/scaffold.mjs +2 -1
- package/registry/templates/app/package.json +1 -1
- package/src/core/runtime.ts +96 -8
- package/src/core/types.ts +5 -2
- package/src/ui/components/patterns/action-form-card.tsx +11 -1
- package/src/ui/components/patterns/content-header.tsx +109 -18
- package/src/ui/components/patterns/fill-region.ts +150 -0
- package/src/ui/components/patterns/list.tsx +91 -92
- package/src/ui/components/patterns/page.tsx +20 -4
- package/src/ui/components/patterns/presentation.tsx +52 -37
- package/src/ui/components/patterns/split.tsx +44 -4
- package/src/ui/components/patterns/surface-anatomy.ts +35 -8
- package/src/ui/components/patterns/surface-header.tsx +58 -25
- package/src/ui/components/patterns/trigger.tsx +12 -2
- package/src/ui/components/primitives/alert.tsx +9 -1
- package/src/ui/components/primitives/avatar.tsx +7 -3
- package/src/ui/components/primitives/card.tsx +1 -1
- package/src/ui/components/primitives/carousel.tsx +34 -5
- package/src/ui/components/primitives/checkbox.tsx +11 -3
- package/src/ui/components/primitives/command.tsx +2 -1
- package/src/ui/components/primitives/copyable.tsx +120 -10
- package/src/ui/components/primitives/dialog.tsx +42 -11
- package/src/ui/components/primitives/drawer.tsx +10 -2
- package/src/ui/components/primitives/empty.tsx +1 -1
- package/src/ui/components/primitives/icon-picker.tsx +6 -2
- package/src/ui/components/primitives/icon-tile.tsx +7 -0
- package/src/ui/components/primitives/input-group.tsx +9 -2
- package/src/ui/components/primitives/input-otp.tsx +1 -1
- package/src/ui/components/primitives/kbd.tsx +1 -1
- package/src/ui/components/primitives/label.tsx +3 -0
- package/src/ui/components/primitives/menu.tsx +5 -0
- package/src/ui/components/primitives/popover.tsx +1 -1
- package/src/ui/components/primitives/progress.tsx +9 -1
- package/src/ui/components/primitives/slider.tsx +62 -11
- package/src/ui/components/primitives/truncate.tsx +10 -1
- package/src/ui/docs/content/accordion.md +74 -27
- package/src/ui/docs/content/action-form-card.md +24 -15
- package/src/ui/docs/content/action-form-dialog.md +17 -7
- package/src/ui/docs/content/action-form.md +92 -77
- package/src/ui/docs/content/action-list-dialog.md +75 -38
- package/src/ui/docs/content/action-list.md +141 -58
- package/src/ui/docs/content/action-trigger.md +27 -10
- package/src/ui/docs/content/action-view.md +14 -14
- package/src/ui/docs/content/actions.md +161 -66
- package/src/ui/docs/content/ai.md +43 -44
- package/src/ui/docs/content/alert.md +83 -29
- package/src/ui/docs/content/ask.md +51 -26
- package/src/ui/docs/content/aspect-ratio.md +22 -24
- package/src/ui/docs/content/audit.md +23 -19
- package/src/ui/docs/content/auth.md +38 -19
- package/src/ui/docs/content/avatar.md +54 -5
- package/src/ui/docs/content/badge.md +29 -9
- package/src/ui/docs/content/breadcrumb.md +21 -8
- package/src/ui/docs/content/button.md +73 -43
- package/src/ui/docs/content/calendar.md +134 -26
- package/src/ui/docs/content/card.md +41 -15
- package/src/ui/docs/content/carousel.md +37 -6
- package/src/ui/docs/content/chat.md +118 -37
- package/src/ui/docs/content/checkbox.md +73 -7
- package/src/ui/docs/content/cli.md +94 -55
- package/src/ui/docs/content/collapsible.md +43 -19
- package/src/ui/docs/content/command.md +30 -8
- package/src/ui/docs/content/composer.md +23 -8
- package/src/ui/docs/content/content.md +119 -27
- package/src/ui/docs/content/copyable.md +39 -8
- package/src/ui/docs/content/customization.md +55 -46
- package/src/ui/docs/content/cycle.md +33 -18
- package/src/ui/docs/content/data-products.md +115 -0
- package/src/ui/docs/content/data-state.md +20 -6
- package/src/ui/docs/content/data.md +20 -19
- package/src/ui/docs/content/detail.md +12 -5
- package/src/ui/docs/content/dialog.md +107 -24
- package/src/ui/docs/content/dictionary-value.md +4 -4
- package/src/ui/docs/content/dock.md +54 -19
- package/src/ui/docs/content/dot.md +35 -5
- package/src/ui/docs/content/drawer.md +63 -19
- package/src/ui/docs/content/empty-value.md +20 -6
- package/src/ui/docs/content/empty.md +24 -3
- package/src/ui/docs/content/events.md +76 -16
- package/src/ui/docs/content/executions.md +9 -9
- package/src/ui/docs/content/field.md +21 -6
- package/src/ui/docs/content/getting-started.md +44 -34
- package/src/ui/docs/content/icon-picker.md +19 -13
- package/src/ui/docs/content/icon-tile.md +38 -15
- package/src/ui/docs/content/input-otp.md +58 -7
- package/src/ui/docs/content/input.md +22 -3
- package/src/ui/docs/content/item.md +53 -6
- package/src/ui/docs/content/kbd.md +5 -1
- package/src/ui/docs/content/label.md +13 -4
- package/src/ui/docs/content/log.md +5 -5
- package/src/ui/docs/content/markdown.md +5 -4
- package/src/ui/docs/content/mcp.md +49 -23
- package/src/ui/docs/content/menu.md +95 -10
- package/src/ui/docs/content/metric-card.md +12 -4
- package/src/ui/docs/content/observability.md +10 -9
- package/src/ui/docs/content/page.md +118 -12
- package/src/ui/docs/content/pagination.md +13 -5
- package/src/ui/docs/content/pane.md +55 -5
- package/src/ui/docs/content/popover.md +42 -5
- package/src/ui/docs/content/presentation.md +91 -47
- package/src/ui/docs/content/progress.md +27 -14
- package/src/ui/docs/content/queue.md +39 -21
- package/src/ui/docs/content/radio-group.md +26 -4
- package/src/ui/docs/content/router.md +29 -10
- package/src/ui/docs/content/runtime.md +90 -54
- package/src/ui/docs/content/scheduler.md +53 -21
- package/src/ui/docs/content/scroll-area.md +42 -35
- package/src/ui/docs/content/select.md +78 -33
- package/src/ui/docs/content/semantic-context.md +21 -2
- package/src/ui/docs/content/separator.md +15 -9
- package/src/ui/docs/content/sidebar.md +3 -2
- package/src/ui/docs/content/skeleton.md +6 -1
- package/src/ui/docs/content/slider.md +37 -9
- package/src/ui/docs/content/spinner.md +4 -3
- package/src/ui/docs/content/split.md +19 -0
- package/src/ui/docs/content/storage.md +8 -9
- package/src/ui/docs/content/switch.md +10 -7
- package/src/ui/docs/content/table.md +46 -8
- package/src/ui/docs/content/tabs.md +14 -4
- package/src/ui/docs/content/testing.md +76 -31
- package/src/ui/docs/content/textarea.md +5 -4
- package/src/ui/docs/content/toast.md +48 -7
- package/src/ui/docs/content/toggle.md +3 -3
- package/src/ui/docs/content/tokens.md +15 -26
- package/src/ui/docs/content/tooltip.md +18 -2
- package/src/ui/docs/content/truncate.md +36 -6
- package/src/ui/docs/content/ui.md +74 -22
- package/src/ui/docs/content/upgrading.md +45 -36
- package/src/ui/docs/plugin.ts +36 -2
- package/src/ui/docs/registry.tsx +6 -0
- package/src/ui/drivers/react.tsx +16 -8
- package/src/ui/meta.ts +32 -32
- package/src/ui/react.tsx +1 -0
- package/src/vite/design.ts +16 -4
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Produtos de Dados
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Produtos de Dados
|
|
6
|
+
|
|
7
|
+
Use um Produto de Dados quando um conjunto de dados precisar de identidade estável para catálogos,
|
|
8
|
+
agentes e revisão, independentemente da action que o entrega. A declaração diz o que o conjunto
|
|
9
|
+
representa, de onde vem, quem responde por ele e quais actions o disponibilizam. Ela não é uma
|
|
10
|
+
consulta, uma tabela nem uma view, e não executa nada: cada action continua sendo a fronteira de
|
|
11
|
+
execução, recorte e autorização.
|
|
12
|
+
|
|
13
|
+
## Declarar e registrar
|
|
14
|
+
|
|
15
|
+
`defineDataProduct` declara o produto e valida seus campos no momento em que roda. O domínio o
|
|
16
|
+
registra em `dataProducts`, ao lado das actions e entidades citadas:
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { defineDataProduct, defineDomain } from '@softize/opus/core'
|
|
20
|
+
import { LeadEntity } from './entity.ts' // defineEntity({ name: 'lead', … })
|
|
21
|
+
import { leadList } from './actions/list.ts' // action 'lead.list'
|
|
22
|
+
import { leadFunnel } from './actions/funnel.ts' // action 'lead.funnel'
|
|
23
|
+
|
|
24
|
+
export const salesLeads = defineDataProduct({
|
|
25
|
+
id: 'sales.leads',
|
|
26
|
+
version: 1,
|
|
27
|
+
label: 'Leads comerciais',
|
|
28
|
+
description: 'Leads e seus resultados comerciais.',
|
|
29
|
+
owner: 'Vendas',
|
|
30
|
+
grain: 'Um lead.',
|
|
31
|
+
classification: 'internal',
|
|
32
|
+
nature: 'real',
|
|
33
|
+
sources: [{ id: 'crm', label: 'CRM comercial' }],
|
|
34
|
+
entities: ['lead'],
|
|
35
|
+
access: {
|
|
36
|
+
permissionContexts: ['sales'],
|
|
37
|
+
organizationalScopes: ['unit', 'team'],
|
|
38
|
+
},
|
|
39
|
+
interfaces: ['lead.list', 'lead.funnel'],
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
export const leadDomain = defineDomain({
|
|
43
|
+
name: 'lead',
|
|
44
|
+
entities: { LeadEntity },
|
|
45
|
+
actions: { leadList, leadFunnel },
|
|
46
|
+
dataProducts: { salesLeads },
|
|
47
|
+
})
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`interfaces` e `entities` usam os nomes declarados em `bindAction`/`defineContract` e em
|
|
51
|
+
`defineEntity`, não nomes de arquivo ou de tabela. `sources` identifica os sistemas ou conjuntos de
|
|
52
|
+
origem; uma fonte não precisa ser uma entidade do Opus.
|
|
53
|
+
|
|
54
|
+
## Verificações
|
|
55
|
+
|
|
56
|
+
A declaração é conferida em três momentos:
|
|
57
|
+
|
|
58
|
+
- `defineDataProduct` lança `TypeError` quando o `id` não tem namespace (como `sales.leads`), a
|
|
59
|
+
`version` não é um inteiro positivo, falta texto obrigatório, `sources` ou `interfaces` estão
|
|
60
|
+
vazias ou há itens repetidos.
|
|
61
|
+
- `defineDomain` recusa um produto duplicado no domínio e referências a actions ou entidades que o
|
|
62
|
+
domínio não declara (`domain.data_product_interface_not_found` e
|
|
63
|
+
`domain.data_product_entity_not_found`).
|
|
64
|
+
- `opus check` lê o código sem executá-lo e confere exportação, `id`, `version`, duplicidade e as
|
|
65
|
+
referências a actions e entidades do projeto, com as regras de prefixo `data-product`. Para isso,
|
|
66
|
+
o produto precisa ser `export const` e usar valores literais.
|
|
67
|
+
|
|
68
|
+
## Acesso e segurança
|
|
69
|
+
|
|
70
|
+
`access` documenta o alcance esperado de forma verificável. Ele ajuda o catálogo e a revisão a
|
|
71
|
+
perceber, por exemplo, que uma interface deveria respeitar unidade e equipe. `access` não é RLS nem
|
|
72
|
+
autorização automática: cada action listada em `interfaces` precisa aplicar o próprio `authorize` e
|
|
73
|
+
os recortes no handler ou no repositório, inclusive quando chamada por IA ou por
|
|
74
|
+
[MCP](/docs/mcp).
|
|
75
|
+
|
|
76
|
+
`permissionContexts` nomeia chaves do vocabulário de permissão do projeto. Ele não descreve área,
|
|
77
|
+
departamento nem contexto de tela; um projeto que não usa essa dimensão declara uma lista vazia.
|
|
78
|
+
O nome antigo `contexts` ainda é aceito e projetado para compatibilidade, mas declarações novas usam
|
|
79
|
+
`permissionContexts`.
|
|
80
|
+
|
|
81
|
+
## Onde o produto aparece
|
|
82
|
+
|
|
83
|
+
- `opus gen` publica os produtos no `.opus/manifest.json`, e `opus introspect` os inclui no modelo
|
|
84
|
+
do projeto. Veja [CLI opus](/docs/cli).
|
|
85
|
+
- Em `runtime.aiTools()`, cada action exposta à IA traz em `metadata.dataProducts` os IDs dos
|
|
86
|
+
produtos relacionados e, em `metadata.dataProductLabels`, o `label` de cada ID. Veja
|
|
87
|
+
[IA generativa](/docs/ai).
|
|
88
|
+
- No servidor MCP, as mesmas informações aparecem em `_meta['com.softize.opus/data-products']` e
|
|
89
|
+
`_meta['com.softize.opus/data-product-labels']`.
|
|
90
|
+
|
|
91
|
+
Esses metadados servem a quem hospeda o agente: mostre o `label` às pessoas e use o ID na execução e
|
|
92
|
+
na auditoria. Eles não entram no prompt e não concedem acesso. Nenhuma dessas projeções é a fonte
|
|
93
|
+
da verdade: para mudar um produto, corrija a declaração e gere de novo.
|
|
94
|
+
|
|
95
|
+
## Evoluir um produto
|
|
96
|
+
|
|
97
|
+
Incremente `version` quando a forma observável do produto mudar. Para substituí-lo, mantenha a
|
|
98
|
+
declaração anterior com `status: 'deprecated'` e `replacedBy` apontando para o novo ID durante a
|
|
99
|
+
migração. Um produto ativo não declara `replacedBy`.
|
|
100
|
+
|
|
101
|
+
## Referência de DataProductConfig
|
|
102
|
+
|
|
103
|
+
| Campo | Contrato |
|
|
104
|
+
| --- | --- |
|
|
105
|
+
| `id` | Identidade estável com namespace, como `sales.leads`. |
|
|
106
|
+
| `version` | Inteiro positivo; muda com a forma observável do produto. |
|
|
107
|
+
| `label`, `description` | Nome e descrição para pessoas. |
|
|
108
|
+
| `owner` | Responsável técnico ou de negócio. |
|
|
109
|
+
| `grain` | O que uma linha ou observação representa. |
|
|
110
|
+
| `classification`, `nature` | Classificação (como `internal` ou `personal`) e natureza (como `real`, `demo` ou `synthetic`) definidas pelo projeto. |
|
|
111
|
+
| `sources` | Pelo menos uma fonte `{ id, label, description? }`, sem `id` repetido. |
|
|
112
|
+
| `entities` | Nomes das entidades Opus que estruturam o produto; pode ser vazia. |
|
|
113
|
+
| `access` | `permissionContexts` e `organizationalScopes`, ambos listas de texto. |
|
|
114
|
+
| `interfaces` | Nomes das actions que disponibilizam o produto; pelo menos uma. |
|
|
115
|
+
| `status`, `replacedBy` | `active` (padrão) ou `deprecated`; um produto `deprecated` exige `replacedBy`. |
|
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
## Estados
|
|
2
2
|
|
|
3
|
-
Use `DataState` para apresentar carregamento, erro, vazio e conteúdo de uma mesma consulta
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
Use `DataState` para apresentar carregamento, erro, vazio e conteúdo de uma mesma consulta, na escala
|
|
4
|
+
de uma seção. Para o estado da página inteira, use `PageState`, descrito em [`Page`](/ui/page); para
|
|
5
|
+
uma coleção carregada por uma action `kind: 'list'`, [`ActionList`](/ui/action-list) já trata esses
|
|
6
|
+
estados.
|
|
7
|
+
|
|
8
|
+
Cada estado tem uma apresentação fixa:
|
|
9
|
+
|
|
10
|
+
- **Carregando:** um contêiner `role="status"` com o [`Spinner`](/ui/spinner) centralizado. Quando o
|
|
11
|
+
formato do conteúdo já é conhecido, em vez de passar `loading`, renderize o
|
|
12
|
+
[`Skeleton`](/ui/skeleton) durante a espera: ele reproduz esse formato e evita o salto de layout.
|
|
13
|
+
- **Vazio:** um [`Empty`](/ui/empty) com moldura sólida e `emptyMessage` como frase discreta.
|
|
14
|
+
- **Erro:** um [`Alert`](/ui/alert) de contexto `danger` com a mensagem segura de `errorMessage`,
|
|
15
|
+
sem expor o texto técnico de `error`. Com `onRetry`, o aviso ganha uma ação `ghost` só com ícone,
|
|
16
|
+
e `retryLabel` fornece o nome acessível e o tooltip dessa ação.
|
|
17
|
+
|
|
18
|
+
Quando mais de uma condição vale ao mesmo tempo, vence a primeira desta ordem: erro, carregando,
|
|
19
|
+
vazio. O conteúdo aparece somente quando nenhuma delas vale. Assim, uma nova tentativa com
|
|
20
|
+
`loading` e `error` ativos continua mostrando o erro até que `error` volte a `null`.
|
|
21
|
+
|
|
22
|
+
Para uma ação em andamento depois do clique, use `busy` em [`Button`](/ui/button).
|
|
9
23
|
|
|
10
24
|
```tsx preview col
|
|
11
25
|
<div className="w-full space-y-3">
|
|
@@ -32,9 +32,9 @@ export const SkillEntity = defineEntity({
|
|
|
32
32
|
|
|
33
33
|
## Código em camelCase, banco em snake_case
|
|
34
34
|
|
|
35
|
-
O schema tipado do Kysely e todas as consultas usam camelCase
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
O schema tipado do Kysely e todas as consultas usam camelCase (`workspaceId`, `ghRepo`). O banco
|
|
36
|
+
usa snake_case, definido na DDL do schema idempotente, como `db/schema.sql`. `CamelCasePlugin`
|
|
37
|
+
converte os nomes nas consultas e nos resultados. Um sink que não usa o Kysely com o
|
|
38
38
|
plugin, como `audit_log`, mantém os nomes do banco.
|
|
39
39
|
|
|
40
40
|
```ts
|
|
@@ -52,19 +52,20 @@ await db.selectFrom('agents').select(['isDefault', 'roleId']).where('workspaceId
|
|
|
52
52
|
|
|
53
53
|
Cada etapa possui um papel diferente:
|
|
54
54
|
|
|
55
|
-
- `opus db migrate`
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
- `opus seed`
|
|
59
|
-
|
|
55
|
+
- `opus db migrate` aplica o schema idempotente (`config.schema`), um único script SQL em que
|
|
56
|
+
`IF NOT EXISTS` e condições de guarda cobrem tanto o banco novo quanto a atualização. Em seguida,
|
|
57
|
+
compara entidades e banco e encerra com exit ≠ 0 quando há divergência.
|
|
58
|
+
- `opus seed` aplica datasets de desenvolvimento e teste declarados com `defineSeed` e registrados no
|
|
59
|
+
`opus.config.ts`. Ele só conecta com `NODE_ENV=development` ou `test`. Veja
|
|
60
|
+
[CLI opus](/docs/cli).
|
|
60
61
|
|
|
61
62
|
## Campos JSON
|
|
62
63
|
|
|
63
|
-
`kyselyRepo` serializa campos `t.json()` na escrita (`insert` e `update`), guiado pela
|
|
64
|
-
declaração. Sem
|
|
65
|
-
array do
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
`kyselyRepo` serializa os campos `t.json()` na escrita (`insert` e `update`), guiado pela
|
|
65
|
+
declaração da entidade. Sem essa etapa, o driver `pg` converte um objeto simples, mas transforma um
|
|
66
|
+
array em literal de array do Postgres, o que é inválido para `jsonb` e não aparece no typecheck.
|
|
67
|
+
Uma string passa sem alteração, o que preserva valores já serializados; na leitura, o `pg` devolve
|
|
68
|
+
o objeto. Uma consulta escrita fora do repositório continua responsável pela própria serialização.
|
|
68
69
|
|
|
69
70
|
## SQL gerado por modelo
|
|
70
71
|
|
|
@@ -79,13 +80,13 @@ const bi = readOnlyContextPool({ pool: new Pool({ connectionString, max: 4 }) })
|
|
|
79
80
|
const { rows } = await bi.query(sqlDoLlm, { role: roleFor(ctx) })
|
|
80
81
|
```
|
|
81
82
|
|
|
82
|
-
1. **Pool com limite** (`max`
|
|
83
|
+
1. **Pool com limite** (`max`, `maxConcurrent` ou ambos): impede que as consultas consumam todas as
|
|
83
84
|
conexões do aplicativo.
|
|
84
|
-
2. **`BEGIN TRANSACTION READ ONLY
|
|
85
|
-
3. **`SET LOCAL ROLE` por transação
|
|
86
|
-
|
|
87
|
-
4. **Protocolo estendido
|
|
88
|
-
(`SELECT 1; DROP …`)
|
|
85
|
+
2. **`BEGIN TRANSACTION READ ONLY`:** o servidor rejeita tentativas de escrita.
|
|
86
|
+
3. **`SET LOCAL ROLE` por transação:** o contexto define o alcance da consulta. Crie os roles nas
|
|
87
|
+
suas migrações com grants fechados por padrão; sem isso, `sales_read` leria `hr_employees`.
|
|
88
|
+
4. **Protocolo estendido:** o SQL sempre recebe os valores separadamente, e múltiplas instruções
|
|
89
|
+
(`SELECT 1; DROP …`) são recusadas pelo próprio protocolo.
|
|
89
90
|
|
|
90
91
|
Ao terminar, `DISCARD ALL` limpa a conexão. Se a limpeza falhar, a conexão é descartada em vez de
|
|
91
92
|
voltar ao pool. `statement_timeout`, aplicado por transação, limita consultas demoradas; o padrão é
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Use `DetailGroup` para organizar dados somente leitura como pares de rótulo e valor.
|
|
2
2
|
`DetailField` representa cada par e aceita conteúdo React em `label` e `value`. Para entrada e
|
|
3
|
-
validação, use `Field
|
|
3
|
+
validação, use [`Field`](/ui/field).
|
|
4
4
|
|
|
5
5
|
```tsx preview col
|
|
6
6
|
<DetailGroup columns={2}>
|
|
@@ -27,7 +27,8 @@ validação, use `Field`.
|
|
|
27
27
|
|
|
28
28
|
`null`, `undefined`, string vazia ou só com espaços mostram um travessão; a leitura assistiva
|
|
29
29
|
recebe “Não informado”. Uma string em `empty` troca esse significado no domínio, enquanto um nó
|
|
30
|
-
React substitui a apresentação. `0` e `false` seguem como valores.
|
|
30
|
+
React substitui a apresentação. `0` e `false` seguem como valores. A regra é a mesma de
|
|
31
|
+
[`EmptyValue`](/ui/empty-value).
|
|
31
32
|
|
|
32
33
|
```tsx preview col
|
|
33
34
|
<DetailGroup columns={2}>
|
|
@@ -64,15 +65,21 @@ começam depois da mesma coluna de rótulo, com largura padrão de `7rem` em gru
|
|
|
64
65
|
| Propriedade | Tipo | Padrão | Descrição |
|
|
65
66
|
|---|---|---|---|
|
|
66
67
|
| `variant` | `'plain' \| 'framed'` | `'plain'` | `framed` aplica a moldura canônica ao conjunto, com fundo transparente; na orientação horizontal, a coluna de rótulos recebe fundo sutil. |
|
|
67
|
-
| `dividers` | `boolean` | `false` |
|
|
68
|
+
| `dividers` | `boolean` | `false` | Linhas finas somente entre os campos, sem exigir moldura externa. |
|
|
68
69
|
| `columns` | `1 \| 2 \| 3 \| 4 \| 'auto'` | `1` | Número fixo de colunas ou distribuição automática por largura mínima. |
|
|
69
70
|
| `orientation` | `'vertical' \| 'horizontal'` | `'vertical'` | Chave sobre o valor ou ao lado dele em cada campo. |
|
|
70
71
|
|
|
72
|
+
O grupo renderiza uma lista de definições (`<dl>`); os demais atributos, como `className`, vão para
|
|
73
|
+
ela.
|
|
74
|
+
|
|
71
75
|
## Propriedades de DetailField
|
|
72
76
|
|
|
73
77
|
| Propriedade | Tipo | Padrão | Descrição |
|
|
74
78
|
|---|---|---|---|
|
|
75
|
-
| `label` | `ReactNode` | | A chave do par. |
|
|
76
|
-
| `value` | `ReactNode` | | O valor; `null`, `undefined` e string vazia renderizam a ausência, `0` e `false` seguem como valores. |
|
|
79
|
+
| `label` | `ReactNode` | obrigatório | A chave do par. |
|
|
80
|
+
| `value` | `ReactNode` | obrigatório | O valor; `null`, `undefined` e string vazia renderizam a ausência, `0` e `false` seguem como valores. |
|
|
77
81
|
| `icon` | `ReactNode` | | Ícone decorativo antes do par chave/valor. |
|
|
78
82
|
| `empty` | `ReactNode` | `“Não informado”` | Uma string dá significado acessível ao travessão; um nó próprio substitui a apresentação. |
|
|
83
|
+
|
|
84
|
+
Os demais atributos, como `className`, vão para o contêiner do par. `DetailField` não aceita
|
|
85
|
+
`children`: o conteúdo entra por `value`.
|
|
@@ -10,7 +10,8 @@ contexto da página. A forma escolhida depende do tipo de interrupção:
|
|
|
10
10
|
| Pedir reconhecimento, confirmação binária ou uma string sem montar a superfície. | Use `dialog.alert`, `dialog.confirm` ou `dialog.prompt`. |
|
|
11
11
|
| Escolher entre vários resultados simples. | Use `dialog.choose`. |
|
|
12
12
|
|
|
13
|
-
Para conteúdo ancorado e não modal, use `Popover
|
|
13
|
+
Para conteúdo ancorado e não modal, use [`Popover`](/ui/popover). Para uma lista de ações, use
|
|
14
|
+
[`Menu`](/ui/menu).
|
|
14
15
|
|
|
15
16
|
## Compor uma tarefa modal
|
|
16
17
|
|
|
@@ -118,13 +119,13 @@ fluxo modal encadeado, em que a superfície permanece aberta e troca de etapa.
|
|
|
118
119
|
<DialogContent>
|
|
119
120
|
<DialogHeader>
|
|
120
121
|
<DialogNavigation>
|
|
121
|
-
<Button variant="ghost" size="icon
|
|
122
|
+
<Button variant="ghost" size="icon" aria-label="Voltar para a lista">
|
|
122
123
|
<ArrowLeft />
|
|
123
124
|
</Button>
|
|
124
125
|
</DialogNavigation>
|
|
125
126
|
<DialogTitle>Evento 4821</DialogTitle>
|
|
126
127
|
<DialogActions>
|
|
127
|
-
<Button variant="ghost" size="icon
|
|
128
|
+
<Button variant="ghost" size="icon" aria-label="Mais ações">
|
|
128
129
|
<Ellipsis />
|
|
129
130
|
</Button>
|
|
130
131
|
</DialogActions>
|
|
@@ -136,9 +137,82 @@ fluxo modal encadeado, em que a superfície permanece aberta e troca de etapa.
|
|
|
136
137
|
</Dialog>
|
|
137
138
|
```
|
|
138
139
|
|
|
140
|
+
Voltar e ações do cabeçalho usam `Button` `ghost` no tamanho `icon` (2.25rem), o mesmo que a
|
|
141
|
+
[`Presentation`](/ui/presentation) usa no voltar. O botão de fechar é menor: um `CloseButton` no
|
|
142
|
+
quadrado `icon-sm` (1.75rem) por padrão.
|
|
143
|
+
|
|
139
144
|
O cabeçalho não recebe descrição: um modal já é a superfície de uma tarefa, e o contexto que ajuda a
|
|
140
145
|
decidir aparece no começo do corpo, como texto ou `Alert`.
|
|
141
146
|
|
|
147
|
+
## Identificar a tarefa com um ícone
|
|
148
|
+
|
|
149
|
+
Use `DialogMedia` quando um ícone ajuda a reconhecer a tarefa antes da leitura do título. No modo
|
|
150
|
+
padrão, ele abre a linha do cabeçalho com a mesma moldura de [`IconTile`](/ui/icon-tile) das demais
|
|
151
|
+
superfícies. No `mode="alert"`, a moldura cresce e fica centralizada acima do título, porque ali o
|
|
152
|
+
ícone é o principal sinal de reconhecimento da resposta.
|
|
153
|
+
|
|
154
|
+
```tsx preview
|
|
155
|
+
render(
|
|
156
|
+
<div className="flex flex-wrap gap-3">
|
|
157
|
+
<Dialog>
|
|
158
|
+
<DialogTrigger asChild>
|
|
159
|
+
<Button variant="outline">Compartilhar contrato</Button>
|
|
160
|
+
</DialogTrigger>
|
|
161
|
+
<DialogContent>
|
|
162
|
+
<DialogHeader>
|
|
163
|
+
<DialogMedia>
|
|
164
|
+
<Share2 />
|
|
165
|
+
</DialogMedia>
|
|
166
|
+
<DialogTitle>Compartilhar contrato</DialogTitle>
|
|
167
|
+
</DialogHeader>
|
|
168
|
+
<DialogBody className="space-y-2">
|
|
169
|
+
<Label htmlFor="share-email">E-mail</Label>
|
|
170
|
+
<Input id="share-email" placeholder="nome@empresa.com.br" />
|
|
171
|
+
</DialogBody>
|
|
172
|
+
<DialogFooter>
|
|
173
|
+
<ButtonGroup mode="spaced">
|
|
174
|
+
<DialogClose asChild>
|
|
175
|
+
<Button variant="ghost">Cancelar</Button>
|
|
176
|
+
</DialogClose>
|
|
177
|
+
<Button>Compartilhar</Button>
|
|
178
|
+
</ButtonGroup>
|
|
179
|
+
</DialogFooter>
|
|
180
|
+
</DialogContent>
|
|
181
|
+
</Dialog>
|
|
182
|
+
<Dialog mode="alert">
|
|
183
|
+
<DialogTrigger asChild>
|
|
184
|
+
<Button variant="outline">Revogar acesso</Button>
|
|
185
|
+
</DialogTrigger>
|
|
186
|
+
<DialogContent>
|
|
187
|
+
<DialogHeader>
|
|
188
|
+
<DialogMedia context="danger">
|
|
189
|
+
<ShieldOff />
|
|
190
|
+
</DialogMedia>
|
|
191
|
+
<DialogTitle>Revogar acesso de Marina?</DialogTitle>
|
|
192
|
+
</DialogHeader>
|
|
193
|
+
<DialogBody>
|
|
194
|
+
<p className="text-sm text-muted-foreground">
|
|
195
|
+
Ela deixa de ver o contrato imediatamente.
|
|
196
|
+
</p>
|
|
197
|
+
</DialogBody>
|
|
198
|
+
<DialogFooter>
|
|
199
|
+
<ButtonGroup mode="spaced" distribution="equal">
|
|
200
|
+
<DialogClose initialFocus asChild>
|
|
201
|
+
<Button variant="outline">Cancelar</Button>
|
|
202
|
+
</DialogClose>
|
|
203
|
+
<DialogClose asChild>
|
|
204
|
+
<Button context="danger">Revogar acesso</Button>
|
|
205
|
+
</DialogClose>
|
|
206
|
+
</ButtonGroup>
|
|
207
|
+
</DialogFooter>
|
|
208
|
+
</DialogContent>
|
|
209
|
+
</Dialog>
|
|
210
|
+
</div>,
|
|
211
|
+
);
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Na API imperativa, a opção `media` recebe o ícone e o apresenta na mesma moldura do alerta.
|
|
215
|
+
|
|
142
216
|
## Pedir uma resposta pela API imperativa
|
|
143
217
|
|
|
144
218
|
Use `dialog.alert`, `dialog.confirm`, `dialog.prompt` e `dialog.choose` quando a resposta couber em
|
|
@@ -169,12 +243,13 @@ function Demo() {
|
|
|
169
243
|
size="sm"
|
|
170
244
|
onClick={async () => {
|
|
171
245
|
const accepted = await dialog.confirm({
|
|
172
|
-
title: "Excluir
|
|
173
|
-
body: "O
|
|
246
|
+
title: "Excluir rascunho?",
|
|
247
|
+
body: "O rascunho e os comentários dele serão removidos.",
|
|
248
|
+
media: <Trash2 />,
|
|
174
249
|
action: "Excluir",
|
|
175
250
|
context: "danger",
|
|
176
251
|
});
|
|
177
|
-
setResult(accepted ? "
|
|
252
|
+
setResult(accepted ? "Rascunho excluído." : "Rascunho mantido.");
|
|
178
253
|
}}
|
|
179
254
|
>
|
|
180
255
|
Confirmar exclusão
|
|
@@ -184,14 +259,14 @@ function Demo() {
|
|
|
184
259
|
size="sm"
|
|
185
260
|
onClick={async () => {
|
|
186
261
|
const name = await dialog.prompt({
|
|
187
|
-
title: "Renomear
|
|
262
|
+
title: "Renomear versão",
|
|
188
263
|
action: "Renomear",
|
|
189
264
|
placeholder: "Ex.: versão final",
|
|
190
265
|
});
|
|
191
266
|
setResult(name === null ? "Nome mantido." : `Novo nome: ${name}`);
|
|
192
267
|
}}
|
|
193
268
|
>
|
|
194
|
-
Renomear
|
|
269
|
+
Renomear versão
|
|
195
270
|
</Button>
|
|
196
271
|
<Button
|
|
197
272
|
variant="outline"
|
|
@@ -229,7 +304,8 @@ render(<Demo />);
|
|
|
229
304
|
`dialog.prompt` devolve `Promise<string | null>` e `dialog.choose` infere a união dos resultados
|
|
230
305
|
declarados, acrescida de `null`. Fechar ou cancelar resolve a resposta negativa de cada operação,
|
|
231
306
|
em vez de deixar a promise pendente. No aviso com uma ação, o botão ocupa toda a largura do rodapé;
|
|
232
|
-
confirmações e prompts distribuem confirmar e cancelar em duas colunas.
|
|
307
|
+
confirmações e prompts distribuem confirmar e cancelar em duas colunas. O `dialog.choose` usa uma
|
|
308
|
+
superfície mais larga, de `32rem`, para que as ações caibam lado a lado.
|
|
233
309
|
|
|
234
310
|
Em `dialog.choose`, declare pelo menos uma ação e marque exatamente uma ação habilitada com
|
|
235
311
|
`initialFocus`. Essa ação representa a saída segura. Os valores de `result` precisam ser únicos.
|
|
@@ -238,7 +314,7 @@ neutro e variante outline. Defina `context` ou `variant` na própria ação para
|
|
|
238
314
|
|
|
239
315
|
### Montar o host
|
|
240
316
|
|
|
241
|
-
Monte um `DialogHost` no shell do aplicativo, ao lado do `Toaster
|
|
317
|
+
Monte um `DialogHost` no shell do aplicativo, ao lado do [`Toaster`](/ui/toast):
|
|
242
318
|
|
|
243
319
|
```tsx
|
|
244
320
|
<>
|
|
@@ -248,8 +324,8 @@ Monte um `DialogHost` no shell do aplicativo, ao lado do `Toaster`:
|
|
|
248
324
|
</>
|
|
249
325
|
```
|
|
250
326
|
|
|
251
|
-
Sem o host, a
|
|
252
|
-
aparecem uma por vez, preservando a resposta de cada `await`.
|
|
327
|
+
Sem o host, a promise é rejeitada imediatamente com um erro. Solicitações concorrentes entram em
|
|
328
|
+
uma fila e aparecem uma por vez, preservando a resposta de cada `await`.
|
|
253
329
|
|
|
254
330
|
### Adicionar contexto relevante
|
|
255
331
|
|
|
@@ -259,21 +335,21 @@ texto, um `Alert` ou uma composição própria:
|
|
|
259
335
|
|
|
260
336
|
```tsx
|
|
261
337
|
const accepted = await dialog.confirm({
|
|
262
|
-
title: "
|
|
338
|
+
title: "Revogar três acessos?",
|
|
263
339
|
body: (
|
|
264
340
|
<Alert
|
|
265
341
|
context="warning"
|
|
266
|
-
description="
|
|
342
|
+
description="As pessoas convidadas deixam de ver o contrato imediatamente."
|
|
267
343
|
/>
|
|
268
344
|
),
|
|
269
|
-
action: "
|
|
345
|
+
action: "Revogar acessos",
|
|
270
346
|
});
|
|
271
347
|
```
|
|
272
348
|
|
|
273
|
-
Use `ActionTrigger` quando a confirmação já pertencer ao contrato de uma
|
|
274
|
-
`ActionFormDialog` para formulários com validação ou vários
|
|
275
|
-
uma string livre. Componha `Dialog` quando uma ação precisar
|
|
276
|
-
aberta para exibir carregamento, validação ou falha antes do fechamento.
|
|
349
|
+
Use [`ActionTrigger`](/ui/action-trigger) quando a confirmação já pertencer ao contrato de uma
|
|
350
|
+
action. Use [`ActionFormDialog`](/ui/action-form-dialog) para formulários com validação ou vários
|
|
351
|
+
campos. `dialog.prompt` atende somente uma string livre. Componha `Dialog` quando uma ação precisar
|
|
352
|
+
manter a superfície aberta para exibir carregamento, validação ou falha antes do fechamento.
|
|
277
353
|
|
|
278
354
|
## Compor uma resposta personalizada
|
|
279
355
|
|
|
@@ -282,6 +358,10 @@ com o clique externo. Prefira a API imperativa quando a resposta couber em `aler
|
|
|
282
358
|
`prompt` ou `choose`. Componha o componente quando a resposta depender de estado próprio ou de um
|
|
283
359
|
layout específico.
|
|
284
360
|
|
|
361
|
+
O alerta tem `20rem` de largura máxima, suficiente para duas ações lado a lado. Com três ações,
|
|
362
|
+
amplie a superfície com `className="max-w-lg"` em `DialogContent`, a mesma largura que o
|
|
363
|
+
`dialog.choose` usa; sem isso, os rótulos não cabem no rodapé.
|
|
364
|
+
|
|
285
365
|
```tsx preview
|
|
286
366
|
function ResponseDialogDemo() {
|
|
287
367
|
const [result, setResult] = useState("Nenhuma resposta.");
|
|
@@ -292,7 +372,7 @@ function ResponseDialogDemo() {
|
|
|
292
372
|
<DialogTrigger asChild>
|
|
293
373
|
<Button variant="outline">Fechar editor</Button>
|
|
294
374
|
</DialogTrigger>
|
|
295
|
-
<DialogContent>
|
|
375
|
+
<DialogContent className="max-w-lg">
|
|
296
376
|
<DialogHeader>
|
|
297
377
|
<DialogTitle>Fechar editor?</DialogTitle>
|
|
298
378
|
</DialogHeader>
|
|
@@ -328,13 +408,16 @@ render(<ResponseDialogDemo />);
|
|
|
328
408
|
contexto, variante e tamanho pertencem ao botão. `result` identifica a resposta entregue a
|
|
329
409
|
`Dialog onResult`. Marque com `initialFocus` a saída segura — normalmente `Cancelar`; em um aviso
|
|
330
410
|
com uma única ação, marque essa ação. Para uma confirmação destrutiva, use `context="danger"` no
|
|
331
|
-
botão. O texto informa o efeito real, como `Excluir`, `Revogar acesso` ou `
|
|
411
|
+
botão. O texto informa o efeito real, como `Excluir`, `Revogar acesso` ou `Arquivar contratos`.
|
|
332
412
|
|
|
333
413
|
## Acessibilidade
|
|
334
414
|
|
|
335
415
|
- `DialogTitle` nomeia a superfície para tecnologias assistivas. Quando um texto conciso do corpo
|
|
336
416
|
também precisar descrevê-la, relacione seu `id` por `aria-describedby` em `DialogContent`.
|
|
337
417
|
- O modo padrão permite fechamento externo; `mode="alert"` exige uma resposta explícita.
|
|
418
|
+
- Enquanto uma operação não puder ser interrompida, `closeDisabled` em `DialogContent` impede que o
|
|
419
|
+
botão de fechar, Esc e o clique externo fechem a superfície. As ações que você compõe com
|
|
420
|
+
`DialogClose` continuam ativas; desabilite-as pelo mesmo estado da operação.
|
|
338
421
|
- O comportamento selecionado por `mode` determina o papel de acessibilidade; `role` não configura
|
|
339
422
|
o comportamento.
|
|
340
423
|
- O foco permanece dentro do diálogo aberto e retorna ao gatilho após o fechamento.
|
|
@@ -363,10 +446,10 @@ botão. O texto informa o efeito real, como `Excluir`, `Revogar acesso` ou `Ence
|
|
|
363
446
|
| Propriedade | Tipo | Padrão | Descrição |
|
|
364
447
|
| ----------------- | ----------- | ------ | --------------------------------------------------------------------------------------------------------- |
|
|
365
448
|
| `showCloseButton` | `boolean` | `true` | No modo padrão, inclui a ação de fechamento no header; o modo de alerta exige uma resposta identificável. |
|
|
366
|
-
| `closeDisabled` | `boolean` | `false` | Mantém
|
|
449
|
+
| `closeDisabled` | `boolean` | `false` | Mantém o botão de fechar visível e desabilitado e impede que Esc e o clique externo fechem a superfície. Handlers como `onEscapeKeyDown` e `onPointerDownOutside` continuam sendo chamados antes do bloqueio. |
|
|
367
450
|
| `closeSize` | `"xs" \| "sm"` | `"sm"` | Define o tamanho da ação circular de fechar. |
|
|
368
451
|
| `layout` | `'continuous' \| 'sectioned'` | `'sectioned'` | Anatomia da superfície inteira; os slots leem esse valor e não o recebem. Ignorado em `mode="alert"`. |
|
|
369
|
-
| `className` | `string` | | Ajusta a superfície.
|
|
452
|
+
| `className` | `string` | | Ajusta a superfície. A largura máxima é `32rem` no modo padrão e `20rem` no alerta; amplie o alerta com `max-w-lg` quando o rodapé tiver três ações. |
|
|
370
453
|
| `children` | `ReactNode` | | Cabeçalho, corpo, rodapé ou conteúdo próprio. |
|
|
371
454
|
|
|
372
455
|
## Propriedades de DialogHeader
|
|
@@ -409,7 +492,7 @@ botão. O texto informa o efeito real, como `Excluir`, `Revogar acesso` ou `Ence
|
|
|
409
492
|
| Propriedade | Tipo | Padrão | Descrição |
|
|
410
493
|
| ----------- | ----------- | ------ | ------------------------------------ |
|
|
411
494
|
| `level` | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | `h2` da primitiva | Hierarquia do documento. O tamanho vem do `layout`, nunca do nível. |
|
|
412
|
-
| `className` | `string` | | Ajusta o título,
|
|
495
|
+
| `className` | `string` | | Ajusta o título. No modo padrão, ele trunca quando não cabe na linha; no alerta, fica centralizado e pode ocupar mais de uma linha. |
|
|
413
496
|
| `children` | `ReactNode` | | Nome da tarefa ou do conteúdo modal. |
|
|
414
497
|
|
|
415
498
|
## Propriedades de DialogMedia
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Use `DictionaryValue` para apresentar um valor de `t.dict` de acordo com o papel declarado pelo
|
|
2
|
-
próprio dicionário. A tela fornece o dicionário e o código; o componente escolhe texto,
|
|
3
|
-
sem perder o rótulo visível.
|
|
2
|
+
próprio dicionário. A tela fornece o dicionário e o código; o componente escolhe texto,
|
|
3
|
+
[`Badge`](/ui/badge) e ícone sem perder o rótulo visível.
|
|
4
4
|
|
|
5
5
|
```tsx preview
|
|
6
6
|
<DictionaryValue
|
|
@@ -56,7 +56,7 @@ export const customerStageDict = t.dict(
|
|
|
56
56
|
- `context` (`neutral` · `info` · `success` · `warning` · `danger`) só existe em status e estágio;
|
|
57
57
|
`t.dict` rejeita o contexto em classificação e em `plain`.
|
|
58
58
|
- `icon` é um nome do catálogo do Opus (`iconPickerIcons`). Nome fora do catálogo não renderiza
|
|
59
|
-
ícone; o
|
|
59
|
+
ícone; o componente não escolhe um substituto. Vocabulário próprio entra por `icons`.
|
|
60
60
|
- `description` é o texto curto para a pessoa e aparece em tooltip focável. Descrição igual ao
|
|
61
61
|
rótulo não vira tooltip. `doc` é o entendimento de negócio para manifest e Lens; não aparece.
|
|
62
62
|
|
|
@@ -100,7 +100,7 @@ Valor fora do dicionário aparece como texto com o próprio código, marcado com
|
|
|
100
100
|
|
|
101
101
|
## Em ActionList
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
Em [`ActionList`](/ui/action-list), a coluna cujo campo do schema de saída é um `t.dict().zod()` já renderiza `DictionaryValue` com o
|
|
104
104
|
papel declarado, sem `cells`. Quando o campo de saída é uma string comum, a coluna nomeia o
|
|
105
105
|
dicionário registrado no provider: `{ key: 'source', label: 'Fonte', dictionary: 'customerSource' }`.
|
|
106
106
|
Dimensões independentes (tipo e estágio, por exemplo) ficam em colunas distintas; não empilhar
|