@softize/opus 22.2.0 → 23.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.
- package/CHANGELOG.md +39 -1
- package/bin/cli.mjs +1 -9
- package/bin/lib/check.mjs +1 -7
- package/bin/lib/copy.mjs +1 -1
- package/bin/lib/gen-runner.mjs +2 -0
- package/bin/lib/gen-stubs.mjs +3 -1
- package/docs/adr/0005-structural-surfaces-share-an-explicit-anatomy.md +1 -1
- package/docs/adr/0032-tab-baseline-respects-the-container-inset.md +16 -12
- package/docs/adr/0034-tabs-can-own-a-frame-without-owning-a-surface.md +8 -6
- package/docs/adr/0036-tabs-declares-trigger-distribution.md +11 -8
- package/docs/adr/0037-design-data-lives-outside-the-contract.md +1 -1
- package/docs/adr/0038-content-level-defines-primary-presentation.md +62 -0
- package/docs/adr/0039-filter-presentation-is-independent-from-filter-type.md +62 -0
- package/docs/adr/0040-tabs-use-a-single-navigation-presentation.md +60 -0
- package/docs/code-style.md +1 -1
- package/package.json +1 -1
- package/registry/skills/build-opus-ui/references/ui-patterns.md +1 -1
- package/registry/skills/create-opus-action/references/contract-and-binding.md +3 -3
- package/src/core/contracts.ts +1 -1
- package/src/core/runtime.ts +4 -37
- package/src/core/types.ts +5 -23
- package/src/design/index.ts +14 -3
- package/src/testing/index.ts +6 -26
- package/src/ui/components/patterns/content-header.tsx +5 -9
- package/src/ui/components/patterns/list.tsx +55 -1
- package/src/ui/components/patterns/presentation.tsx +1 -1
- package/src/ui/components/primitives/icon-picker.tsx +6 -0
- package/src/ui/components/primitives/tabs.tsx +55 -87
- package/src/ui/components/primitives/toggle.tsx +1 -1
- package/src/ui/docs/content/action-list.md +13 -4
- package/src/ui/docs/content/actions.md +6 -7
- package/src/ui/docs/content/content.md +5 -6
- package/src/ui/docs/content/semantic-context.md +0 -1
- package/src/ui/docs/content/tabs.md +68 -69
- package/src/ui/docs/content/testing.md +2 -3
- package/src/ui/docs/content/toggle.md +15 -10
- package/src/ui/drivers/react.tsx +4 -3
- package/src/ui/meta.ts +2 -2
- package/src/ui/react.tsx +2 -3
- package/src/vite/design.ts +23 -21
|
@@ -42,7 +42,6 @@ extremos opostos do cabeçalho da coleção; busca, filtros e atualização perm
|
|
|
42
42
|
<Content
|
|
43
43
|
title="Workspaces"
|
|
44
44
|
level={1}
|
|
45
|
-
variant="page"
|
|
46
45
|
actions={<Button>Criar workspace</Button>}
|
|
47
46
|
>
|
|
48
47
|
<ActionList action={workspaceList} input={{}} />
|
|
@@ -238,7 +237,7 @@ render(
|
|
|
238
237
|
<div className="h-80 w-full overflow-x-auto overflow-y-hidden rounded-lg border border-border">
|
|
239
238
|
<Page layout="fill">
|
|
240
239
|
<PageBody>
|
|
241
|
-
<Content level={1}
|
|
240
|
+
<Content level={1}>
|
|
242
241
|
<ContentHeader>
|
|
243
242
|
<ContentTitle>Workspaces</ContentTitle>
|
|
244
243
|
<ContentActions>
|
|
@@ -276,7 +275,7 @@ componentes no caminho. Nos demais casos, a decisão é sua:
|
|
|
276
275
|
|
|
277
276
|
- Para a lista manter a altura dos resultados, como em uma seção secundária cuja rolagem pertence ao
|
|
278
277
|
`PageBody`, declare `layout="flow"`.
|
|
279
|
-
- Um contêiner próprio entre `ContentBody` e a lista, como um `
|
|
278
|
+
- Um contêiner próprio entre `ContentBody` e a lista, como um `TabsPanel`, não repassa a altura, e
|
|
280
279
|
a lista volta a `flow`. Declare `layout="fill"` na lista e dê ao contêiner as classes
|
|
281
280
|
`flex min-h-0 flex-1 flex-col`.
|
|
282
281
|
- Dentro de [`Card`](/ui/card), [`Dialog`](/ui/dialog), [`Drawer`](/ui/drawer) ou
|
|
@@ -364,6 +363,9 @@ render(
|
|
|
364
363
|
|
|
365
364
|
- **`depends: ['outroFiltro']`:** desabilita o filtro até que suas dependências tenham valor e o
|
|
366
365
|
limpa quando uma delas muda.
|
|
366
|
+
- **`presentation: 'toggle'`:** mantém visíveis as opções curtas de um `select` de escolha única;
|
|
367
|
+
estado, URL e input da action continuam iguais aos do controle padrão. Entradas de dicionário
|
|
368
|
+
com `icon` apresentam o ícone junto ao rótulo.
|
|
367
369
|
- **`options: { kind: 'dictionary', ref: 'sessionStatus' }`:** resolve opções e rótulos por um
|
|
368
370
|
dicionário registrado no `OpusProvider`. `filterOptions` substitui essa fonte quando informado.
|
|
369
371
|
- **`options: { kind: 'lookup', source: 'x.lookup' }`:** consulta uma action com `{ q }` e os valores
|
|
@@ -371,6 +373,13 @@ render(
|
|
|
371
373
|
|
|
372
374
|
```tsx
|
|
373
375
|
filters: {
|
|
376
|
+
channel: {
|
|
377
|
+
label: 'Canal',
|
|
378
|
+
type: 'select',
|
|
379
|
+
presentation: 'toggle',
|
|
380
|
+
allowEmpty: false,
|
|
381
|
+
options: { kind: 'static', items: [...] },
|
|
382
|
+
},
|
|
374
383
|
project: { label: 'Projeto', type: 'select', options: { kind: 'static', items: [...] } },
|
|
375
384
|
sprint: {
|
|
376
385
|
label: 'Sprint',
|
|
@@ -469,7 +478,7 @@ render(
|
|
|
469
478
|
| Chave | O que declara |
|
|
470
479
|
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
471
480
|
| `columns` | `{ key, label, type?, sortable?, fit?, hidden?, dateFormat?, dictionary?, empty? }` — a tabela. `hidden` nasce oculta e pode ser exibida pelo seletor de colunas; `dictionary` nomeia o dicionário do provider; `empty` dá o significado da ausência. |
|
|
472
|
-
| `filters` | `{ [nome]: { label, type, options?, multiple?, searchable?, allowEmpty?, placement?, section?, depends?, … } }` — a toolbar. `placement` classifica o controle como `inline`, `advanced` ou `external`; filtros avançados com `section` são agrupados no diálogo; `external` mantém estado e input, mas delega a apresentação ao consumidor. `searchable` habilita busca local em `select`; `allowEmpty: false` omite a opção vazia. `depends` desabilita/cascateia; options `kind: 'lookup'` busca em uma action. Valor aplicado entra no input com o mesmo nome. |
|
|
481
|
+
| `filters` | `{ [nome]: { label, type, options?, multiple?, presentation?, searchable?, allowEmpty?, placement?, section?, depends?, … } }` — a toolbar. `placement` classifica o controle como `inline`, `advanced` ou `external`; filtros avançados com `section` são agrupados no diálogo; `external` mantém estado e input, mas delega a apresentação ao consumidor. `presentation: 'toggle'` mostra todas as opções curtas de um `select` de escolha única em um `ToggleGroup`; sem a propriedade, o controle continua sendo `Select`. `searchable` habilita busca local em `select`; `allowEmpty: false` omite a opção vazia. `depends` desabilita/cascateia; options `kind: 'lookup'` busca em uma action. Valor aplicado entra no input com o mesmo nome. |
|
|
473
482
|
| `text` | `{ fields }` — liga a busca; convenção: param `q` no input. Ela aparece depois dos filtros inline e imediatamente antes do acesso aos filtros avançados. A busca participa da contagem de controles inline: com mais de um controle, a barra exibe as labels; quando ela é o único recorte, sua label é omitida. |
|
|
474
483
|
| `sort` | `{ fields, default }` — ordenação inicial; header ordenável escreve `sort: 'chave:dir'`. |
|
|
475
484
|
| `periods` | `{ value, label, default? }[]` — o controle de período (presets + Personalizado com calendário); `default: true` marca o preset inicial, senão vale o primeiro; materializa em `from`/`to` no input. |
|
|
@@ -120,8 +120,8 @@ nome de uma action não começa pelo caminho do domínio, como `workspace.` ou `
|
|
|
120
120
|
`subdomains` aceita outros domínios e forma esse caminho.
|
|
121
121
|
|
|
122
122
|
`actions` aceita um mapa, uma lista ou o resultado de `import * as actions`. Um domínio também pode
|
|
123
|
-
registrar só o contrato, sem handler:
|
|
124
|
-
fora dele, a execução falha por falta de handler.
|
|
123
|
+
registrar só o contrato, sem handler: o plugin do modo design liga uma resposta gerada ou um
|
|
124
|
+
cenário; fora dele, a execução falha por falta de handler.
|
|
125
125
|
|
|
126
126
|
## Ordem canônica dos campos
|
|
127
127
|
|
|
@@ -134,7 +134,7 @@ name, kind
|
|
|
134
134
|
→ label, title, summary, description, icon, color, messages, tags, examples, errors
|
|
135
135
|
→ input, output, effect, personalData
|
|
136
136
|
→ public, requires, authorize, loads
|
|
137
|
-
→ handler
|
|
137
|
+
→ handler
|
|
138
138
|
→ audit, confirm, invalidates, rateLimit, idempotency
|
|
139
139
|
→ ai, automatable, internal
|
|
140
140
|
```
|
|
@@ -228,10 +228,9 @@ O Opus não decide onde os cenários ficam nem se entram no repositório. Eles n
|
|
|
228
228
|
aplicação: podem ficar numa pasta ignorada pelo git e ser escritos durante a sessão de design,
|
|
229
229
|
quando forem necessários.
|
|
230
230
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
para o design, transforme-a em cenário.
|
|
231
|
+
Dados de design não pertencem ao contrato. Quando uma resposta específica importar para a revisão
|
|
232
|
+
da interface, declare um cenário com `defineDesignScenario`; para os demais casos, preserve apenas
|
|
233
|
+
o contrato e deixe o plugin gerar a resposta pelo schema.
|
|
235
234
|
|
|
236
235
|
O plugin `opusDesign()`, de `@softize/opus/vite`, monta o runtime do Opus dentro do dev server e
|
|
237
236
|
responde `/api` no próprio processo. No modo design, qualquer `server.proxy` para um backend externo
|
|
@@ -205,15 +205,15 @@ Quando a região principal da página é uma coleção, `Content` nomeia e gover
|
|
|
205
205
|
toolbar da lista. Essa divisão aproxima cada comando do objeto que ele afeta sem criar uma família
|
|
206
206
|
paralela de componentes `List*`.
|
|
207
207
|
|
|
208
|
-
Quando a coleção é o assunto da página, use `
|
|
209
|
-
escala do cabeçalho de [`Page`](/ui/page) e
|
|
210
|
-
extremo oposto ao título
|
|
208
|
+
Quando a coleção é o assunto da página, use `level={1}`: o `Content` passa a ser o heading
|
|
209
|
+
principal, assume automaticamente a escala do cabeçalho de [`Page`](/ui/page) e mantém o mesmo
|
|
210
|
+
ritmo entre introdução e corpo. As ações ficam no extremo oposto ao título. Níveis de 2 a 6
|
|
211
|
+
continuam usando a apresentação compacta de uma seção.
|
|
211
212
|
|
|
212
213
|
```tsx preview col
|
|
213
214
|
render(
|
|
214
215
|
<Content
|
|
215
216
|
level={1}
|
|
216
|
-
variant="page"
|
|
217
217
|
title="Clientes"
|
|
218
218
|
actions={<Button>Criar cliente</Button>}
|
|
219
219
|
>
|
|
@@ -242,8 +242,7 @@ classes adicionais; um `Content` irmão, sem lista, mantém a altura do próprio
|
|
|
242
242
|
| `count` | `number` | | Forma curta: total de itens ao lado do título da região. |
|
|
243
243
|
| `description` | `ReactNode` | | Forma curta: frase de apoio sob o título. |
|
|
244
244
|
| `actions` | `ReactNode` | | Forma curta: ações sobre a região inteira, no extremo oposto ao título. |
|
|
245
|
-
| `level` | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | `2` | Nível
|
|
246
|
-
| `variant` | `'page' \| 'section'` | `'section'` | Hierarquia visual: `page` reproduz o cabeçalho de `Page`; `section` é a região dentro de uma superfície. |
|
|
245
|
+
| `level` | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | `2` | Nível do heading; `1` identifica o conteúdo principal da página e os demais, uma seção interna. |
|
|
247
246
|
| `as` | `'section' \| 'fieldset'` | `'section'` | Usa uma região com heading ou um grupo de campos cujo título é a legenda nativa. |
|
|
248
247
|
| `framed` | `boolean` | `false` | Envolve o header e o body com a moldura estrutural da região. |
|
|
249
248
|
| `layout` | `'continuous' \| 'sectioned'` | `'continuous'` | Mantém header e body contínuos ou os separa com divisor e body sem recuo. `sectioned` exige `framed`. |
|
|
@@ -54,7 +54,6 @@ O par `context` + `variant` vale para componentes que comunicam significado, com
|
|
|
54
54
|
| --- | --- | --- |
|
|
55
55
|
| [`Select`](/ui/select) | `default`, `outline`, `ghost` | `default` é o campo completo; `outline` é compacto com borda, e `ghost`, compacto sem borda nem fundo. |
|
|
56
56
|
| [`Toggle`](/ui/toggle) | `default`, `outline` | `default` não tem borda nem fundo em repouso; o fundo aparece no hover e quando ativo. `outline` acrescenta a borda. |
|
|
57
|
-
| [`TabsList`](/ui/tabs) | `default`, `line` | `default` agrupa as abas em uma superfície preenchida; `line` usa uma linha-base e marca nela a aba ativa. |
|
|
58
57
|
|
|
59
58
|
O mesmo nome pode aparecer nos dois grupos. Num `Badge`, `outline` é um tratamento do significado
|
|
60
59
|
declarado em `context`; num `Select`, é apenas o formato compacto com borda.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
## Alternar painéis relacionados
|
|
2
2
|
|
|
3
|
-
Use `Tabs` para alternar painéis relacionados no mesmo contexto. O `value` de cada `
|
|
4
|
-
corresponde ao `
|
|
3
|
+
Use `Tabs` para alternar painéis relacionados no mesmo contexto. O `value` de cada `TabsListItem`
|
|
4
|
+
corresponde ao `TabsPanel` que ele abre. `defaultValue` define a aba inicial no modo não
|
|
5
5
|
controlado.
|
|
6
6
|
|
|
7
7
|
`icon` posiciona um ícone decorativo antes do rótulo e normaliza SVGs para `1rem`. Quando a aba
|
|
@@ -10,31 +10,31 @@ mostrar somente o ícone, declare `aria-label` para preservar seu nome acessíve
|
|
|
10
10
|
```tsx preview col
|
|
11
11
|
<Tabs defaultValue="sessions">
|
|
12
12
|
<TabsList>
|
|
13
|
-
<
|
|
13
|
+
<TabsListItem value="overview" icon={<LayoutDashboard />}>
|
|
14
14
|
Visão geral
|
|
15
|
-
</
|
|
16
|
-
<
|
|
15
|
+
</TabsListItem>
|
|
16
|
+
<TabsListItem value="sessions" icon={<History />}>
|
|
17
17
|
Sessões
|
|
18
|
-
</
|
|
19
|
-
<
|
|
18
|
+
</TabsListItem>
|
|
19
|
+
<TabsListItem value="skills" icon={<Settings />}>
|
|
20
20
|
Skills
|
|
21
|
-
</
|
|
21
|
+
</TabsListItem>
|
|
22
22
|
</TabsList>
|
|
23
|
-
<
|
|
23
|
+
<TabsPanel value="overview">
|
|
24
24
|
<p className="text-sm text-muted-foreground">
|
|
25
25
|
Resumo do workspace Empresa X.
|
|
26
26
|
</p>
|
|
27
|
-
</
|
|
28
|
-
<
|
|
27
|
+
</TabsPanel>
|
|
28
|
+
<TabsPanel value="sessions">
|
|
29
29
|
<p className="text-sm text-muted-foreground">
|
|
30
30
|
3 sessões nesta semana, 1 em andamento.
|
|
31
31
|
</p>
|
|
32
|
-
</
|
|
33
|
-
<
|
|
32
|
+
</TabsPanel>
|
|
33
|
+
<TabsPanel value="skills">
|
|
34
34
|
<p className="text-sm text-muted-foreground">
|
|
35
35
|
6 skills vinculadas aos agentes do workspace.
|
|
36
36
|
</p>
|
|
37
|
-
</
|
|
37
|
+
</TabsPanel>
|
|
38
38
|
</Tabs>
|
|
39
39
|
```
|
|
40
40
|
|
|
@@ -47,20 +47,20 @@ moldura.
|
|
|
47
47
|
|
|
48
48
|
```tsx preview col
|
|
49
49
|
<Tabs defaultValue="overview" framed className="gap-0">
|
|
50
|
-
<TabsList
|
|
51
|
-
<
|
|
52
|
-
<
|
|
50
|
+
<TabsList className="px-2 pt-2">
|
|
51
|
+
<TabsListItem value="overview">Visão geral</TabsListItem>
|
|
52
|
+
<TabsListItem value="history">Histórico</TabsListItem>
|
|
53
53
|
</TabsList>
|
|
54
|
-
<
|
|
54
|
+
<TabsPanel value="overview" className="p-4">
|
|
55
55
|
<p className="text-sm text-muted-foreground">
|
|
56
56
|
Resumo do workspace Empresa X.
|
|
57
57
|
</p>
|
|
58
|
-
</
|
|
59
|
-
<
|
|
58
|
+
</TabsPanel>
|
|
59
|
+
<TabsPanel value="history" className="p-4">
|
|
60
60
|
<p className="text-sm text-muted-foreground">
|
|
61
61
|
Alterações recentes do workspace.
|
|
62
62
|
</p>
|
|
63
|
-
</
|
|
63
|
+
</TabsPanel>
|
|
64
64
|
</Tabs>
|
|
65
65
|
```
|
|
66
66
|
|
|
@@ -73,24 +73,23 @@ altera os painéis associados.
|
|
|
73
73
|
|
|
74
74
|
```tsx preview col
|
|
75
75
|
<Tabs defaultValue="overview" distribution="equal">
|
|
76
|
-
<TabsList
|
|
77
|
-
<
|
|
78
|
-
<
|
|
79
|
-
<
|
|
76
|
+
<TabsList>
|
|
77
|
+
<TabsListItem value="overview">Visão geral</TabsListItem>
|
|
78
|
+
<TabsListItem value="contacts">Contatos</TabsListItem>
|
|
79
|
+
<TabsListItem value="history">Histórico</TabsListItem>
|
|
80
80
|
</TabsList>
|
|
81
|
-
<
|
|
82
|
-
<
|
|
83
|
-
<
|
|
81
|
+
<TabsPanel value="overview">Resumo do atendimento.</TabsPanel>
|
|
82
|
+
<TabsPanel value="contacts">Tentativas de contato.</TabsPanel>
|
|
83
|
+
<TabsPanel value="history">Alterações registradas.</TabsPanel>
|
|
84
84
|
</Tabs>
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
-
##
|
|
87
|
+
## Linha de navegação
|
|
88
88
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
sem alcançar as laterais da superfície.
|
|
89
|
+
`TabsList` usa uma linha-base porque representa navegação entre painéis, não uma escolha de valor.
|
|
90
|
+
Na orientação horizontal, a lista ocupa toda a largura disponível e a aba ativa é marcada sobre
|
|
91
|
+
essa linha. A linha respeita o recuo do contêiner; dentro de `DrawerBody` e `DialogBody`, ela
|
|
92
|
+
acompanha o alinhamento do restante do corpo sem alcançar as laterais da superfície.
|
|
94
93
|
Cada item contém uma área interativa com a mesma altura de um Button do tamanho correspondente. A
|
|
95
94
|
área permanece transparente e recebe fundo apenas no hover; a aba ativa é indicada pela linha
|
|
96
95
|
primária inferior. Um respiro de `0.375rem` separa o controle dessa linha; em `Tabs framed`, ele
|
|
@@ -98,69 +97,68 @@ aumenta para `0.5rem` e acompanha o padding superior da moldura.
|
|
|
98
97
|
|
|
99
98
|
```tsx preview col
|
|
100
99
|
<Tabs defaultValue="agents">
|
|
101
|
-
<TabsList
|
|
102
|
-
<
|
|
103
|
-
<
|
|
104
|
-
<
|
|
100
|
+
<TabsList>
|
|
101
|
+
<TabsListItem value="agents">Agentes</TabsListItem>
|
|
102
|
+
<TabsListItem value="repos">Repositórios</TabsListItem>
|
|
103
|
+
<TabsListItem value="settings">Configurações</TabsListItem>
|
|
105
104
|
</TabsList>
|
|
106
|
-
<
|
|
105
|
+
<TabsPanel value="agents">
|
|
107
106
|
<p className="text-sm text-muted-foreground">
|
|
108
107
|
developer, reviewer e designer ativos.
|
|
109
108
|
</p>
|
|
110
|
-
</
|
|
111
|
-
<
|
|
109
|
+
</TabsPanel>
|
|
110
|
+
<TabsPanel value="repos">
|
|
112
111
|
<p className="text-sm text-muted-foreground">
|
|
113
112
|
empresa-x-api e empresa-x-web vinculados.
|
|
114
113
|
</p>
|
|
115
|
-
</
|
|
116
|
-
<
|
|
114
|
+
</TabsPanel>
|
|
115
|
+
<TabsPanel value="settings">
|
|
117
116
|
<p className="text-sm text-muted-foreground">Preferências do workspace.</p>
|
|
118
|
-
</
|
|
117
|
+
</TabsPanel>
|
|
119
118
|
</Tabs>
|
|
120
119
|
```
|
|
121
120
|
|
|
122
121
|
## Vertical
|
|
123
122
|
|
|
124
|
-
Com `orientation="vertical"`, a lista forma uma coluna
|
|
125
|
-
|
|
123
|
+
Com `orientation="vertical"`, a lista forma uma coluna e a marca da aba ativa passa para a lateral
|
|
124
|
+
do gatilho.
|
|
126
125
|
|
|
127
126
|
```tsx preview col
|
|
128
127
|
<Tabs defaultValue="prompt" orientation="vertical">
|
|
129
128
|
<TabsList>
|
|
130
|
-
<
|
|
131
|
-
<
|
|
132
|
-
<
|
|
129
|
+
<TabsListItem value="prompt">Prompt</TabsListItem>
|
|
130
|
+
<TabsListItem value="skills">Skills</TabsListItem>
|
|
131
|
+
<TabsListItem value="history">Histórico</TabsListItem>
|
|
133
132
|
</TabsList>
|
|
134
|
-
<
|
|
133
|
+
<TabsPanel value="prompt">
|
|
135
134
|
<p className="text-sm text-muted-foreground">
|
|
136
135
|
O prompt do agente developer.
|
|
137
136
|
</p>
|
|
138
|
-
</
|
|
139
|
-
<
|
|
137
|
+
</TabsPanel>
|
|
138
|
+
<TabsPanel value="skills">
|
|
140
139
|
<p className="text-sm text-muted-foreground">
|
|
141
140
|
Skills vinculadas a este agente.
|
|
142
141
|
</p>
|
|
143
|
-
</
|
|
144
|
-
<
|
|
142
|
+
</TabsPanel>
|
|
143
|
+
<TabsPanel value="history">
|
|
145
144
|
<p className="text-sm text-muted-foreground">
|
|
146
145
|
Sessões anteriores deste agente.
|
|
147
146
|
</p>
|
|
148
|
-
</
|
|
147
|
+
</TabsPanel>
|
|
149
148
|
</Tabs>
|
|
150
149
|
```
|
|
151
150
|
|
|
152
151
|
## Tamanho
|
|
153
152
|
|
|
154
|
-
`size="sm"` no `Tabs` reduz a
|
|
155
|
-
`Select` para uma fileira densa, como uma toolbar ou um cabeçalho
|
|
156
|
-
mais alto que os elementos vizinhos.
|
|
153
|
+
`size="sm"` no `Tabs` reduz a área interativa de `2.25rem` para `2rem`. É o par do `sm` de `Button` e
|
|
154
|
+
`Select` para uma fileira densa, como uma toolbar ou um cabeçalho.
|
|
157
155
|
|
|
158
156
|
```tsx preview col
|
|
159
157
|
<Tabs defaultValue="preview" size="sm">
|
|
160
158
|
<TabsList>
|
|
161
|
-
<
|
|
162
|
-
<
|
|
163
|
-
<
|
|
159
|
+
<TabsListItem value="preview">Preview</TabsListItem>
|
|
160
|
+
<TabsListItem value="code">Código</TabsListItem>
|
|
161
|
+
<TabsListItem value="ai">IA</TabsListItem>
|
|
164
162
|
</TabsList>
|
|
165
163
|
</Tabs>
|
|
166
164
|
```
|
|
@@ -172,28 +170,29 @@ mais alto que os elementos vizinhos.
|
|
|
172
170
|
| `defaultValue` | `string` | | Aba inicial no modo não controlado. |
|
|
173
171
|
| `value` | `string` | | Aba ativa no modo controlado. Use com `onValueChange`. |
|
|
174
172
|
| `onValueChange` | `(value: string) => void` | | Chamado quando a pessoa seleciona outra aba. |
|
|
175
|
-
| `size` | `'default' \| 'sm'` | `'default'` | Altura da
|
|
173
|
+
| `size` | `'default' \| 'sm'` | `'default'` | Altura da área interativa na escala única dos controles (2.25 e 2rem), aplicada aos gatilhos. |
|
|
176
174
|
| `distribution` | `'content' \| 'equal'` | `'content'` | Preserva a largura dos rótulos ou divide igualmente a largura disponível entre os gatilhos. |
|
|
177
175
|
| `framed` | `boolean` | `false` | Aplica borda, raio e recorte ao conjunto sem definir fundo ou foreground. |
|
|
178
176
|
| `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | Direção da lista de abas. |
|
|
179
177
|
|
|
180
178
|
## Propriedades de TabsList
|
|
181
179
|
|
|
182
|
-
| Propriedade
|
|
183
|
-
|
|
|
184
|
-
| `
|
|
180
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
181
|
+
| ------------ | -------- | ------ | --------------------------------------------------------------- |
|
|
182
|
+
| `aria-label` | `string` | | Nomeia a navegação quando não houver um rótulo visível associado. |
|
|
183
|
+
| `className` | `string` | | Ajusta a composição externa sem substituir a linha de navegação. |
|
|
185
184
|
|
|
186
|
-
## Propriedades de
|
|
185
|
+
## Propriedades de TabsListItem
|
|
187
186
|
|
|
188
187
|
| Propriedade | Tipo | Padrão | Descrição |
|
|
189
188
|
| ----------- | ----------- | ------- | ----------------------------------------------------------------------------------------------- |
|
|
190
|
-
| `value` | `string` | | Identificador que associa o gatilho ao `
|
|
189
|
+
| `value` | `string` | | Identificador que associa o gatilho ao `TabsPanel` correspondente. |
|
|
191
190
|
| `icon` | `ReactNode` | | Ícone decorativo antes do rótulo, em `1rem`. Em uma aba sem texto, declare também `aria-label`. |
|
|
192
191
|
| `disabled` | `boolean` | `false` | Impede a seleção da aba. |
|
|
193
192
|
|
|
194
|
-
## Propriedades de
|
|
193
|
+
## Propriedades de TabsPanel
|
|
195
194
|
|
|
196
195
|
| Propriedade | Tipo | Padrão | Descrição |
|
|
197
196
|
| ------------ | -------- | ------ | ---------------------------------------------------------------------- |
|
|
198
|
-
| `value` | `string` | | Identificador do painel, igual ao `value` do `
|
|
197
|
+
| `value` | `string` | | Identificador do painel, igual ao `value` do `TabsListItem` que o abre. |
|
|
199
198
|
| `forceMount` | `true` | | Mantém o painel montado mesmo quando outra aba está ativa. |
|
|
@@ -90,9 +90,8 @@ expect(emitted).toEqual([{ event: 'task.done', data: { id: '1' } }])
|
|
|
90
90
|
|
|
91
91
|
Os dados que o modo design mostra não são comportamento da action e não precisam de teste de
|
|
92
92
|
unidade: o plugin `opusDesign()` os gera a partir do contrato (ver
|
|
93
|
-
[Actions](/docs/actions)).
|
|
94
|
-
|
|
95
|
-
handler em vez dele.
|
|
93
|
+
[Actions](/docs/actions)). `runAction` executa somente a action ligada ao handler; use cenários do
|
|
94
|
+
plugin para respostas específicas do modo design.
|
|
96
95
|
|
|
97
96
|
## memStorage
|
|
98
97
|
|
|
@@ -30,8 +30,8 @@ render(
|
|
|
30
30
|
|
|
31
31
|
## Variantes e tamanhos
|
|
32
32
|
|
|
33
|
-
Na variante `default`, o fundo aparece somente quando o controle está ativo
|
|
34
|
-
borda. `size` ajusta a altura.
|
|
33
|
+
Na variante `default`, o fundo aparece somente quando o controle está ativo. `outline` mantém a
|
|
34
|
+
borda e usa um fundo sutil distinto dela no estado ativo. `size` ajusta a altura.
|
|
35
35
|
|
|
36
36
|
```tsx preview
|
|
37
37
|
<Toggle aria-label="Quebra de linha">
|
|
@@ -140,19 +140,24 @@ render(
|
|
|
140
140
|
)
|
|
141
141
|
```
|
|
142
142
|
|
|
143
|
-
###
|
|
143
|
+
### Grupo conectado com borda
|
|
144
144
|
|
|
145
145
|
`variant`, `size` e `shape` definidos no grupo chegam aos itens por contexto. `spacing` separa os
|
|
146
|
-
itens
|
|
146
|
+
itens. Use `variant="outline"` com `spacing={0}` para formar um controle conectado com contorno
|
|
147
|
+
único. A borda pertence aos itens: o grupo remove as bordas internas duplicadas, arredonda apenas
|
|
148
|
+
as extremidades e preserva o contorno quando um item está ativo.
|
|
147
149
|
|
|
148
150
|
```tsx preview
|
|
149
|
-
<ToggleGroup type="single" variant="outline" spacing={
|
|
150
|
-
<ToggleGroupItem value="
|
|
151
|
-
<ToggleGroupItem value="
|
|
152
|
-
<ToggleGroupItem value="
|
|
151
|
+
<ToggleGroup type="single" variant="outline" spacing={0} defaultValue="call">
|
|
152
|
+
<ToggleGroupItem value="call"><Phone /> Telefone</ToggleGroupItem>
|
|
153
|
+
<ToggleGroupItem value="email"><Mail /> E-mail</ToggleGroupItem>
|
|
154
|
+
<ToggleGroupItem value="whatsapp"><MessageCircle /> WhatsApp</ToggleGroupItem>
|
|
153
155
|
</ToggleGroup>
|
|
154
156
|
```
|
|
155
157
|
|
|
158
|
+
Use um valor maior que zero quando cada item precisar preservar sua própria borda e seu próprio
|
|
159
|
+
raio, sem formar um bloco contínuo.
|
|
160
|
+
|
|
156
161
|
### Propriedades de ToggleGroup
|
|
157
162
|
|
|
158
163
|
| Propriedade | Tipo | Padrão | Descrição |
|
|
@@ -161,10 +166,10 @@ itens; com zero, eles formam um bloco contínuo.
|
|
|
161
166
|
| `value` | `string \| string[]` | | Seleção controlada; o tipo acompanha `type`. |
|
|
162
167
|
| `onValueChange` | `(value: string \| string[]) => void` | | Informa a nova seleção. No modo single, uma string vazia representa nenhum item ativo. |
|
|
163
168
|
| `defaultValue` | `string \| string[]` | | Seleção inicial no modo não controlado. |
|
|
164
|
-
| `variant` | `'default' \| 'outline'` | `'default'` | Tratamento visual repassado aos itens. |
|
|
169
|
+
| `variant` | `'default' \| 'outline'` | `'default'` | Tratamento visual repassado aos itens. `outline` aplica borda a cada item. |
|
|
165
170
|
| `size` | `'default' \| 'sm' \| 'lg'` | `'default'` | Tamanho repassado aos itens. |
|
|
166
171
|
| `shape` | `'default' \| 'pill'` | `'default'` | Geometria do grupo e de suas extremidades. |
|
|
167
|
-
| `spacing` | `number` | `0` | Distância entre os itens em unidades de spacing. |
|
|
172
|
+
| `spacing` | `number` | `0` | Distância entre os itens em unidades de spacing. Com zero, remove bordas internas duplicadas e conecta o grupo. |
|
|
168
173
|
|
|
169
174
|
### Propriedades de ToggleGroupItem
|
|
170
175
|
|
package/src/ui/drivers/react.tsx
CHANGED
|
@@ -51,11 +51,12 @@ import { objectSchemaShape } from '../lib/object-schema.ts'
|
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
53
|
* Dicionário estrutural pro resolvedor de `options: { kind: 'dictionary', ref }`:
|
|
54
|
-
* qualquer coisa com `options()` → `{ value, label }`. O `DictType` do `t.dict`
|
|
55
|
-
* satisfaz direto — sem este driver importar o driver de schema.
|
|
54
|
+
* qualquer coisa com `options()` → `{ value, label, icon? }`. O `DictType` do `t.dict`
|
|
55
|
+
* satisfaz direto — sem este driver importar o driver de schema. A metadata opcional
|
|
56
|
+
* continua sendo declarada pelo dicionário; o provider não inventa apresentação.
|
|
56
57
|
*/
|
|
57
58
|
export interface DictLike {
|
|
58
|
-
options(): Array<{ value: string; label: string }>
|
|
59
|
+
options(): Array<{ value: string; label: string; icon?: string }>
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
interface OpusContextValue {
|
package/src/ui/meta.ts
CHANGED
|
@@ -201,7 +201,7 @@ export const componentMeta = {
|
|
|
201
201
|
name: "tabs",
|
|
202
202
|
ancestry: "shadcn",
|
|
203
203
|
whenToUse:
|
|
204
|
-
'
|
|
204
|
+
'Navegue entre painéis relacionados no mesmo nível. Cada TabsListItem aponta para um TabsPanel pelo mesmo `value`; TabsList usa uma linha para indicar a aba ativa, enquanto Tabs define orientação, escala, distribuição e moldura. Use ToggleGroup para escolher valores sem navegar entre painéis.',
|
|
205
205
|
},
|
|
206
206
|
textarea: {
|
|
207
207
|
name: "textarea",
|
|
@@ -363,7 +363,7 @@ export const componentMeta = {
|
|
|
363
363
|
name: "toggle",
|
|
364
364
|
ancestry: "shadcn",
|
|
365
365
|
whenToUse:
|
|
366
|
-
|
|
366
|
+
'Alterne um estado diretamente no contexto atual. ToggleGroup reúne escolhas únicas ou múltiplas e compartilha aparência e escala entre os itens. Use `variant="outline"` com `spacing={0}` para formar um grupo conectado com borda. Para navegar entre painéis, use Tabs.',
|
|
367
367
|
},
|
|
368
368
|
|
|
369
369
|
confirm: {
|
package/src/ui/react.tsx
CHANGED
|
@@ -192,8 +192,8 @@ export { Separator } from "./components/primitives/separator.tsx";
|
|
|
192
192
|
export {
|
|
193
193
|
Tabs,
|
|
194
194
|
TabsList,
|
|
195
|
-
|
|
196
|
-
|
|
195
|
+
TabsListItem,
|
|
196
|
+
TabsPanel,
|
|
197
197
|
} from "./components/primitives/tabs.tsx";
|
|
198
198
|
|
|
199
199
|
export { Skeleton } from "./components/primitives/skeleton.tsx";
|
|
@@ -499,7 +499,6 @@ export type {
|
|
|
499
499
|
ContentElement,
|
|
500
500
|
ContentFrameVariant,
|
|
501
501
|
ContentHeaderLevel,
|
|
502
|
-
ContentHeaderVariant,
|
|
503
502
|
} from "./components/patterns/content-header.tsx";
|
|
504
503
|
|
|
505
504
|
// Layout composicional: Split decide a relação espacial; Pane carrega conteúdo com inset.
|
package/src/vite/design.ts
CHANGED
|
@@ -88,8 +88,8 @@ export interface OpusDesignOptions {
|
|
|
88
88
|
|
|
89
89
|
/**
|
|
90
90
|
* Cenários de design (ADR 0037): módulos que exportam `defineDesignScenario(contrato, resposta)`.
|
|
91
|
-
* Aceita caminhos e globs, como `entry`. O cenário vence
|
|
92
|
-
*
|
|
91
|
+
* Aceita caminhos e globs, como `entry`. O cenário vence a resposta padrão gerada pelo `fake`;
|
|
92
|
+
* cenário de action inexistente impede o boot, com a lista no erro.
|
|
93
93
|
* Sem default: o projeto decide onde os cenários ficam e se são versionados.
|
|
94
94
|
*/
|
|
95
95
|
scenarios?: string | string[]
|
|
@@ -116,7 +116,7 @@ export interface OpusDesignOptions {
|
|
|
116
116
|
stubs?: Record<string, DesignStubHandler>
|
|
117
117
|
|
|
118
118
|
/**
|
|
119
|
-
* Monta o /api também no dev NORMAL
|
|
119
|
+
* Monta o /api também no dev NORMAL, com os handlers reais.
|
|
120
120
|
* Default false — devBackend puro não é design (sem sonda, proxy intacto).
|
|
121
121
|
*/
|
|
122
122
|
devBackend?: boolean
|
|
@@ -156,7 +156,7 @@ const PROBE_PATH = '/__opus/design'
|
|
|
156
156
|
const RUNTIME_ENDPOINT_PATHS = new Set(['/health', '/ready', '/openapi.json'])
|
|
157
157
|
|
|
158
158
|
/** De onde vem a resposta de cada action no modo design, na ordem de precedência. */
|
|
159
|
-
type DesignSource = 'scenario' | '
|
|
159
|
+
type DesignSource = 'scenario' | 'handler' | 'generated'
|
|
160
160
|
|
|
161
161
|
interface DesignState {
|
|
162
162
|
handle: NodeServerHandle
|
|
@@ -214,9 +214,8 @@ export function opusDesign(options: OpusDesignOptions = {}): Plugin {
|
|
|
214
214
|
exProxyPrefixes = Object.keys(config.server?.proxy ?? {})
|
|
215
215
|
// 2) Mata o proxy: fecha o vazamento preview→prod pra QUALQUER prefixo.
|
|
216
216
|
if (config.server?.proxy !== undefined) config.server.proxy = undefined
|
|
217
|
-
// 3) O
|
|
218
|
-
//
|
|
219
|
-
process.env.OPUS_MODE = 'design'
|
|
217
|
+
// 3) O boot liga em cada contrato um handler de cenário ou resposta gerada,
|
|
218
|
+
// e o preview fica autossuficiente sem alterar o runtime de produção.
|
|
220
219
|
},
|
|
221
220
|
|
|
222
221
|
configureServer(server) {
|
|
@@ -362,15 +361,14 @@ export function opusDesign(options: OpusDesignOptions = {}): Plugin {
|
|
|
362
361
|
const named = (source: DesignSource): string[] =>
|
|
363
362
|
state.actions.filter((a) => state.sources.get(a.name) === source).map((a) => a.name)
|
|
364
363
|
const scenarios = named('scenario')
|
|
365
|
-
const mocks = named('mock')
|
|
366
364
|
payload.runtime = {
|
|
367
365
|
prefix: apiPrefix,
|
|
368
366
|
actions: state.actions.length,
|
|
369
|
-
// `mocked`/`unmocked`
|
|
370
|
-
//
|
|
371
|
-
mocked: scenarios.length
|
|
367
|
+
// `mocked`/`unmocked` preservam o formato histórico da sonda: cenário escrito
|
|
368
|
+
// à mão contra respostas vindas de handler real ou geradas pelo `fake`.
|
|
369
|
+
mocked: scenarios.length,
|
|
372
370
|
unmocked: state.actions
|
|
373
|
-
.filter((a) =>
|
|
371
|
+
.filter((a) => state.sources.get(a.name) !== 'scenario')
|
|
374
372
|
.map((a) => a.name),
|
|
375
373
|
scenarios,
|
|
376
374
|
generated: named('generated'),
|
|
@@ -687,8 +685,8 @@ function scenariosOfModule(mod: Record<string, unknown>): DesignScenario[] {
|
|
|
687
685
|
}
|
|
688
686
|
|
|
689
687
|
/**
|
|
690
|
-
* Liga a cada action a resposta de design, nesta precedência: cenário,
|
|
691
|
-
*
|
|
688
|
+
* Liga a cada action a resposta de design, nesta precedência: cenário, handler real
|
|
689
|
+
* (`bindAction`) e, por fim, a resposta padrão gerada pelo `fake`.
|
|
692
690
|
*/
|
|
693
691
|
function prepareDesignActions(
|
|
694
692
|
actions: ActionDef[],
|
|
@@ -699,18 +697,23 @@ function prepareDesignActions(
|
|
|
699
697
|
const scenario = scenarios.get(action.name)
|
|
700
698
|
if (scenario !== undefined) {
|
|
701
699
|
sources.set(action.name, 'scenario')
|
|
702
|
-
return {
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
700
|
+
return {
|
|
701
|
+
...action,
|
|
702
|
+
// Resposta simulada não representa exposição real de dado pessoal (ADR 0041).
|
|
703
|
+
personalData: false,
|
|
704
|
+
handler: (ctx: ActionContext, input: unknown) => scenario.respond(input, ctx),
|
|
705
|
+
} as ActionDef
|
|
707
706
|
}
|
|
708
707
|
if (typeof (action as { handler?: unknown }).handler === 'function') {
|
|
709
708
|
sources.set(action.name, 'handler')
|
|
710
709
|
return action
|
|
711
710
|
}
|
|
712
711
|
sources.set(action.name, 'generated')
|
|
713
|
-
return {
|
|
712
|
+
return {
|
|
713
|
+
...action,
|
|
714
|
+
personalData: false,
|
|
715
|
+
handler: async (_ctx: ActionContext, input: unknown) => designResponse(action, input),
|
|
716
|
+
} as ActionDef
|
|
714
717
|
})
|
|
715
718
|
return { actions: prepared, sources }
|
|
716
719
|
}
|
|
@@ -759,4 +762,3 @@ function sendEnvelope(res: ServerResponse, status: number, input: ErrorInput): v
|
|
|
759
762
|
),
|
|
760
763
|
)
|
|
761
764
|
}
|
|
762
|
-
|