@softize/opus 29.0.0 → 31.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 +137 -0
- package/bin/lib/check.mjs +178 -94
- package/bin/lib/copy.mjs +2970 -1801
- package/docs/adr/0043-neutral-surfaces-and-interactions-use-opaque-role-tokens.md +52 -0
- package/docs/adr/0044-neutral-appearance-derives-from-bounded-axes-per-mode.md +27 -2
- package/docs/adr/0045-surface-replaces-content-and-card.md +3 -0
- package/docs/adr/0060-surface-composition-separates-chrome-and-intro.md +142 -0
- package/docs/adr/0062-listas-contidas-e-texto-flexivel.md +68 -0
- package/docs/adr/0063-metadados-das-superficies.md +44 -0
- package/docs/adr/0064-action-list-com-itens-e-fontes-externas.md +47 -0
- package/docs/adr/0065-presentation-composes-settings-sections.md +295 -0
- package/docs/adr/README.md +8 -0
- package/package.json +1 -1
- package/registry/skills/build-opus-ui/SKILL.md +21 -19
- package/registry/skills/build-opus-ui/references/evaluations.md +21 -10
- package/registry/skills/build-opus-ui/references/ui-patterns.md +33 -28
- package/src/core/presentation.ts +938 -32
- package/src/core/types.ts +2 -1
- package/src/ui/components/patterns/form.tsx +2 -0
- package/src/ui/components/patterns/grouped-list.ts +2 -2
- package/src/ui/components/patterns/list.tsx +588 -365
- package/src/ui/components/patterns/page-heading-context.tsx +12 -5
- package/src/ui/components/patterns/page.tsx +238 -181
- package/src/ui/components/patterns/presentation-fields.tsx +246 -0
- package/src/ui/components/patterns/presentation-sections.tsx +494 -0
- package/src/ui/components/patterns/presentation.tsx +1142 -229
- package/src/ui/components/patterns/structural-header.tsx +47 -34
- package/src/ui/components/patterns/surface-anatomy.ts +27 -217
- package/src/ui/components/patterns/surface.tsx +55 -37
- package/src/ui/components/patterns/trigger.tsx +10 -2
- package/src/ui/components/primitives/close-button.tsx +11 -3
- package/src/ui/components/primitives/command.tsx +2 -8
- package/src/ui/components/primitives/detail.tsx +1 -1
- package/src/ui/components/primitives/dialog.tsx +59 -89
- package/src/ui/components/primitives/drawer.tsx +52 -87
- package/src/ui/components/primitives/field.tsx +1 -1
- package/src/ui/components/primitives/pagination.tsx +3 -2
- package/src/ui/components/primitives/table.tsx +3 -3
- package/src/ui/components/primitives/tabs.tsx +13 -2
- package/src/ui/docs/content/action-form.md +2 -1
- package/src/ui/docs/content/action-list.md +146 -73
- package/src/ui/docs/content/action-trigger.md +5 -0
- package/src/ui/docs/content/button.md +5 -6
- package/src/ui/docs/content/command.md +62 -50
- package/src/ui/docs/content/compositions.md +129 -459
- package/src/ui/docs/content/dialog.md +72 -101
- package/src/ui/docs/content/drawer.md +69 -92
- package/src/ui/docs/content/field.md +2 -1
- package/src/ui/docs/content/page.md +207 -386
- package/src/ui/docs/content/presentation.md +371 -23
- package/src/ui/docs/content/separator.md +1 -1
- package/src/ui/docs/content/surface.md +94 -116
- package/src/ui/docs/content/table.md +7 -0
- package/src/ui/docs/content/tokens.md +18 -4
- package/src/ui/drivers/context.tsx +11 -4
- package/src/ui/drivers/react.tsx +27 -7
- package/src/ui/meta.ts +5 -5
- package/src/ui/react.tsx +38 -8
- package/src/ui/theme.css +44 -33
|
@@ -16,7 +16,11 @@ render(
|
|
|
16
16
|
title="Dispositivos conectados"
|
|
17
17
|
count={3}
|
|
18
18
|
description="Sessões com acesso à sua conta."
|
|
19
|
-
actions={
|
|
19
|
+
actions={
|
|
20
|
+
<Button variant="outline" size="sm">
|
|
21
|
+
Encerrar outras sessões
|
|
22
|
+
</Button>
|
|
23
|
+
}
|
|
20
24
|
>
|
|
21
25
|
<p className="text-sm text-muted-foreground">MacBook Pro · ativo agora</p>
|
|
22
26
|
</Surface>,
|
|
@@ -43,17 +47,16 @@ Quando `aria-label` é informado, ele substitui a ligação com o título. Para
|
|
|
43
47
|
|
|
44
48
|
## Moldura, fundo e anatomia
|
|
45
49
|
|
|
46
|
-
|
|
50
|
+
Moldura e fundo têm funções independentes:
|
|
47
51
|
|
|
48
52
|
- `framed` acrescenta borda e raio.
|
|
49
53
|
- `elevation="raised"` dá à superfície o fundo de um elemento elevado sobre a página.
|
|
50
|
-
- `layout` escolhe entre o ritmo contínuo e as faixas com divisores.
|
|
51
54
|
|
|
52
55
|
Uma superfície com moldura ou com fundo passa a aplicar o recuo de `1rem` nas suas regiões; você não
|
|
53
56
|
escreve classes de padding no header, no corpo ou no rodapé. O header delimitado usa
|
|
54
|
-
`1rem` em todos os lados e linha mínima de `
|
|
55
|
-
Declare `
|
|
56
|
-
quando hospeda Surface. Seções sem moldura
|
|
57
|
+
`1rem` em todos os lados e linha mínima de `2,25rem`, como Page, Dialog e Drawer.
|
|
58
|
+
Declare `default` para botões textuais e `icon` para ícones; Pane usa a mesma geometria
|
|
59
|
+
quando hospeda Surface. Seções sem moldura permanecem alinhadas ao hospedeiro.
|
|
57
60
|
|
|
58
61
|
```tsx preview col
|
|
59
62
|
render(
|
|
@@ -130,12 +133,7 @@ ainda ter rodapé:
|
|
|
130
133
|
|
|
131
134
|
```tsx preview col
|
|
132
135
|
render(
|
|
133
|
-
<Surface
|
|
134
|
-
as="section"
|
|
135
|
-
framed
|
|
136
|
-
title="Plano atual"
|
|
137
|
-
level={3}
|
|
138
|
-
>
|
|
136
|
+
<Surface as="section" framed title="Plano atual" level={3}>
|
|
139
137
|
<SurfaceBody>
|
|
140
138
|
<p className="text-sm text-muted-foreground">
|
|
141
139
|
Renova em 12 de maio. Até 10 workspaces e 50 agentes.
|
|
@@ -163,56 +161,13 @@ A `Surface` reprova as composições que deixariam a estrutura ambígua:
|
|
|
163
161
|
|
|
164
162
|
O `opus check` aponta os mesmos problemas antes da execução.
|
|
165
163
|
|
|
166
|
-
##
|
|
164
|
+
## Divisores automáticos
|
|
167
165
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
<div className="grid grid-cols-2 gap-4">
|
|
174
|
-
<Surface as="section" framed elevation="raised" title="Contínuo" level={3}>
|
|
175
|
-
<SurfaceBody>
|
|
176
|
-
<p className="text-sm text-muted-foreground">
|
|
177
|
-
Um assunto, lido de cima a baixo.
|
|
178
|
-
</p>
|
|
179
|
-
</SurfaceBody>
|
|
180
|
-
<SurfaceFooter>
|
|
181
|
-
<Button size="sm" variant="outline">
|
|
182
|
-
Abrir
|
|
183
|
-
</Button>
|
|
184
|
-
</SurfaceFooter>
|
|
185
|
-
</Surface>
|
|
186
|
-
<Surface
|
|
187
|
-
as="section"
|
|
188
|
-
framed
|
|
189
|
-
elevation="raised"
|
|
190
|
-
layout="sectioned"
|
|
191
|
-
title="Seccionado"
|
|
192
|
-
level={3}
|
|
193
|
-
>
|
|
194
|
-
<SurfaceBody>
|
|
195
|
-
<p className="text-sm text-muted-foreground">
|
|
196
|
-
Regiões com pesos diferentes.
|
|
197
|
-
</p>
|
|
198
|
-
</SurfaceBody>
|
|
199
|
-
<SurfaceFooter>
|
|
200
|
-
<Button size="sm" variant="outline">
|
|
201
|
-
Abrir
|
|
202
|
-
</Button>
|
|
203
|
-
</SurfaceFooter>
|
|
204
|
-
</Surface>
|
|
205
|
-
</div>,
|
|
206
|
-
);
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
Use `continuous` para um bloco de leitura e `sectioned` para uma superfície de operação, com lista
|
|
210
|
-
ou tabela no corpo e ações persistentes no rodapé. `sectioned` exige `framed` ou
|
|
211
|
-
`elevation="raised"`: os divisores precisam de um limite para alcançar.
|
|
212
|
-
|
|
213
|
-
Os divisores são derivados das regiões presentes. Um header sozinho não desenha linha, e um header
|
|
214
|
-
seguido direto de um rodapé produz uma linha só. Por isso, não escreva `border-b` nem `border-t`
|
|
215
|
-
nas regiões, e use `layout="continuous"` para uma superfície sem divisores.
|
|
166
|
+
Em superfícies delimitadas, as regiões presentes determinam os divisores. Um header isolado não desenha linha; seguido
|
|
167
|
+
pelo rodapé, produz uma linha só. O recuo de uma superfície delimitada é `1rem` em cada região.
|
|
168
|
+
Sem moldura ou elevação, as regiões permanecem alinhadas ao hospedeiro e separadas por `1rem`,
|
|
169
|
+
sem padding nem divisor de região. Abas no Header conservam a linha de seleção própria.
|
|
170
|
+
Não há prop para alternar essa anatomia.
|
|
216
171
|
|
|
217
172
|
### Corpo de ponta a ponta
|
|
218
173
|
|
|
@@ -225,7 +180,6 @@ render(
|
|
|
225
180
|
<Surface
|
|
226
181
|
as="section"
|
|
227
182
|
framed
|
|
228
|
-
layout="sectioned"
|
|
229
183
|
title="Veículo"
|
|
230
184
|
media={
|
|
231
185
|
<IconTile size="xs">
|
|
@@ -328,9 +282,8 @@ render(
|
|
|
328
282
|
);
|
|
329
283
|
```
|
|
330
284
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
flui alinhado ao formulário.
|
|
285
|
+
Em uma superfície delimitada, a legenda nativa ocupa a faixa do header. Sem moldura ou
|
|
286
|
+
elevação, o grupo flui alinhado ao formulário.
|
|
334
287
|
|
|
335
288
|
## Superfície clicável
|
|
336
289
|
|
|
@@ -387,33 +340,16 @@ declaram, sem recuo nem espaçamento. O conteúdo e o recuo são seus.
|
|
|
387
340
|
documento: uma superfície dentro de uma seção que já tem `h2` usa `level={3}`. Os níveis de 2 a 6
|
|
388
341
|
têm a mesma aparência.
|
|
389
342
|
|
|
390
|
-
O nível 1
|
|
391
|
-
|
|
343
|
+
O nível 1 usa a escala do título de [`Page`](/ui/page), mas fica restrito a superfícies
|
|
344
|
+
fora de Page, como o acesso à aplicação. Dentro da Page, o título principal pertence ao
|
|
345
|
+
PageHeader e as Surfaces são seções subordinadas.
|
|
392
346
|
|
|
393
347
|
## Coleção nomeada
|
|
394
348
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
```tsx preview col
|
|
401
|
-
render(
|
|
402
|
-
<Surface
|
|
403
|
-
as="section"
|
|
404
|
-
level={1}
|
|
405
|
-
title="Clientes"
|
|
406
|
-
actions={<Button size="sm">Criar cliente</Button>}
|
|
407
|
-
>
|
|
408
|
-
<p className="text-sm text-muted-foreground">
|
|
409
|
-
A lista de clientes ocupa o corpo.
|
|
410
|
-
</p>
|
|
411
|
-
</Surface>,
|
|
412
|
-
);
|
|
413
|
-
```
|
|
414
|
-
|
|
415
|
-
A criação usa um `Button` textual na variante padrão e no tamanho `sm`, sem ícone, nomeado
|
|
416
|
-
`Criar {recurso}`; o diálogo aberto por ele repete esse título, e a edição usa `Editar {recurso}`.
|
|
349
|
+
Uma coleção principal usa [`Page`](/ui/page) com [`ActionList`](/ui/action-list).
|
|
350
|
+
Use Surface para nomear uma coleção subordinada. Criação, importação e exportação ficam em
|
|
351
|
+
`ActionList.toolbarActions`, junto de busca e filtros. Ações gerais da superfície permanecem
|
|
352
|
+
no header. O botão de criação mostra **Criar**, com nome acessível e título de formulário específicos.
|
|
417
353
|
|
|
418
354
|
Em uma [`Page`](/ui/page) com `layout="fill"`, a coleção ocupa a altura da página e rola só os
|
|
419
355
|
resultados. `Surface` e `SurfaceBody` repassam a altura até a `ActionList` sem classes adicionais;
|
|
@@ -432,32 +368,60 @@ faixa que cobre o conteúdo ao rolar — usa `bg-[var(--surface-background,var(-
|
|
|
432
368
|
página, vale `--background`; dentro de uma superfície elevada, o fundo dela. Um contêiner da
|
|
433
369
|
aplicação que pinte outro fundo declara a mesma variável com a cor que usa.
|
|
434
370
|
|
|
371
|
+
## Metadados junto à identidade
|
|
372
|
+
|
|
373
|
+
Use `SurfaceMeta` para badges, autoria e datas relacionados ao registro. A faixa fica
|
|
374
|
+
abaixo da linha de título e ações, com espaço próprio; `SurfaceDescription` continua
|
|
375
|
+
reservado à orientação textual breve. Os itens quebram em várias linhas quando necessário.
|
|
376
|
+
|
|
377
|
+
```tsx preview col
|
|
378
|
+
render(
|
|
379
|
+
<Surface
|
|
380
|
+
as="article"
|
|
381
|
+
title="Manual de atendimento"
|
|
382
|
+
meta={
|
|
383
|
+
<>
|
|
384
|
+
<Badge>Publicado</Badge>
|
|
385
|
+
<span>Por Ana</span>
|
|
386
|
+
</>
|
|
387
|
+
}
|
|
388
|
+
>
|
|
389
|
+
<p>Conteúdo do manual.</p>
|
|
390
|
+
</Surface>,
|
|
391
|
+
);
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
A composição aceita um único `SurfaceMeta` e exige um `SurfaceTitle`. Na forma explícita, coloque-o em `SurfaceHeader`. Em `fieldset`, coloque os metadados no corpo; a legenda permanece restrita a título e mídia decorativa.
|
|
395
|
+
|
|
396
|
+
O total da coleção continua ao lado do título: use `count` na forma curta ou `SurfaceCount` na explícita. Se um `SurfaceMeta` antigo representava esse total, migre para `SurfaceCount`.
|
|
397
|
+
|
|
435
398
|
## Propriedades de Surface
|
|
436
399
|
|
|
437
|
-
| Propriedade | Tipo | Padrão
|
|
438
|
-
| ------------- | ----------------------------------------------- |
|
|
439
|
-
| `as` | `'div' \| 'section' \| 'article' \| 'fieldset'` | `'div'`
|
|
440
|
-
| `asChild` | `boolean` | `false`
|
|
441
|
-
| `framed` | `boolean` | `false`
|
|
442
|
-
| `elevation` | `'none' \| 'raised'` | `'none'`
|
|
443
|
-
| `
|
|
444
|
-
| `
|
|
445
|
-
| `
|
|
446
|
-
| `
|
|
447
|
-
| `
|
|
448
|
-
| `
|
|
449
|
-
| `actions` | `ReactNode` |
|
|
450
|
-
| `className` | `string` |
|
|
451
|
-
| `children` | `ReactNode` |
|
|
400
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
401
|
+
| ------------- | ----------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------ |
|
|
402
|
+
| `as` | `'div' \| 'section' \| 'article' \| 'fieldset'` | `'div'` | Elemento da superfície. Não combina com `asChild`. |
|
|
403
|
+
| `asChild` | `boolean` | `false` | Entrega a superfície ao único filho, como um `button` ou um link. |
|
|
404
|
+
| `framed` | `boolean` | `false` | Acrescenta borda e raio, e recorta o conteúdo seccionado ou sem recuo. |
|
|
405
|
+
| `elevation` | `'none' \| 'raised'` | `'none'` | `raised` pinta o fundo de superfície elevada e mantém a altura do próprio conteúdo. |
|
|
406
|
+
| `level` | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | `2` | Nível do heading do título. `1` identifica o conteúdo principal da página. Não se aplica a `fieldset`. |
|
|
407
|
+
| `title` | `ReactNode` | | Forma curta: título da superfície. |
|
|
408
|
+
| `media` | `ReactNode` | | Forma curta: conteúdo do slot de mídia, antes do título. Exige `title`. |
|
|
409
|
+
| `count` | `number` | | Forma curta: total de itens ao lado do título. Exige `title`; indisponível em `fieldset`. |
|
|
410
|
+
| `description` | `ReactNode` | | Forma curta: frase de apoio sob o título. Exige `title`; indisponível em `fieldset`. |
|
|
411
|
+
| `meta` | `ReactNode` | | Forma curta: metadados sob o título e a orientação. Exige `title`; indisponível em `fieldset`. |
|
|
412
|
+
| `actions` | `ReactNode` | | Forma curta: ações sobre a superfície inteira. Exige `title`; indisponível em `fieldset`. |
|
|
413
|
+
| `className` | `string` | | Compõe sobre a superfície. Na caixa simples, é onde entra o recuo. |
|
|
414
|
+
| `children` | `ReactNode` | | Filhos livres, que formam o corpo, ou os slots `SurfaceHeader`, `SurfaceBody` e `SurfaceFooter`. |
|
|
452
415
|
|
|
453
416
|
As demais propriedades são as nativas do elemento escolhido em `as`.
|
|
454
417
|
|
|
455
418
|
## Propriedades de SurfaceHeader
|
|
456
419
|
|
|
457
|
-
| Propriedade | Tipo | Padrão | Descrição
|
|
458
|
-
| ----------- | ----------- | ------ |
|
|
459
|
-
| `
|
|
460
|
-
| `
|
|
420
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
421
|
+
| ----------- | ----------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
422
|
+
| `tabs` | `ReactNode` | | `TabsList` sob o título, controlado por um `Tabs` ancestral. Não disponível em fieldset. |
|
|
423
|
+
| `children` | `ReactNode` | | No máximo um de cada: `SurfaceMedia`, `SurfaceTitle`, `SurfaceCount`, `SurfaceMeta`, `SurfaceDescription` e `SurfaceActions`. Em `fieldset`, o `SurfaceTitle` é obrigatório e somente ele e `SurfaceMedia` são aceitos. |
|
|
424
|
+
| `className` | `string` | | Ajusta a região do header. |
|
|
461
425
|
|
|
462
426
|
`SurfaceHeader` só existe como filho direto de `Surface`.
|
|
463
427
|
|
|
@@ -475,13 +439,6 @@ As demais propriedades são as nativas do elemento escolhido em `as`.
|
|
|
475
439
|
| `children` | `ReactNode` | | Nome da superfície. O heading usa o `level` declarado em `Surface`. |
|
|
476
440
|
| `className` | `string` | | Ajusta o título. |
|
|
477
441
|
|
|
478
|
-
## Propriedades de SurfaceMeta
|
|
479
|
-
|
|
480
|
-
| Propriedade | Tipo | Padrão | Descrição |
|
|
481
|
-
| ----------- | ----------- | ------ | -------------------------------------------------------- |
|
|
482
|
-
| `children` | `ReactNode` | | Metadado curto ao lado do título, como o total de itens. |
|
|
483
|
-
| `className` | `string` | | Ajusta o metadado. |
|
|
484
|
-
|
|
485
442
|
## Propriedades de SurfaceDescription
|
|
486
443
|
|
|
487
444
|
| Propriedade | Tipo | Padrão | Descrição |
|
|
@@ -517,6 +474,27 @@ um wrapper filho declara relações locais mais compactas.
|
|
|
517
474
|
## Subheading e regras de negócio
|
|
518
475
|
|
|
519
476
|
`description` e `SurfaceDescription` apresentam brevemente a finalidade da região. A orientação
|
|
520
|
-
é opcional e não repete o título. A mesma regra vale para uma página
|
|
477
|
+
é opcional e não repete o título. A mesma regra vale para uma página, uma seção ou
|
|
521
478
|
um card: condições, limites e consequências necessários à decisão ficam no corpo ou junto do
|
|
522
479
|
controle correspondente. Pular o subheading não deve comprometer a tarefa.
|
|
480
|
+
|
|
481
|
+
## Propriedades de SurfaceCount
|
|
482
|
+
|
|
483
|
+
| Propriedade | Tipo | Descrição |
|
|
484
|
+
| ----------- | ----------- | ---------------------------------- |
|
|
485
|
+
| `children` | `ReactNode` | Contador ao lado do título. |
|
|
486
|
+
| `className` | `string` | Ajusta a apresentação do contador. |
|
|
487
|
+
|
|
488
|
+
Aceita atributos nativos de `span`. Deve estar em `SurfaceHeader`, fora de `fieldset`.
|
|
489
|
+
|
|
490
|
+
## Propriedades de SurfaceMeta
|
|
491
|
+
|
|
492
|
+
| Propriedade | Tipo | Descrição |
|
|
493
|
+
| ----------- | ----------- | ------------------------------------------------------- |
|
|
494
|
+
| `children` | `ReactNode` | Badges, datas, autoria ou outros metadados do registro. |
|
|
495
|
+
| `className` | `string` | Ajusta a faixa de metadados. |
|
|
496
|
+
|
|
497
|
+
Aceita atributos nativos de `div`. O intervalo superior é de 0,75rem; os itens têm
|
|
498
|
+
0,75rem entre colunas e 0,5rem entre linhas. O componente não insere separadores nem
|
|
499
|
+
carrega dados. Omitir o slot não reserva espaço. Metadados não substituem orientações
|
|
500
|
+
ou condições necessárias à decisão e não substituem a descrição textual da região.
|
|
@@ -8,6 +8,13 @@ A variante padrão não possui borda externa; `TableBody` mantém somente as div
|
|
|
8
8
|
Aplique `text-right` ao cabeçalho e às células quando o domínio pedir alinhamento numérico.
|
|
9
9
|
`TableCaption` descreve a tabela abaixo do conteúdo.
|
|
10
10
|
|
|
11
|
+
As células quebram texto entre palavras. Identificadores longos sem espaços podem exigir
|
|
12
|
+
rolagem horizontal; aplique `wrap-anywhere` à célula quando quiser permitir quebra no meio
|
|
13
|
+
desses valores. Use `whitespace-nowrap` para preservar valores inteiros, como números e datas.
|
|
14
|
+
O contêiner limita a tabela ao espaço recebido e oferece rolagem horizontal quando o conteúdo
|
|
15
|
+
ou larguras mínimas explícitas não cabem. Em uma composição flex ou grid, permita que o ancestral também
|
|
16
|
+
encolha (`min-w-0`); a tabela não pode redefinir a largura de um ancestral.
|
|
17
|
+
|
|
11
18
|
```tsx preview col
|
|
12
19
|
<Table>
|
|
13
20
|
<TableCaption>Sessões recentes do workspace Empresa X.</TableCaption>
|
|
@@ -64,6 +64,10 @@ tom ao sair do canvas e entrar num card, porque `control` representa o resultado
|
|
|
64
64
|
cor translúcida a ser misturada com o ancestral. Da mesma forma, hover, seleção e pressão usam
|
|
65
65
|
papéis diferentes e preservam a mesma ordem comportamental nos dois temas.
|
|
66
66
|
|
|
67
|
+
O fundo discreto (`muted`) também se distingue do hover nos dois temas. A calibração de base
|
|
68
|
+
mantém essa diferença mesmo com Contraste em zero; aumentar esse ajuste amplia os degraus da
|
|
69
|
+
escala. Use os tokens de interação para hover, seleção e pressão, sem criar opacidades por componente.
|
|
70
|
+
|
|
67
71
|
```tsx preview
|
|
68
72
|
const NEUTRAL_STATES = [
|
|
69
73
|
["control", "Controle em repouso."],
|
|
@@ -87,6 +91,16 @@ render(
|
|
|
87
91
|
);
|
|
88
92
|
```
|
|
89
93
|
|
|
94
|
+
A navegação interna usa `bg-sidebar text-foreground`. No claro, ela é quase branca sobre
|
|
95
|
+
conteúdo branco; no escuro, acompanha a camada elevada (`card`). A sidebar geral pode usar
|
|
96
|
+
`bg-sidebar-global text-foreground` para acompanhar o canvas nos dois temas, mantendo continuidade visual sem transparência.
|
|
97
|
+
Os tokens são opacos, e hover, seleção e
|
|
98
|
+
pressão mantêm os papéis de interação compartilhados. O fundo da navegação global continua
|
|
99
|
+
sendo escolhido pelo hospedeiro.
|
|
100
|
+
|
|
101
|
+
No dark, a calibração aplica uma redução final de 5% à luminosidade HSL das superfícies,
|
|
102
|
+
linhas e estados neutros. Textos e contextos semânticos mantêm seus valores para preservar a leitura.
|
|
103
|
+
|
|
90
104
|
### Ajuste de aparência
|
|
91
105
|
|
|
92
106
|
Os tokens neutros desta seção não são valores fixos: o tema os calcula a partir de quatro eixos,
|
|
@@ -298,8 +312,8 @@ render(
|
|
|
298
312
|
|
|
299
313
|
1. Formato HSL nos tokens (o upstream migrou para oklch), escolhido pela legibilidade.
|
|
300
314
|
2. Hierarquia opaca de superfícies e estados nos dois temas: canvas, card/popover, controle, hover,
|
|
301
|
-
seleção e pressão têm papéis próprios. No light,
|
|
302
|
-
|
|
315
|
+
seleção e pressão têm papéis próprios. No preset light, canvas, controles, cards e popovers são brancos; bordas e elevação
|
|
316
|
+
distinguem as regiões; no dark, cards e popovers permanecem acima do canvas. A ADR 0043 registra os valores e
|
|
303
317
|
as restrições de composição. A escala neutra é derivada dos eixos de aparência (ADR 0044); no
|
|
304
318
|
preset, os valores são os calibrados.
|
|
305
319
|
3. Radius mapeado (`xs` a `2xl`) preservando o sentido pré-v4 dos utilitários `rounded-*`. Esse
|
|
@@ -307,8 +321,8 @@ render(
|
|
|
307
321
|
precisam revisar a transição para esse degrau nativo ao customizar `--radius`.
|
|
308
322
|
4. Escala `shadow-*` recomposta em duas camadas claras e difusas — preserva os degraus do
|
|
309
323
|
Tailwind e troca somente o peso visual. Componentes e consumidores usam diretamente a escala.
|
|
310
|
-
5. Bordas mais leves no tema claro: `--border`/`--input` em `92
|
|
311
|
-
|
|
324
|
+
5. Bordas mais leves no tema claro: `--border`/`--input` em `92%` (upstream `89.8%`) — par do canvas
|
|
325
|
+
branco do body; superfícies elevadas usam essa mesma borda real.
|
|
312
326
|
6. Radius base em `0.75rem` — superfícies principais ganham cantos mais presentes sem aproximar
|
|
313
327
|
controles compactos de modais. A decisão detalhada acompanha o pacote em
|
|
314
328
|
`docs/radius-scale.md`, com os defaults, o comportamento de superfícies aninhadas e as
|
|
@@ -29,9 +29,12 @@ export function OpusProvider({ client, dicts, children }: OpusProviderProps): Re
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/** Acesso interno ao adapter; não é reexportado por `@softize/opus/ui/react`. */
|
|
32
|
-
export function
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
export function useOptionalClient(): ClientAdapter | null {
|
|
33
|
+
return useContext(OpusContext)?.client ?? null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function requireClient(client: ClientAdapter | null, hook: string): ClientAdapter {
|
|
37
|
+
if (client === null) {
|
|
35
38
|
throw new Error(
|
|
36
39
|
`${hook} foi chamado fora de um <OpusProvider> e não tem como executar a action. ` +
|
|
37
40
|
'Os hooks de action e os componentes que os usam, como ActionForm, ActionList e ActionView, ' +
|
|
@@ -39,7 +42,11 @@ export function useClient(hook: string): ClientAdapter {
|
|
|
39
42
|
'em testes e previews, envolva o componente renderizado.',
|
|
40
43
|
)
|
|
41
44
|
}
|
|
42
|
-
return
|
|
45
|
+
return client
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function useClient(hook: string): ClientAdapter {
|
|
49
|
+
return requireClient(useOptionalClient(), hook)
|
|
43
50
|
}
|
|
44
51
|
|
|
45
52
|
const NO_DICTS: Record<string, DictLike> = {}
|
package/src/ui/drivers/react.tsx
CHANGED
|
@@ -39,7 +39,7 @@ import {
|
|
|
39
39
|
idleAction,
|
|
40
40
|
idleLookup,
|
|
41
41
|
} from '../index.ts'
|
|
42
|
-
import { useClient } from './context.tsx'
|
|
42
|
+
import { useClient, useOptionalClient, requireClient } from './context.tsx'
|
|
43
43
|
export {
|
|
44
44
|
OpusProvider,
|
|
45
45
|
useDicts,
|
|
@@ -213,18 +213,37 @@ export function useListAction<TItem = unknown, TInput extends Record<string, unk
|
|
|
213
213
|
input?: TInput,
|
|
214
214
|
options?: { enabled?: boolean },
|
|
215
215
|
): UseListActionResult<TItem> {
|
|
216
|
-
|
|
216
|
+
return useListResource<TItem, TInput>(action, input, options)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** Fonte já governada por outro serviço; a chave inclui o escopo de acesso. */
|
|
220
|
+
export interface ListDataSource<TItem> {
|
|
221
|
+
key: readonly string[]
|
|
222
|
+
load: (input: Record<string, unknown>, context: { signal: AbortSignal }) => Promise<Paginated<TItem>>
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Consulta comum ao ActionList e ao hook público, sem duplicar estados de busca. */
|
|
226
|
+
export function useListResource<TItem = unknown, TInput extends Record<string, unknown> = Record<string, unknown>>(
|
|
227
|
+
resource: { name: string; kind: 'list' } | ListDataSource<TItem>,
|
|
228
|
+
input?: TInput,
|
|
229
|
+
options?: { enabled?: boolean },
|
|
230
|
+
): UseListActionResult<TItem> {
|
|
231
|
+
const providedClient = useOptionalClient()
|
|
232
|
+
const external = 'load' in resource
|
|
233
|
+
const client = external ? null : requireClient(providedClient, 'useListAction')
|
|
217
234
|
|
|
218
235
|
const query = useQuery<Paginated<TItem>, ActionError>({
|
|
219
|
-
queryKey: [
|
|
220
|
-
|
|
221
|
-
|
|
236
|
+
queryKey: external ? ['opus:list-source', ...resource.key, input ?? {}] : [resource.name, input ?? {}],
|
|
237
|
+
...(external ? { gcTime: 0, retry: false } : {}),
|
|
238
|
+
queryFn: async (context) => {
|
|
239
|
+
if (external) return resource.load(input ?? {}, { signal: context.signal })
|
|
240
|
+
const result = await client!.run<Paginated<TItem>>(resource as unknown as ActionDef, input ?? {})
|
|
222
241
|
if (!result.ok) throw result.error
|
|
223
242
|
return result.data
|
|
224
243
|
},
|
|
225
244
|
// Input mudou (filtro/página) → mantém os itens na tela enquanto busca: o
|
|
226
245
|
// ActionList esmaece em vez de piscar skeleton. isPending = só o 1º load.
|
|
227
|
-
placeholderData: keepPreviousData,
|
|
246
|
+
placeholderData: external ? undefined : keepPreviousData,
|
|
228
247
|
enabled: options?.enabled ?? true,
|
|
229
248
|
})
|
|
230
249
|
|
|
@@ -259,7 +278,8 @@ export function useGroupedListAction<
|
|
|
259
278
|
pages: Record<string, number>,
|
|
260
279
|
pageSize: number,
|
|
261
280
|
): ListActionGroup<TItem>[] {
|
|
262
|
-
const
|
|
281
|
+
const providedClient = useOptionalClient()
|
|
282
|
+
const client = field === null ? providedClient : requireClient(providedClient, 'ActionList grouping')
|
|
263
283
|
return useGroupedListQueries<TItem, TInput>(
|
|
264
284
|
client,
|
|
265
285
|
action,
|
package/src/ui/meta.ts
CHANGED
|
@@ -15,7 +15,7 @@ export const componentMeta = {
|
|
|
15
15
|
name: "surface",
|
|
16
16
|
ancestry: "opus",
|
|
17
17
|
whenToUse:
|
|
18
|
-
'Delimite e nomeie um trecho da tela: uma região da página, um grupo de campos, um registro, um item de grade ou um bloco clicável. `as` escolhe o elemento (`div`, `section`, `article` ou `fieldset`) e `asChild` entrega a superfície a um botão ou link. `framed` acrescenta a moldura, `elevation="raised"` dá fundo próprio
|
|
18
|
+
'Delimite e nomeie um trecho da tela: uma região da página, um grupo de campos, um registro, um item de grade ou um bloco clicável. `as` escolhe o elemento (`div`, `section`, `article` ou `fieldset`) e `asChild` entrega a superfície a um botão ou link. `framed` acrescenta a moldura, `elevation="raised"` dá fundo próprio. Superfícies delimitadas derivam os divisores das regiões presentes; sem moldura, conservam o alinhamento e ritmo do hospedeiro. No caso comum, declare `title`, `description`, `count`, `actions` e `media` na própria Surface; componha SurfaceHeader, SurfaceBody e SurfaceFooter quando precisar controlar a estrutura. O título principal de Page vem de PageTitle; Surfaces internas usam níveis subordinados. `level={1}` fica restrito a superfícies fora de Page. Para um modal use Dialog, para um painel lateral use Drawer e para uma coluna de layout use Pane.',
|
|
19
19
|
},
|
|
20
20
|
ask: {
|
|
21
21
|
name: "ask",
|
|
@@ -165,7 +165,7 @@ export const componentMeta = {
|
|
|
165
165
|
name: "separator",
|
|
166
166
|
ancestry: "shadcn",
|
|
167
167
|
whenToUse:
|
|
168
|
-
'Marque a fronteira entre blocos de conteúdo que não pertencem a uma superfície com regiões. A linha horizontal fica entre blocos empilhados; com `orientation="vertical"`, ela separa itens lado a lado e herda a altura do contêiner. A linha é decorativa por padrão e fica fora da árvore de acessibilidade; use `decorative={false}` quando a divisão tiver significado. Não use Separator entre cabeçalho, corpo e rodapé de Surface, Dialog ou Drawer: nessas superfícies, os divisores são derivados das regiões
|
|
168
|
+
'Marque a fronteira entre blocos de conteúdo que não pertencem a uma superfície com regiões. A linha horizontal fica entre blocos empilhados; com `orientation="vertical"`, ela separa itens lado a lado e herda a altura do contêiner. A linha é decorativa por padrão e fica fora da árvore de acessibilidade; use `decorative={false}` quando a divisão tiver significado. Não use Separator entre cabeçalho, corpo e rodapé de Surface, Dialog ou Drawer: nessas superfícies, os divisores são derivados das regiões automaticamente.',
|
|
169
169
|
},
|
|
170
170
|
skeleton: {
|
|
171
171
|
name: "skeleton",
|
|
@@ -261,7 +261,7 @@ export const componentMeta = {
|
|
|
261
261
|
name: "floating-layer",
|
|
262
262
|
ancestry: "opus",
|
|
263
263
|
whenToUse:
|
|
264
|
-
|
|
264
|
+
'Mantenha utilizável, por cima de qualquer Dialog ou Drawer aberto, uma região que acompanha a aplicação inteira, como o botão e o painel de um assistente. O modal continua aberto e intacto atrás; a camada recebe clique, foco, digitação, rolagem e Esc. Diálogos que exigem resposta, como `Dialog mode="alert"` e `dialog.confirm`, continuam cobrindo a camada até a resposta. Cada filho se posiciona contra a janela, e o hospedeiro publica em `data-modal` e `data-modal-side` o Dialog ou Drawer aberto. Para ferramentas sobre uma única superfície, use Dock; para conteúdo preso a um gatilho, Popover.',
|
|
265
265
|
},
|
|
266
266
|
drawer: {
|
|
267
267
|
name: "drawer",
|
|
@@ -388,7 +388,7 @@ export const componentMeta = {
|
|
|
388
388
|
name: "action-list",
|
|
389
389
|
ancestry: "opus",
|
|
390
390
|
whenToUse:
|
|
391
|
-
"Apresente uma coleção pesquisável a partir de uma ListAction. O contrato declara colunas, filtros, período, busca e dimensões fechadas de agrupamento; ActionList coordena visualizações, paginação, seleção em lote e sincronização com a URL. A tabela derivada não cria moldura própria.
|
|
391
|
+
"Apresente uma coleção pesquisável a partir de uma ListAction. O contrato declara colunas, filtros, período, busca e dimensões fechadas de agrupamento; ActionList coordena visualizações, paginação, seleção em lote e sincronização com a URL. A tabela derivada não cria moldura própria. Use item com os campos title e description para uma lista de itens conectados; title da coleção reúne suas toolbarActions. Fontes externas já governadas por outro serviço usam source com chave de escopo e loader, preservando o transporte existente. Para um único recurso, use ActionView.",
|
|
392
392
|
},
|
|
393
393
|
"action-trigger": {
|
|
394
394
|
name: "action-trigger",
|
|
@@ -406,7 +406,7 @@ export const componentMeta = {
|
|
|
406
406
|
name: "page",
|
|
407
407
|
ancestry: "opus",
|
|
408
408
|
whenToUse:
|
|
409
|
-
"Estruture uma página com
|
|
409
|
+
"Estruture uma página aberta com título e ações no header, metadados abaixo e conteúdo na mesma coluna. Page usa padding de 2,5rem, título de 1,875rem, intervalo de 1,5rem após o cabeçalho e de 1rem entre as demais regiões, sem divisores. width escolhe narrow, default ou wide; sem opção, ocupa a largura disponível. Ações dos headers de Page, Drawer, Dialog e Surface usam default/icon e linha mínima de 2,25rem. PageHeader.tabs recebe TabsList; TabsPanel fica no body. PageBack é o primeiro ícone das ações, com destino explícito e tooltip. PageShell fornece ações sem criar outra barra. layout flow rola a página inteira; fill mantém o header visível e entrega a altura a listas ou à rolagem do body. Drawer e Dialog mantêm suas superfícies delimitadas e recuo de 1rem. PageState substitui o header e o conteúdo durante carregamento, falha ou ausência. Use DataState para falha parcial. Título e metadados pertencem ao PageHeader.",
|
|
410
410
|
},
|
|
411
411
|
presentation: {
|
|
412
412
|
name: "presentation",
|
package/src/ui/react.tsx
CHANGED
|
@@ -25,6 +25,7 @@ export type {
|
|
|
25
25
|
UseFormActionOptions,
|
|
26
26
|
UseFormActionResult,
|
|
27
27
|
UseListActionResult,
|
|
28
|
+
ListDataSource,
|
|
28
29
|
UseLookupActionResult,
|
|
29
30
|
UseTriggerActionOptions,
|
|
30
31
|
UseTriggerActionResult,
|
|
@@ -175,8 +176,7 @@ export {
|
|
|
175
176
|
DialogNavigation,
|
|
176
177
|
DialogMedia,
|
|
177
178
|
DialogActions,
|
|
178
|
-
|
|
179
|
-
DialogIntroActions,
|
|
179
|
+
DialogMeta,
|
|
180
180
|
DialogBody,
|
|
181
181
|
DialogFooter,
|
|
182
182
|
DialogTitle,
|
|
@@ -194,7 +194,6 @@ export type { FloatingLayerProps } from "./components/primitives/floating-layer.
|
|
|
194
194
|
|
|
195
195
|
// O pacote de anatomia que Surface, Dialog, Drawer e Pane declaram no root (ADR 0023). As
|
|
196
196
|
// tabelas e o contexto continuam internos; o consumidor escolhe entre os dois nomes.
|
|
197
|
-
export type { SurfaceLayout } from "./components/patterns/surface-anatomy.ts";
|
|
198
197
|
|
|
199
198
|
export {
|
|
200
199
|
Menu,
|
|
@@ -324,8 +323,7 @@ export {
|
|
|
324
323
|
export {
|
|
325
324
|
Drawer,
|
|
326
325
|
DrawerActions,
|
|
327
|
-
|
|
328
|
-
DrawerIntroActions,
|
|
326
|
+
DrawerMeta,
|
|
329
327
|
DrawerClose,
|
|
330
328
|
DrawerContent,
|
|
331
329
|
DrawerFooter,
|
|
@@ -465,12 +463,15 @@ export {
|
|
|
465
463
|
presetRange,
|
|
466
464
|
listParamsToState,
|
|
467
465
|
listStateToParams,
|
|
466
|
+
listStateToInput,
|
|
468
467
|
} from "./components/patterns/list.tsx";
|
|
469
468
|
export type {
|
|
470
469
|
ActionFilterBarProps,
|
|
471
470
|
ActionFilterState,
|
|
472
471
|
AdvancedFiltersLayout,
|
|
473
472
|
ActionListProps,
|
|
473
|
+
ActionListItemSpec,
|
|
474
|
+
ActionListContext,
|
|
474
475
|
ActionListColumn,
|
|
475
476
|
ActionListState,
|
|
476
477
|
ActionListView,
|
|
@@ -515,9 +516,7 @@ export {
|
|
|
515
516
|
PageShell,
|
|
516
517
|
Page,
|
|
517
518
|
PageHeader,
|
|
518
|
-
|
|
519
|
-
PageIntroActions,
|
|
520
|
-
PageNavigation,
|
|
519
|
+
PageMeta,
|
|
521
520
|
PageBack,
|
|
522
521
|
PageTitle,
|
|
523
522
|
PageDescription,
|
|
@@ -549,6 +548,7 @@ export {
|
|
|
549
548
|
SurfaceTitle,
|
|
550
549
|
SurfaceDescription,
|
|
551
550
|
SurfaceMeta,
|
|
551
|
+
SurfaceCount,
|
|
552
552
|
SurfaceActions,
|
|
553
553
|
SurfaceBody,
|
|
554
554
|
SurfaceFooter,
|
|
@@ -622,3 +622,33 @@ export type {
|
|
|
622
622
|
DotContext,
|
|
623
623
|
DotVariant,
|
|
624
624
|
} from "./components/primitives/dot.tsx";
|
|
625
|
+
|
|
626
|
+
export {
|
|
627
|
+
bindPresentationList,
|
|
628
|
+
bindPresentationActionList,
|
|
629
|
+
bindPresentationCells,
|
|
630
|
+
bindPresentationCollection,
|
|
631
|
+
bindPresentationDetail,
|
|
632
|
+
bindPresentationOptions,
|
|
633
|
+
bindPresentationField,
|
|
634
|
+
bindPresentationComponent,
|
|
635
|
+
bindPresentationContent,
|
|
636
|
+
type PresentationRecordSource,
|
|
637
|
+
type PresentationComponentProps,
|
|
638
|
+
type PresentationComponentAdapter,
|
|
639
|
+
bindPresentationListContent,
|
|
640
|
+
bindPresentationRowContent,
|
|
641
|
+
} from "./components/patterns/presentation-sections.tsx";
|
|
642
|
+
export type {
|
|
643
|
+
PresentationCompositionRuntime,
|
|
644
|
+
PresentationListAdapter,
|
|
645
|
+
PresentationCellsAdapter,
|
|
646
|
+
PresentationCollectionAdapter,
|
|
647
|
+
PresentationCollectionControls,
|
|
648
|
+
PresentationDetailAdapter,
|
|
649
|
+
PresentationOptionsAdapter,
|
|
650
|
+
PresentationFieldAdapter,
|
|
651
|
+
PresentationFieldProps,
|
|
652
|
+
PresentationListContentAdapter,
|
|
653
|
+
PresentationRowContentAdapter,
|
|
654
|
+
} from "./components/patterns/presentation-sections.tsx";
|