@softize/opus 17.0.0 → 17.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 +11 -0
- package/docs/adr/0013-presentation-is-a-portable-action-oriented-artifact.md +17 -11
- package/docs/adr/0015-action-size-follows-interaction-density.md +64 -0
- package/package.json +1 -1
- package/registry/skills/build-opus-ui/references/ui-patterns.md +4 -2
- package/src/ui/components/patterns/form.tsx +7 -1
- package/src/ui/components/patterns/presentation.tsx +36 -6
- package/src/ui/docs/content/button.md +8 -2
- package/src/ui/docs/content/page.md +2 -2
- package/src/ui/docs/content/presentation.md +22 -7
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,17 @@ Depois de qualquer bump, rode os gates (`typecheck` · `test` · `opus check` ·
|
|
|
7
7
|
`opus copy --check` · `base copy check` · `manifest:check`) — eles apontam o que a
|
|
8
8
|
mudança cobra do seu código.
|
|
9
9
|
|
|
10
|
+
## 17.1.0 — 2026-09-12
|
|
11
|
+
|
|
12
|
+
Formulários de `Presentation` em Dialog e Drawer passam a oferecer `Cancelar` ao lado da ação
|
|
13
|
+
principal. O retorno no header modal aparece somente quando a surface anterior também é um Dialog
|
|
14
|
+
ou Drawer; a Page mantida ao fundo não cria uma etapa de navegação.
|
|
15
|
+
|
|
16
|
+
Em Page hospedada por `PageShell`, a `Presentation` mantém navegação e ações na barra e apresenta o
|
|
17
|
+
título no `PageIntro`. Ações que abrem outra Presentation usam o botão default para assumir a
|
|
18
|
+
hierarquia primária do fluxo. Ações textuais de header e footer usam o tamanho `default`; `sm` fica
|
|
19
|
+
reservado às operações densas de toolbar, seção e coleção.
|
|
20
|
+
|
|
10
21
|
## 17.0.0 — 2026-09-11
|
|
11
22
|
|
|
12
23
|
A inspeção de `Presentation` passa a pertencer exclusivamente à Lens, que lê as definições estáticas
|
|
@@ -19,11 +19,12 @@ capacidades em uma apresentação verificável.
|
|
|
19
19
|
`Presentation` é o artefato declarativo que descreve um recurso independentemente da superfície em
|
|
20
20
|
que aparece. `Surface` escolhe `page`, `dialog` ou `drawer` no momento da renderização.
|
|
21
21
|
|
|
22
|
-
A anatomia portátil é `Header + Body + Footer
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
A anatomia portátil é `Header + Body + Footer?`, mas cada surface materializa seu header de acordo
|
|
23
|
+
com o espaço disponível. Dialog e Drawer organizam horizontalmente
|
|
24
|
+
`Navigation? + Title + Actions? + Close` e mantêm as ações de conclusão no footer. Em Page
|
|
25
|
+
hospedada por `PageShell`, a barra persistente reúne navegação e ações; o título inicia o conteúdo
|
|
26
|
+
em `PageIntro`, sem repetir a página atual ao lado do breadcrumb. Fora do shell, a Page materializa
|
|
27
|
+
seu header completo no próprio container.
|
|
27
28
|
|
|
28
29
|
Uma action assume um de dois papéis:
|
|
29
30
|
|
|
@@ -48,12 +49,15 @@ estática, publicável no manifest e inspecionável pela Lens. A invocação é
|
|
|
48
49
|
uma execução: identifica a Presentation, escolhe a surface, carrega somente input JSON e mantém a
|
|
49
50
|
pilha de frames necessária para voltar. Ela não é publicada no manifest.
|
|
50
51
|
|
|
51
|
-
`back` restaura o último frame da pilha.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
`back` restaura o último frame da pilha. Em Dialog ou Drawer, ele só é apresentado quando o frame
|
|
53
|
+
anterior também é modal; a Page mantida ao fundo não cria uma etapa intermediária. `close` encerra
|
|
54
|
+
apenas dialog ou drawer e é inválido para page. Formulários modais oferecem `Cancelar` como ação de
|
|
55
|
+
abandono no footer, ao lado da conclusão. `navigate` abre outra invocação por push ou substitui a
|
|
56
|
+
atual; sincronizar esse estado com URL é responsabilidade do adaptador da aplicação quando refresh,
|
|
57
|
+
deep link ou histórico forem necessários. O inspetor de desenvolvimento reúne definição,
|
|
58
|
+
invocação, bindings resolvidos e diagnósticos, mascarando chaves sensíveis antes de expor o JSON. O
|
|
59
|
+
inventário estático e seu JSON ficam disponíveis na Lens; a aplicação não adiciona um launcher
|
|
60
|
+
flutuante ao shell.
|
|
57
61
|
|
|
58
62
|
## Consequências
|
|
59
63
|
|
|
@@ -90,6 +94,8 @@ Foi descartada porque compatibilidade exige delegar cada kind ao pattern canôni
|
|
|
90
94
|
- o manifest projeta Presentations sem callbacks nem dados server-only;
|
|
91
95
|
- a lens lista e expõe o JSON integral de cada Presentation;
|
|
92
96
|
- testes de invocação cobrem push, replace, back, close e rejeição de input não JSON;
|
|
97
|
+
- testes do renderer cobrem a ausência de retorno entre Page e modal, o retorno entre modais e as
|
|
98
|
+
ações Cancelar/concluir do formulário;
|
|
93
99
|
- o inspetor mascara segredo, token, cookie, autorização, senha e chave de API;
|
|
94
100
|
- a Lens lista as definições registradas e abre o JSON integral sem depender da árvore React;
|
|
95
101
|
- uma aplicação consumidora usa o pacote local por symlink durante a migração página por página.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# ADR 0015 — O tamanho da ação acompanha a densidade da interação
|
|
2
|
+
|
|
3
|
+
- **Status:** aceita.
|
|
4
|
+
- **Data:** 2026-09-12.
|
|
5
|
+
|
|
6
|
+
## Contexto
|
|
7
|
+
|
|
8
|
+
O uso de `default` e `sm` passou a variar entre headers de Page, footers de Dialog e Drawer,
|
|
9
|
+
toolbars e ações de listagem. Como tamanho também era usado para sugerir importância, duas decisões
|
|
10
|
+
equivalentes podiam ganhar alturas diferentes, enquanto ações repetitivas ocupavam o mesmo espaço
|
|
11
|
+
de uma conclusão da superfície.
|
|
12
|
+
|
|
13
|
+
O Opus já separa contexto semântico, variante visual e tamanho. Misturar esses eixos impede que a
|
|
14
|
+
mesma regra funcione quando uma ação muda de secundária para primária ou quando um recurso troca de
|
|
15
|
+
Page para Dialog ou Drawer.
|
|
16
|
+
|
|
17
|
+
## Decisão
|
|
18
|
+
|
|
19
|
+
O tamanho comunica densidade e alcance da interação; `context` e `variant` comunicam hierarquia e
|
|
20
|
+
risco.
|
|
21
|
+
|
|
22
|
+
- ações textuais que decidem a superfície usam `default`: header de Page e footer de Page, Dialog
|
|
23
|
+
ou Drawer;
|
|
24
|
+
- ações operacionais em toolbar, header de seção ou coleção densa usam `sm`;
|
|
25
|
+
- ações internas de linha, célula ou campo usam `xs` ou `icon-xs`;
|
|
26
|
+
- ações somente com ícone que pertencem ao chrome da superfície, como voltar e fechar, usam
|
|
27
|
+
`icon-sm`;
|
|
28
|
+
- `lg` fica reservado a chamadas que deliberadamente precisam de uma área de toque maior, não a
|
|
29
|
+
uma ação primária comum.
|
|
30
|
+
|
|
31
|
+
Patterns que conhecem a região aplicam o tamanho. Componentes estruturais genéricos não clonam nem
|
|
32
|
+
reescrevem filhos arbitrários: em composição manual, o consumidor segue a mesma matriz. Alterar a
|
|
33
|
+
ênfase de uma ação não altera seu tamanho dentro da região.
|
|
34
|
+
|
|
35
|
+
## Consequências
|
|
36
|
+
|
|
37
|
+
- Page, Dialog e Drawer mantêm proporção equivalente nas decisões principais;
|
|
38
|
+
- toolbars e grids preservam densidade sem reduzir ações de conclusão;
|
|
39
|
+
- `primary`, `neutral` e `danger` podem mudar sem provocar salto de altura;
|
|
40
|
+
- exemplos e skills deixam de recomendar `sm` por todo cabeçalho compacto.
|
|
41
|
+
|
|
42
|
+
## Alternativas consideradas
|
|
43
|
+
|
|
44
|
+
### Usar `sm` em toda barra ou header
|
|
45
|
+
|
|
46
|
+
Preservaria a menor altura possível, mas trataria uma decisão da página como operação repetitiva e
|
|
47
|
+
criaria diferença em relação ao footer modal do mesmo recurso.
|
|
48
|
+
|
|
49
|
+
### Fazer a ação primária sempre maior
|
|
50
|
+
|
|
51
|
+
Reforçaria hierarquia, mas misturaria importância com densidade. A variante `solid` e o contexto
|
|
52
|
+
`primary` já comunicam essa relação sem deslocar o layout.
|
|
53
|
+
|
|
54
|
+
### Fazer `ButtonGroup` redimensionar qualquer filho
|
|
55
|
+
|
|
56
|
+
Centralizaria parte da aparência, mas introduziria comportamento implícito e frágil para filhos que
|
|
57
|
+
não são `Button`. A região ou o pattern conhece melhor o tamanho adequado.
|
|
58
|
+
|
|
59
|
+
## Verificação
|
|
60
|
+
|
|
61
|
+
- testes da `Presentation` conferem `default` em ações textuais do header e do footer;
|
|
62
|
+
- testes de `ActionList` continuam cobrindo `sm` em toolbar e `icon-xs` nas ações de linha;
|
|
63
|
+
- a documentação de Button e Page apresenta a mesma matriz;
|
|
64
|
+
- revisão de consumidor trata divergência como exceção explícita, não como novo default.
|
package/package.json
CHANGED
|
@@ -21,8 +21,10 @@ PageActions?) + PageBody`; `title` e `actions` no próprio `Page` são a abrevia
|
|
|
21
21
|
- Quando shell e rota conhecem partes diferentes da mesma página, use `PageShell` ao redor da rota.
|
|
22
22
|
O shell é o único responsável pela barra: fornece `navigation`; a `Page` descendente continua
|
|
23
23
|
declarando `title` e `actions`. O Opus mantém a barra de `3rem` e projeta título e ações nela.
|
|
24
|
-
Ações com texto na barra
|
|
25
|
-
|
|
24
|
+
Ações com texto na barra usam o tamanho `default`, como as ações de footer de Dialog e Drawer;
|
|
25
|
+
ações somente com ícone de chrome usam `icon-sm`. O tamanho `sm` fica para ações operacionais em
|
|
26
|
+
toolbar, seção ou coleção densa, e `xs`/`icon-xs` para ações internas de linha ou célula. Contexto
|
|
27
|
+
e variante resolvem a hierarquia visual sem alterar essa medida. Na forma explícita dentro do
|
|
26
28
|
shell, use `Page > PageHeader (PageNavigation?, PageTitle, PageActions?) + PageBody`, com
|
|
27
29
|
`PageIntro` opcional antes do body. Não monte
|
|
28
30
|
`PaneHeader`, portal ou seletor global para reconstruir essa composição.
|
|
@@ -719,7 +719,13 @@ export function ActionForm<TInput extends Record<string, unknown>, TData>({
|
|
|
719
719
|
{wrapFooter(
|
|
720
720
|
<>
|
|
721
721
|
{onCancel !== undefined && (
|
|
722
|
-
<Button
|
|
722
|
+
<Button
|
|
723
|
+
type="button"
|
|
724
|
+
form={formId}
|
|
725
|
+
variant="ghost"
|
|
726
|
+
disabled={disabled || isLoading}
|
|
727
|
+
onClick={onCancel}
|
|
728
|
+
>
|
|
723
729
|
{cancelLabel}
|
|
724
730
|
</Button>
|
|
725
731
|
)}
|
|
@@ -29,7 +29,11 @@ import type {
|
|
|
29
29
|
ViewContract,
|
|
30
30
|
} from "../../../core/contracts.ts";
|
|
31
31
|
import { cn } from "../../lib/cn.ts";
|
|
32
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
Button,
|
|
34
|
+
buttonVariants,
|
|
35
|
+
type ButtonSize,
|
|
36
|
+
} from "../primitives/button.tsx";
|
|
33
37
|
import { ButtonGroup } from "../primitives/button-group.tsx";
|
|
34
38
|
import { Copyable } from "../primitives/copyable.tsx";
|
|
35
39
|
import { DetailField, DetailGroup } from "../primitives/detail.tsx";
|
|
@@ -55,6 +59,7 @@ import {
|
|
|
55
59
|
PageBody,
|
|
56
60
|
PageFooter,
|
|
57
61
|
PageHeader,
|
|
62
|
+
PageIntro,
|
|
58
63
|
PageNavigation,
|
|
59
64
|
PageTitle,
|
|
60
65
|
useInsidePageShell,
|
|
@@ -147,13 +152,15 @@ function PresentationFrame({
|
|
|
147
152
|
{navigation === undefined ? null : (
|
|
148
153
|
<PageNavigation>{navigation}</PageNavigation>
|
|
149
154
|
)}
|
|
150
|
-
<PageTitle>{title}</PageTitle>
|
|
151
155
|
{headerActions === undefined ? null : (
|
|
152
156
|
<PageActions>
|
|
153
157
|
<PresentationActions>{headerActions}</PresentationActions>
|
|
154
158
|
</PageActions>
|
|
155
159
|
)}
|
|
156
160
|
</PageHeader>
|
|
161
|
+
<PageIntro>
|
|
162
|
+
<PageTitle>{title}</PageTitle>
|
|
163
|
+
</PageIntro>
|
|
157
164
|
<PageBody className="min-h-0 flex-1 overflow-y-auto">{body}</PageBody>
|
|
158
165
|
{hasFooter ? <PageFooter>{renderFooterActions()}</PageFooter> : null}
|
|
159
166
|
</Page>
|
|
@@ -298,6 +305,7 @@ function PresentationCommandTrigger({
|
|
|
298
305
|
command,
|
|
299
306
|
action,
|
|
300
307
|
input,
|
|
308
|
+
size,
|
|
301
309
|
disabled,
|
|
302
310
|
onLoadingChange,
|
|
303
311
|
onSuccess,
|
|
@@ -305,6 +313,7 @@ function PresentationCommandTrigger({
|
|
|
305
313
|
command: PresentationCommand;
|
|
306
314
|
action: SimpleContract<Record<string, unknown>, unknown>;
|
|
307
315
|
input: Record<string, unknown>;
|
|
316
|
+
size: ButtonSize;
|
|
308
317
|
disabled: boolean;
|
|
309
318
|
onLoadingChange: (command: PresentationCommand, loading: boolean) => void;
|
|
310
319
|
onSuccess: (data: unknown) => void;
|
|
@@ -318,6 +327,7 @@ function PresentationCommandTrigger({
|
|
|
318
327
|
action={action}
|
|
319
328
|
input={input}
|
|
320
329
|
variant="ghost"
|
|
330
|
+
size={size}
|
|
321
331
|
disabled={disabled}
|
|
322
332
|
onLoadingChange={reportLoading}
|
|
323
333
|
onSuccess={onSuccess}
|
|
@@ -374,6 +384,11 @@ export function Presentation({
|
|
|
374
384
|
[context, invocation, onInvocationChange, onRefresh],
|
|
375
385
|
);
|
|
376
386
|
|
|
387
|
+
const closeSurface = useCallback((): void => {
|
|
388
|
+
onOpenChange?.(false);
|
|
389
|
+
applyEffects([{ effect: "close" }]);
|
|
390
|
+
}, [applyEffects, onOpenChange]);
|
|
391
|
+
|
|
377
392
|
const reportLoading = useCallback(
|
|
378
393
|
(command: PresentationCommand, loading: boolean): void => {
|
|
379
394
|
setLoadingCommands((current) => {
|
|
@@ -429,6 +444,10 @@ export function Presentation({
|
|
|
429
444
|
string,
|
|
430
445
|
unknown
|
|
431
446
|
>;
|
|
447
|
+
// Header e footer são regiões de decisão da superfície, não toolbars operacionais.
|
|
448
|
+
// Seus comandos textuais usam a linha padrão; listas e grids continuam donos da
|
|
449
|
+
// densidade compacta de suas próprias ações.
|
|
450
|
+
const size: ButtonSize = "default";
|
|
432
451
|
if (action.kind === "simple") {
|
|
433
452
|
return (
|
|
434
453
|
<PresentationCommandTrigger
|
|
@@ -436,6 +455,7 @@ export function Presentation({
|
|
|
436
455
|
command={command}
|
|
437
456
|
action={action as SimpleContract<Record<string, unknown>, unknown>}
|
|
438
457
|
input={input}
|
|
458
|
+
size={size}
|
|
439
459
|
disabled={isBlocked(command)}
|
|
440
460
|
onLoadingChange={reportLoading}
|
|
441
461
|
onSuccess={(data) => applyEffects(command.onSuccess, data)}
|
|
@@ -450,7 +470,7 @@ export function Presentation({
|
|
|
450
470
|
return (
|
|
451
471
|
<Button
|
|
452
472
|
key={`${command.placement}:${command.action}`}
|
|
453
|
-
|
|
473
|
+
size={size}
|
|
454
474
|
disabled={isBlocked(command)}
|
|
455
475
|
onClick={() =>
|
|
456
476
|
openTarget(
|
|
@@ -480,6 +500,11 @@ export function Presentation({
|
|
|
480
500
|
action={bodyAction as FormContract<Record<string, unknown>, unknown>}
|
|
481
501
|
defaultValues={bodyInput}
|
|
482
502
|
submitLabel={definition.body.submitLabel}
|
|
503
|
+
onCancel={
|
|
504
|
+
invocation.surface === "page"
|
|
505
|
+
? undefined
|
|
506
|
+
: closeSurface
|
|
507
|
+
}
|
|
483
508
|
onSuccess={(data) => applyEffects(definition.body.onSuccess, data)}
|
|
484
509
|
disabled={hasSurfaceBlock}
|
|
485
510
|
onLoadingChange={setBodyLoading}
|
|
@@ -544,8 +569,12 @@ export function Presentation({
|
|
|
544
569
|
const footerActions = definition.actions
|
|
545
570
|
.filter((command) => command.placement === "footer")
|
|
546
571
|
.map(renderCommand);
|
|
572
|
+
const parentSurface = invocation.stack.at(-1)?.surface;
|
|
573
|
+
const canGoBack =
|
|
574
|
+
parentSurface !== undefined &&
|
|
575
|
+
(invocation.surface === "page" || parentSurface !== "page");
|
|
547
576
|
const navigation =
|
|
548
|
-
|
|
577
|
+
!canGoBack ? undefined : (
|
|
549
578
|
<Button
|
|
550
579
|
size="icon-sm"
|
|
551
580
|
variant="ghost"
|
|
@@ -567,10 +596,11 @@ export function Presentation({
|
|
|
567
596
|
footerActions={footerActions.length === 0 ? undefined : footerActions}
|
|
568
597
|
open={open}
|
|
569
598
|
onOpenChange={(nextOpen) => {
|
|
570
|
-
onOpenChange?.(nextOpen);
|
|
571
599
|
if (!nextOpen && invocation.surface !== "page") {
|
|
572
|
-
|
|
600
|
+
closeSurface();
|
|
601
|
+
return;
|
|
573
602
|
}
|
|
603
|
+
onOpenChange?.(nextOpen);
|
|
574
604
|
}}
|
|
575
605
|
className={className}
|
|
576
606
|
bodyClassName={bodyClassNameProp}
|
|
@@ -24,14 +24,20 @@ há texto visível. O glifo dentro do controle acompanha o tamanho (0.875rem em
|
|
|
24
24
|
| Nome | Medida | Uso |
|
|
25
25
|
| --------- | ------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
|
26
26
|
| `xs` | 1.5rem | Ação dentro de um campo (`InputGroupButton`). |
|
|
27
|
-
| `sm` | 2rem |
|
|
28
|
-
| `default` | 2.25rem |
|
|
27
|
+
| `sm` | 2rem | Ação operacional em toolbar, seção ou coleção densa, ao lado de `Select` e `Tabs` `sm`. |
|
|
28
|
+
| `default` | 2.25rem | Decisão da superfície em header de Page ou footer de Dialog/Drawer; é também a linha padrão de `Input`. |
|
|
29
29
|
| `lg` | 2.5rem | Chamada principal com mais área de toque. |
|
|
30
30
|
| `icon-xs` | 1.5rem | Ação só de ícone dentro de um campo ou de uma linha densa; é o quadrado de `ActionTrigger` com `icon`. |
|
|
31
31
|
| `icon-sm` | 1.75rem | Ação só de ícone em composições compactas que precisam de mais presença; também é o quadrado das setas do pager de `ActionList`. |
|
|
32
32
|
| `icon` | 2.25rem | Ação só de ícone na linha padrão. |
|
|
33
33
|
| `icon-lg` | 2.5rem | Ação só de ícone ao lado de um `lg`. |
|
|
34
34
|
|
|
35
|
+
Escolha o tamanho pela região, não pela importância visual: `variant` e `context` resolvem a
|
|
36
|
+
hierarquia da ação. Headers de Page e footers de Dialog/Drawer usam `default`; ações operacionais
|
|
37
|
+
de seção, toolbar e coleção usam `sm`; ações dentro de linha ou célula usam `xs` ou `icon-xs`.
|
|
38
|
+
Controles de chrome, como voltar e fechar, usam `icon-sm`. Assim a mesma decisão mantém a mesma
|
|
39
|
+
altura mesmo quando uma superfície troca uma ação secundária por uma primária.
|
|
40
|
+
|
|
35
41
|
```tsx preview
|
|
36
42
|
<Button size="xs">Mínimo</Button>
|
|
37
43
|
<Button size="sm">Pequeno</Button>
|
|
@@ -82,7 +82,7 @@ Use `PageShell` quando o shell conhece o breadcrumb e a rota conhece título e a
|
|
|
82
82
|
<Page
|
|
83
83
|
title="Clientes"
|
|
84
84
|
actions={
|
|
85
|
-
<Button
|
|
85
|
+
<Button>
|
|
86
86
|
<Plus /> Novo cliente
|
|
87
87
|
</Button>
|
|
88
88
|
}
|
|
@@ -103,7 +103,7 @@ Na composição explícita dentro do shell, use `PageHeader` e `PageBody`:
|
|
|
103
103
|
<PageHeader>
|
|
104
104
|
<PageTitle>Clientes</PageTitle>
|
|
105
105
|
<PageActions>
|
|
106
|
-
<Button
|
|
106
|
+
<Button>Novo cliente</Button>
|
|
107
107
|
</PageActions>
|
|
108
108
|
</PageHeader>
|
|
109
109
|
<PageBody>Conteúdo da listagem.</PageBody>
|
|
@@ -2,17 +2,23 @@
|
|
|
2
2
|
um drawer. A superfície decide quanto espaço ocupar; o recurso continua com a mesma navegação,
|
|
3
3
|
cabeçalho, body e rodapé.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
Dialog e Drawer mantêm no cabeçalho a ordem horizontal de navegação ou retorno, título e ações.
|
|
6
|
+
O retorno aparece em uma surface modal somente
|
|
7
|
+
quando ela foi aberta sobre outro Dialog ou Drawer; a Page ao fundo sustenta o modal, mas não cria
|
|
8
|
+
uma etapa de navegação. Formulários modais encerram o footer com `Cancelar` e a ação principal.
|
|
9
|
+
Em Page hospedada por `PageShell`, a barra concentra navegação e ações, enquanto o título abre o
|
|
10
|
+
conteúdo em um `PageIntro`. Assim o breadcrumb não disputa espaço nem repete o título na mesma linha.
|
|
11
|
+
Ações textuais de header e footer usam o tamanho `default`, pois pertencem à decisão da superfície;
|
|
12
|
+
o tamanho `sm` permanece reservado às operações densas de toolbar, seção e coleção. Contexto e
|
|
13
|
+
variante continuam definindo hierarquia visual sem alterar essa medida.
|
|
9
14
|
|
|
10
15
|
Use esse padrão quando uma lista puder abrir um detalhe lateral, quando a mesma edição precisar
|
|
11
16
|
funcionar em modal e em rota própria ou quando um fluxo começar compacto e crescer sem ganhar uma
|
|
12
17
|
segunda implementação.
|
|
13
18
|
|
|
14
19
|
Dentro de `PageShell`, a superfície `page` usa a anatomia real da aplicação: a Presentation projeta
|
|
15
|
-
navigation
|
|
20
|
+
navigation e actions no header da barra, apresenta o título em `PageIntro` e compõe `PageBody` e,
|
|
21
|
+
quando necessário, `PageFooter`.
|
|
16
22
|
Fora do shell, ela materializa o `PageHeader` completo para exemplos e superfícies independentes.
|
|
17
23
|
Não envolva a Presentation em um card para simular a página; valide proporção, rolagem e ações no
|
|
18
24
|
shell que efetivamente hospeda a rota.
|
|
@@ -35,6 +41,7 @@ const workspacePresentation = definePresentation({
|
|
|
35
41
|
});
|
|
36
42
|
|
|
37
43
|
function Example() {
|
|
44
|
+
const [open, setOpen] = React.useState(true);
|
|
38
45
|
const [invocation, setInvocation] = React.useState(() =>
|
|
39
46
|
definePresentationInvocation({
|
|
40
47
|
schemaVersion: 1,
|
|
@@ -52,7 +59,10 @@ function Example() {
|
|
|
52
59
|
key={value}
|
|
53
60
|
size="sm"
|
|
54
61
|
variant={invocation.surface === value ? "solid" : "outline"}
|
|
55
|
-
onClick={() =>
|
|
62
|
+
onClick={() => {
|
|
63
|
+
setInvocation({ ...invocation, surface: value });
|
|
64
|
+
setOpen(true);
|
|
65
|
+
}}
|
|
56
66
|
>
|
|
57
67
|
{value}
|
|
58
68
|
</Button>
|
|
@@ -64,7 +74,12 @@ function Example() {
|
|
|
64
74
|
definitions={[workspacePresentation]}
|
|
65
75
|
actions={{ [workspaceUpdate.name]: workspaceUpdate }}
|
|
66
76
|
invocation={invocation}
|
|
67
|
-
|
|
77
|
+
open={invocation.surface === "page" || open}
|
|
78
|
+
onOpenChange={setOpen}
|
|
79
|
+
onInvocationChange={(next) => {
|
|
80
|
+
if (next) setInvocation(next);
|
|
81
|
+
else setOpen(false);
|
|
82
|
+
}}
|
|
68
83
|
/>
|
|
69
84
|
</div>
|
|
70
85
|
);
|