@softize/opus 22.2.0 → 24.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 +120 -1
- package/bin/cli.mjs +3 -9
- package/bin/lib/check.mjs +242 -49
- package/bin/lib/copy.mjs +17 -35
- 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/0023-surfaces-share-one-anatomy-and-two-layouts.md +6 -0
- 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 +59 -0
- package/docs/adr/0042-toggle-group-shares-the-grouped-control-grammar.md +79 -0
- package/docs/adr/0043-neutral-surfaces-and-interactions-use-opaque-role-tokens.md +73 -0
- package/docs/adr/0044-neutral-appearance-derives-from-bounded-axes-per-mode.md +133 -0
- package/docs/adr/0045-surface-replaces-content-and-card.md +230 -0
- package/docs/adr/0046-structural-body-owns-content-rhythm.md +103 -0
- package/docs/adr/0047-pane-is-only-a-split-layout-unit.md +71 -0
- package/docs/adr/0048-modal-intro-separates-content-from-chrome.md +34 -0
- package/docs/code-style.md +1 -1
- package/package.json +5 -1
- package/registry/skills/build-opus-ui/SKILL.md +11 -11
- package/registry/skills/build-opus-ui/references/evaluations.md +10 -9
- package/registry/skills/build-opus-ui/references/ui-patterns.md +17 -16
- package/registry/skills/create-opus-action/references/contract-and-binding.md +3 -3
- package/registry/templates/app/src/App.tsx +24 -12
- 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/appearance.ts +149 -0
- package/src/ui/components/patterns/action-form-card.tsx +46 -33
- package/src/ui/components/patterns/action-list-dialog.tsx +2 -4
- package/src/ui/components/patterns/confirm.tsx +3 -1
- package/src/ui/components/patterns/fill-region.ts +8 -7
- package/src/ui/components/patterns/form-dialog.tsx +1 -3
- package/src/ui/components/patterns/form.tsx +8 -7
- package/src/ui/components/patterns/list.tsx +64 -14
- package/src/ui/components/patterns/page.tsx +15 -11
- package/src/ui/components/patterns/pane.tsx +9 -240
- package/src/ui/components/patterns/presentation.tsx +14 -14
- package/src/ui/components/patterns/sidebar.tsx +7 -17
- package/src/ui/components/patterns/split.tsx +111 -59
- package/src/ui/components/patterns/{surface-header.tsx → structural-header.tsx} +50 -19
- package/src/ui/components/patterns/structural-media.tsx +26 -0
- package/src/ui/components/patterns/surface-anatomy.ts +71 -61
- package/src/ui/components/patterns/surface-assistant.tsx +2 -4
- package/src/ui/components/patterns/surface.tsx +620 -0
- package/src/ui/components/patterns/trigger.tsx +11 -9
- package/src/ui/components/primitives/ask.tsx +155 -83
- package/src/ui/components/primitives/button.tsx +11 -9
- package/src/ui/components/primitives/checkbox.tsx +8 -8
- package/src/ui/components/primitives/command.tsx +1 -1
- package/src/ui/components/primitives/composer.tsx +47 -45
- package/src/ui/components/primitives/detail.tsx +9 -2
- package/src/ui/components/primitives/dialog.tsx +60 -26
- package/src/ui/components/primitives/drawer.tsx +58 -12
- package/src/ui/components/primitives/icon-picker.tsx +93 -70
- package/src/ui/components/primitives/input-group.tsx +35 -28
- package/src/ui/components/primitives/input-otp.tsx +16 -16
- package/src/ui/components/primitives/input.tsx +20 -10
- package/src/ui/components/primitives/item.tsx +9 -12
- package/src/ui/components/primitives/menu.tsx +44 -65
- package/src/ui/components/primitives/metric-card.tsx +75 -59
- package/src/ui/components/primitives/radio-group.tsx +9 -9
- package/src/ui/components/primitives/select.tsx +344 -209
- package/src/ui/components/primitives/switch.tsx +10 -10
- package/src/ui/components/primitives/table.tsx +29 -24
- package/src/ui/components/primitives/tabs.tsx +58 -88
- package/src/ui/components/primitives/textarea.tsx +6 -6
- package/src/ui/components/primitives/toggle-group-context.ts +24 -0
- package/src/ui/components/primitives/toggle-group.tsx +116 -74
- package/src/ui/components/primitives/toggle.tsx +82 -16
- package/src/ui/docs/DocBrowser.tsx +77 -69
- package/src/ui/docs/content/action-form-card.md +9 -9
- package/src/ui/docs/content/action-list.md +85 -70
- package/src/ui/docs/content/action-view.md +33 -18
- package/src/ui/docs/content/actions.md +6 -7
- package/src/ui/docs/content/button.md +1 -1
- package/src/ui/docs/content/communication.md +1 -1
- package/src/ui/docs/content/customization.md +64 -11
- package/src/ui/docs/content/detail.md +8 -6
- package/src/ui/docs/content/dialog.md +100 -58
- package/src/ui/docs/content/drawer.md +63 -19
- package/src/ui/docs/content/field.md +14 -15
- package/src/ui/docs/content/icon-tile.md +14 -13
- package/src/ui/docs/content/page.md +34 -26
- package/src/ui/docs/content/pane.md +41 -290
- package/src/ui/docs/content/presentation.md +3 -3
- package/src/ui/docs/content/semantic-context.md +0 -1
- package/src/ui/docs/content/separator.md +6 -6
- package/src/ui/docs/content/sidebar.md +68 -266
- package/src/ui/docs/content/skeleton.md +20 -18
- package/src/ui/docs/content/split.md +14 -14
- package/src/ui/docs/content/surface.md +513 -0
- package/src/ui/docs/content/tabs.md +82 -83
- package/src/ui/docs/content/testing.md +2 -3
- package/src/ui/docs/content/toggle.md +120 -63
- package/src/ui/docs/content/tokens.md +124 -40
- package/src/ui/docs/registry.tsx +4 -6
- package/src/ui/drivers/react.tsx +4 -3
- package/src/ui/index.ts +30 -27
- package/src/ui/meta.ts +15 -27
- package/src/ui/react.tsx +42 -46
- package/src/ui/theme.css +311 -64
- package/src/vite/design.ts +23 -21
- package/src/ui/components/patterns/content-header.tsx +0 -532
- package/src/ui/components/patterns/surface-media.tsx +0 -69
- package/src/ui/components/primitives/card.tsx +0 -191
- package/src/ui/docs/content/card.md +0 -292
- package/src/ui/docs/content/content.md +0 -276
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* <ActionFormCard /> — o ActionForm dentro de
|
|
3
|
-
* pra estruturar uma seção da página como painel. Segue o
|
|
4
|
-
* modal): sem divisor nem faixa
|
|
5
|
-
*
|
|
2
|
+
* <ActionFormCard /> — o ActionForm dentro de uma Surface elevada (header / conteúdo /
|
|
3
|
+
* rodapé), pra estruturar uma seção da página como painel. Segue o layout contínuo (não a
|
|
4
|
+
* chrome de modal): sem divisor nem faixa. Pra form em overlay, use o ActionFormDialog; pra
|
|
5
|
+
* form cru (sem chrome), o ActionForm direto.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
8
|
-
Card,
|
|
9
|
-
CardBody,
|
|
10
|
-
CardDescription,
|
|
11
|
-
CardFooter,
|
|
12
|
-
CardHeader,
|
|
13
|
-
CardTitle,
|
|
14
|
-
} from "../primitives/card.tsx";
|
|
15
7
|
import { ActionForm, type ActionFormProps } from "./form.tsx";
|
|
16
8
|
import type { HeadingLevel } from "./heading-level.ts";
|
|
9
|
+
import {
|
|
10
|
+
Surface,
|
|
11
|
+
SurfaceBody,
|
|
12
|
+
SurfaceDescription,
|
|
13
|
+
SurfaceFooter,
|
|
14
|
+
SurfaceHeader,
|
|
15
|
+
SurfaceTitle,
|
|
16
|
+
} from "./surface.tsx";
|
|
17
17
|
import { ButtonGroup } from "../primitives/button-group.tsx";
|
|
18
18
|
|
|
19
19
|
export interface ActionFormCardProps<
|
|
@@ -23,49 +23,62 @@ export interface ActionFormCardProps<
|
|
|
23
23
|
/** Título do header do card. Sem ele, o card começa direto no conteúdo. */
|
|
24
24
|
title?: string;
|
|
25
25
|
/**
|
|
26
|
-
* Nível semântico do título (`h1`–`h6`), repassado
|
|
27
|
-
*
|
|
28
|
-
* a do Card.
|
|
26
|
+
* Nível semântico do título (`h1`–`h6`), repassado à `Surface` como `level`. Sem ele
|
|
27
|
+
* vale `h3`: o card costuma ser uma seção dentro de uma página que já tem `h1` e `h2`.
|
|
29
28
|
*/
|
|
30
29
|
titleLevel?: HeadingLevel;
|
|
31
|
-
/** Subtítulo opcional, abaixo do título. */
|
|
30
|
+
/** Subtítulo opcional, abaixo do título. Exige `title`. */
|
|
32
31
|
description?: string;
|
|
33
|
-
/** Classes
|
|
32
|
+
/** Classes da superfície. `className` vai pro <form>. */
|
|
34
33
|
cardClassName?: string;
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
export function ActionFormCard<TInput extends Record<string, unknown>, TData>({
|
|
38
37
|
title,
|
|
39
|
-
titleLevel,
|
|
38
|
+
titleLevel = 3,
|
|
40
39
|
description,
|
|
41
40
|
cardClassName,
|
|
42
41
|
...rest
|
|
43
42
|
}: ActionFormCardProps<TInput, TData>) {
|
|
43
|
+
if (description !== undefined && title === undefined) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
"ActionFormCard exige title quando description é informada.",
|
|
46
|
+
);
|
|
47
|
+
}
|
|
44
48
|
return (
|
|
45
|
-
<
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
)}
|
|
54
|
-
</CardHeader>
|
|
55
|
-
)}
|
|
49
|
+
<Surface
|
|
50
|
+
framed
|
|
51
|
+
elevation="raised"
|
|
52
|
+
level={titleLevel}
|
|
53
|
+
className={cardClassName}
|
|
54
|
+
>
|
|
55
|
+
{/* As três regiões são irmãs dentro do <form>: o ritmo e o respiro de cada uma
|
|
56
|
+
derivam da vizinhança, e os botões precisam ficar dentro do form. */}
|
|
56
57
|
<ActionForm
|
|
57
58
|
{...rest}
|
|
58
|
-
body={(fields) =>
|
|
59
|
+
body={(fields) => (
|
|
60
|
+
<>
|
|
61
|
+
{title !== undefined && (
|
|
62
|
+
<SurfaceHeader>
|
|
63
|
+
<SurfaceTitle>{title}</SurfaceTitle>
|
|
64
|
+
{description !== undefined && (
|
|
65
|
+
<SurfaceDescription>{description}</SurfaceDescription>
|
|
66
|
+
)}
|
|
67
|
+
</SurfaceHeader>
|
|
68
|
+
)}
|
|
69
|
+
<SurfaceBody>{fields}</SurfaceBody>
|
|
70
|
+
</>
|
|
71
|
+
)}
|
|
59
72
|
footer={(actions) => (
|
|
60
|
-
<
|
|
73
|
+
<SurfaceFooter>
|
|
61
74
|
{rest.onCancel === undefined ? (
|
|
62
75
|
actions
|
|
63
76
|
) : (
|
|
64
77
|
<ButtonGroup mode="spaced">{actions}</ButtonGroup>
|
|
65
78
|
)}
|
|
66
|
-
</
|
|
79
|
+
</SurfaceFooter>
|
|
67
80
|
)}
|
|
68
81
|
/>
|
|
69
|
-
</
|
|
82
|
+
</Surface>
|
|
70
83
|
);
|
|
71
84
|
}
|
|
@@ -86,14 +86,12 @@ export function ActionListDialog<
|
|
|
86
86
|
</DialogHeader>
|
|
87
87
|
<DialogBody>
|
|
88
88
|
{intro !== undefined && (
|
|
89
|
-
<div id={introId}
|
|
90
|
-
{intro}
|
|
91
|
-
</div>
|
|
89
|
+
<div id={introId}>{intro}</div>
|
|
92
90
|
)}
|
|
93
91
|
{(note !== undefined || actions !== undefined) && (
|
|
94
92
|
<div
|
|
95
93
|
data-slot="action-list-dialog-toolbar"
|
|
96
|
-
className="
|
|
94
|
+
className="flex min-h-8 items-center justify-between gap-4"
|
|
97
95
|
>
|
|
98
96
|
<div className="min-w-0 text-xs text-muted-foreground/70">{note}</div>
|
|
99
97
|
{actions}
|
|
@@ -284,7 +284,9 @@ export function DialogHost(): React.ReactElement {
|
|
|
284
284
|
<DialogTitle>{current.title}</DialogTitle>
|
|
285
285
|
</DialogHeader>
|
|
286
286
|
{current.body !== undefined && (
|
|
287
|
-
<DialogBody id={bodyId}>
|
|
287
|
+
<DialogBody id={bodyId}>
|
|
288
|
+
<div>{current.body}</div>
|
|
289
|
+
</DialogBody>
|
|
288
290
|
)}
|
|
289
291
|
{current.kind === "prompt" && (
|
|
290
292
|
<Input
|
|
@@ -7,15 +7,16 @@
|
|
|
7
7
|
* coluna flexível. Este módulo faz essa cadeia se formar sem classes no consumidor:
|
|
8
8
|
*
|
|
9
9
|
* - a raiz (`FillRoot`) publica o contexto;
|
|
10
|
-
* - os elos canônicos (`PageBody`, `
|
|
11
|
-
* leem o contexto com `useFillRegion` e o republicam para os filhos;
|
|
10
|
+
* - os elos canônicos (`PageBody`, `Surface` sem elevação, `SurfaceBody`, o corpo da
|
|
11
|
+
* `Presentation`) leem o contexto com `useFillRegion` e o republicam para os filhos;
|
|
12
12
|
* - a folha (`ActionList`) usa `useFillLeaf`: assume `fill` por padrão quando o elemento
|
|
13
13
|
* pai é um elo e se registra na cadeia;
|
|
14
|
-
* -
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
14
|
+
* - os bodies estruturais já são colunas para governar o ritmo; quando uma folha se registra,
|
|
15
|
+
* os elos também passam a ocupar a folga disponível. Irmãos sem folha mantêm a altura do
|
|
16
|
+
* próprio conteúdo, e uma página `fill` sem lista não muda;
|
|
17
|
+
* - superfícies com altura própria (`Surface elevation="raised"`, Dialog, Drawer, Pane)
|
|
18
|
+
* cortam a cadeia com `FillBoundary`: a altura delas é decisão delas, inclusive quando um
|
|
19
|
+
* portal as renderiza dentro de uma página `fill`.
|
|
19
20
|
*
|
|
20
21
|
* O registro acontece em `useLayoutEffect`, então as classes chegam antes da primeira pintura.
|
|
21
22
|
*/
|
|
@@ -49,7 +49,8 @@ import {
|
|
|
49
49
|
} from "../primitives/tooltip.tsx";
|
|
50
50
|
import { Info } from "lucide-react";
|
|
51
51
|
import { Select, type SelectOption } from "../primitives/select.tsx";
|
|
52
|
-
import {
|
|
52
|
+
import { Toggle } from "../primitives/toggle.tsx";
|
|
53
|
+
import { ToggleGroup } from "../primitives/toggle-group.tsx";
|
|
53
54
|
import {
|
|
54
55
|
Field,
|
|
55
56
|
FieldError,
|
|
@@ -464,14 +465,14 @@ export function ActionFormField({
|
|
|
464
465
|
value={(formValues[name] as string[] | undefined) ?? []}
|
|
465
466
|
onValueChange={(value) => setValue(value)}
|
|
466
467
|
variant="outline"
|
|
467
|
-
|
|
468
|
+
mode="spaced"
|
|
468
469
|
aria-labelledby={`${name}-label`}
|
|
469
470
|
aria-invalid={errorMessage !== undefined}
|
|
470
471
|
aria-describedby={describedBy}
|
|
471
472
|
className="grid w-full grid-cols-[repeat(auto-fit,minmax(min(100%,12rem),1fr))] items-stretch gap-2"
|
|
472
473
|
>
|
|
473
474
|
{options.map((option) => (
|
|
474
|
-
<
|
|
475
|
+
<Toggle
|
|
475
476
|
key={option.value}
|
|
476
477
|
value={option.value}
|
|
477
478
|
disabled={option.disabled}
|
|
@@ -488,7 +489,7 @@ export function ActionFormField({
|
|
|
488
489
|
</span>
|
|
489
490
|
)}
|
|
490
491
|
</span>
|
|
491
|
-
</
|
|
492
|
+
</Toggle>
|
|
492
493
|
))}
|
|
493
494
|
</ToggleGroup>
|
|
494
495
|
) : (
|
|
@@ -502,7 +503,7 @@ export function ActionFormField({
|
|
|
502
503
|
}
|
|
503
504
|
}}
|
|
504
505
|
variant="outline"
|
|
505
|
-
|
|
506
|
+
mode="spaced"
|
|
506
507
|
aria-labelledby={`${name}-label`}
|
|
507
508
|
aria-required={effectiveKind.required}
|
|
508
509
|
aria-invalid={errorMessage !== undefined}
|
|
@@ -510,7 +511,7 @@ export function ActionFormField({
|
|
|
510
511
|
className="grid w-full grid-cols-[repeat(auto-fit,minmax(min(100%,12rem),1fr))] items-stretch gap-2"
|
|
511
512
|
>
|
|
512
513
|
{options.map((option) => (
|
|
513
|
-
<
|
|
514
|
+
<Toggle
|
|
514
515
|
key={option.value}
|
|
515
516
|
value={option.value}
|
|
516
517
|
disabled={option.disabled}
|
|
@@ -527,7 +528,7 @@ export function ActionFormField({
|
|
|
527
528
|
</span>
|
|
528
529
|
)}
|
|
529
530
|
</span>
|
|
530
|
-
</
|
|
531
|
+
</Toggle>
|
|
531
532
|
))}
|
|
532
533
|
</ToggleGroup>
|
|
533
534
|
)
|
|
@@ -111,6 +111,7 @@ import {
|
|
|
111
111
|
InputGroupButton,
|
|
112
112
|
InputGroupInput,
|
|
113
113
|
} from "../primitives/input-group.tsx";
|
|
114
|
+
import { iconPickerIcons } from "../primitives/icon-picker.tsx";
|
|
114
115
|
import { Separator } from "../primitives/separator.tsx";
|
|
115
116
|
import {
|
|
116
117
|
Field,
|
|
@@ -118,7 +119,7 @@ import {
|
|
|
118
119
|
FieldLabel,
|
|
119
120
|
type FieldSetVariant,
|
|
120
121
|
} from "../primitives/field.tsx";
|
|
121
|
-
import {
|
|
122
|
+
import { Surface } from "./surface.tsx";
|
|
122
123
|
import { Search } from "lucide-react";
|
|
123
124
|
import { Select, type SelectOption } from "../primitives/select.tsx";
|
|
124
125
|
import {
|
|
@@ -129,7 +130,8 @@ import {
|
|
|
129
130
|
TableHeader,
|
|
130
131
|
TableRow,
|
|
131
132
|
} from "../primitives/table.tsx";
|
|
132
|
-
import {
|
|
133
|
+
import { Toggle } from "../primitives/toggle.tsx";
|
|
134
|
+
import { ToggleGroup } from "../primitives/toggle-group.tsx";
|
|
133
135
|
import {
|
|
134
136
|
Tooltip,
|
|
135
137
|
TooltipContent,
|
|
@@ -347,7 +349,7 @@ export interface ActionListProps<TInput, TItem> {
|
|
|
347
349
|
/**
|
|
348
350
|
* `fill` ocupa a altura disponível e mantém a rolagem dentro da região de resultados.
|
|
349
351
|
* Sem valor, vale `fill` quando o elemento pai repassa a altura de uma `Page` ou
|
|
350
|
-
* `Presentation` em `fill` — `PageBody`, `
|
|
352
|
+
* `Presentation` em `fill` — `PageBody`, `SurfaceBody` ou o corpo da Presentation — e
|
|
351
353
|
* `flow` nos demais casos (ADR 0024).
|
|
352
354
|
*/
|
|
353
355
|
layout?: "flow" | "fill";
|
|
@@ -479,7 +481,23 @@ function dictOptionsFor(
|
|
|
479
481
|
if (spec.options?.kind !== "dictionary") return [];
|
|
480
482
|
const dict = dicts[spec.options.ref];
|
|
481
483
|
if (dict === undefined) return [];
|
|
482
|
-
return dict.options().map((o) =>
|
|
484
|
+
return dict.options().map((o) => {
|
|
485
|
+
const Icon = o.icon === undefined ? undefined : iconPickerIcons[o.icon];
|
|
486
|
+
return {
|
|
487
|
+
value: o.value,
|
|
488
|
+
label: o.label,
|
|
489
|
+
...(Icon === undefined
|
|
490
|
+
? {}
|
|
491
|
+
: {
|
|
492
|
+
content: (
|
|
493
|
+
<>
|
|
494
|
+
<Icon aria-hidden="true" />
|
|
495
|
+
<span>{o.label}</span>
|
|
496
|
+
</>
|
|
497
|
+
),
|
|
498
|
+
}),
|
|
499
|
+
};
|
|
500
|
+
});
|
|
483
501
|
}
|
|
484
502
|
|
|
485
503
|
/** Opções vindas de uma action de lookup (`options.kind: 'lookup'`): o Select chama
|
|
@@ -571,6 +589,41 @@ function FilterField({
|
|
|
571
589
|
const changedFromDefault = singleValue !== defaultValue;
|
|
572
590
|
const acceptsEmptyValue = spec.allowEmpty !== false;
|
|
573
591
|
|
|
592
|
+
if (
|
|
593
|
+
spec.type === "select" &&
|
|
594
|
+
spec.multiple !== true &&
|
|
595
|
+
spec.presentation === "toggle"
|
|
596
|
+
) {
|
|
597
|
+
const ALL = "__all__";
|
|
598
|
+
const toggleValue = singleValue === "" ? ALL : singleValue;
|
|
599
|
+
return (
|
|
600
|
+
<ToggleGroup
|
|
601
|
+
type="single"
|
|
602
|
+
mode="segmented"
|
|
603
|
+
id={`filter-${name}`}
|
|
604
|
+
value={toggleValue}
|
|
605
|
+
onValueChange={(next) => {
|
|
606
|
+
// O grupo sempre preserva uma escolha ativa. Quando vazio é permitido,
|
|
607
|
+
// a opção explícita representa esse estado em vez de desmarcar o item atual.
|
|
608
|
+
if (next === "") return;
|
|
609
|
+
onChange(next === ALL ? "" : next);
|
|
610
|
+
}}
|
|
611
|
+
aria-label={label}
|
|
612
|
+
disabled={!enabled}
|
|
613
|
+
className={className ?? "max-w-full"}
|
|
614
|
+
>
|
|
615
|
+
{acceptsEmptyValue && (
|
|
616
|
+
<Toggle value={ALL}>{text(spec.placeholder, "Todos")}</Toggle>
|
|
617
|
+
)}
|
|
618
|
+
{base.map((option) => (
|
|
619
|
+
<Toggle key={option.value} value={option.value}>
|
|
620
|
+
{option.content ?? option.label}
|
|
621
|
+
</Toggle>
|
|
622
|
+
))}
|
|
623
|
+
</ToggleGroup>
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
|
|
574
627
|
if (spec.multiple === true) {
|
|
575
628
|
return (
|
|
576
629
|
<Select
|
|
@@ -993,7 +1046,7 @@ function PeriodControl({
|
|
|
993
1046
|
variant={embedded ? "ghost" : "outline"}
|
|
994
1047
|
data-slot="action-list-period"
|
|
995
1048
|
className={cn(
|
|
996
|
-
"relative max-w-full gap-1.5 overflow-hidden bg-
|
|
1049
|
+
"relative max-w-full gap-1.5 overflow-hidden bg-control px-3 hover:bg-interaction-hover hover:text-foreground",
|
|
997
1050
|
// O X e o chevron ocupam posições explícitas na ponta do controle. O
|
|
998
1051
|
// padding reserva os dois sem depender da largura do texto selecionado.
|
|
999
1052
|
clearable && "pr-16",
|
|
@@ -1217,23 +1270,20 @@ function AdvancedFiltersDialog({
|
|
|
1217
1270
|
<DialogHeader>
|
|
1218
1271
|
<DialogTitle>Filtros</DialogTitle>
|
|
1219
1272
|
</DialogHeader>
|
|
1220
|
-
<DialogBody
|
|
1273
|
+
<DialogBody>
|
|
1221
1274
|
{sectioned ? (
|
|
1222
1275
|
sections.map(([section, entries]) => (
|
|
1223
|
-
<
|
|
1276
|
+
<Surface
|
|
1224
1277
|
key={section}
|
|
1225
1278
|
as="fieldset"
|
|
1226
1279
|
framed={sectionVariant === "framed"}
|
|
1227
|
-
frameVariant={
|
|
1228
|
-
sectionVariant === "framed" ? "legend" : "default"
|
|
1229
|
-
}
|
|
1230
1280
|
title={section}
|
|
1231
1281
|
media={sectionIcons?.[section]}
|
|
1232
1282
|
>
|
|
1233
1283
|
<FieldGroup className={cn("grid gap-4", gridColumns)}>
|
|
1234
1284
|
{entries.map(field)}
|
|
1235
1285
|
</FieldGroup>
|
|
1236
|
-
</
|
|
1286
|
+
</Surface>
|
|
1237
1287
|
))
|
|
1238
1288
|
) : (
|
|
1239
1289
|
<FieldGroup className={cn("grid gap-4", gridColumns)}>
|
|
@@ -1431,7 +1481,7 @@ export function ActionFilterBar({
|
|
|
1431
1481
|
<div
|
|
1432
1482
|
data-slot="action-filter-period-group"
|
|
1433
1483
|
className={cn(
|
|
1434
|
-
"flex min-h-9 w-fit min-w-0 max-w-full items-center rounded-md border border-input bg-
|
|
1484
|
+
"flex min-h-9 w-fit min-w-0 max-w-full items-center rounded-md border border-input bg-control text-foreground transition-[color,box-shadow] focus-within:border-ring focus-within:ring-[0.1875rem] focus-within:ring-ring/50 has-[[data-state=open]]:border-ring has-[[data-state=open]]:ring-[0.1875rem] has-[[data-state=open]]:ring-ring/50",
|
|
1435
1485
|
!periodGroupClearable && "pr-3",
|
|
1436
1486
|
)}
|
|
1437
1487
|
>
|
|
@@ -1981,9 +2031,9 @@ export function ActionList<TInput, TItem>({
|
|
|
1981
2031
|
{viewEntries.map(([key, label, icon]) => (
|
|
1982
2032
|
<Tooltip key={key}>
|
|
1983
2033
|
<TooltipTrigger asChild>
|
|
1984
|
-
<
|
|
2034
|
+
<Toggle value={key} aria-label={label}>
|
|
1985
2035
|
{icon ?? label}
|
|
1986
|
-
</
|
|
2036
|
+
</Toggle>
|
|
1987
2037
|
</TooltipTrigger>
|
|
1988
2038
|
<TooltipContent>{label}</TooltipContent>
|
|
1989
2039
|
</Tooltip>
|
|
@@ -24,12 +24,15 @@ import { createPortal } from "react-dom";
|
|
|
24
24
|
import { ArrowLeft } from "lucide-react";
|
|
25
25
|
import { cn } from "../../lib/cn.ts";
|
|
26
26
|
import { Button } from "../primitives/button.tsx";
|
|
27
|
-
import {
|
|
27
|
+
import { Surface } from "./surface.tsx";
|
|
28
28
|
import {
|
|
29
29
|
PageHeadingPresenceProvider,
|
|
30
30
|
usePageHeadingPresence,
|
|
31
31
|
} from "./page-heading-context.tsx";
|
|
32
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
StructuralHeader,
|
|
34
|
+
structuralHeaderClasses,
|
|
35
|
+
} from "./structural-header.tsx";
|
|
33
36
|
import { PageState, PageStatePresenceProvider } from "./page-state.tsx";
|
|
34
37
|
import { FillRoot, fillRegionClassName, useFillRegion } from "./fill-region.ts";
|
|
35
38
|
|
|
@@ -137,7 +140,7 @@ interface PageBaseProps extends Omit<
|
|
|
137
140
|
className?: string;
|
|
138
141
|
/**
|
|
139
142
|
* `fill` limita a página à altura do shell para que regiões internas assumam a rolagem.
|
|
140
|
-
* A altura chega sozinha a uma `ActionList` composta por `PageBody` e `
|
|
143
|
+
* A altura chega sozinha a uma `ActionList` composta por `PageBody` e `Surface` (ADR 0024).
|
|
141
144
|
*/
|
|
142
145
|
layout?: "flow" | "fill";
|
|
143
146
|
}
|
|
@@ -198,7 +201,7 @@ function countPageBodyHeadings(node: ReactNode): {
|
|
|
198
201
|
return {
|
|
199
202
|
content:
|
|
200
203
|
total.content +
|
|
201
|
-
(child.type ===
|
|
204
|
+
(child.type === Surface && props.level === 1 ? 1 : 0) +
|
|
202
205
|
nested.content,
|
|
203
206
|
state: total.state + nested.state,
|
|
204
207
|
};
|
|
@@ -211,7 +214,7 @@ function hasOpaquePageBodyContent(node: ReactNode): boolean {
|
|
|
211
214
|
return Children.toArray(node).some((child) => {
|
|
212
215
|
if (!isValidElement(child)) return false;
|
|
213
216
|
if (typeof child.type !== "string") {
|
|
214
|
-
return child.type !==
|
|
217
|
+
return child.type !== Surface && child.type !== PageState;
|
|
215
218
|
}
|
|
216
219
|
return hasOpaquePageBodyContent(
|
|
217
220
|
(child.props as { children?: ReactNode }).children,
|
|
@@ -323,7 +326,7 @@ export function Page({
|
|
|
323
326
|
);
|
|
324
327
|
if (headingSources > 1 || (headingSources === 0 && !hasOpaqueBodyContent)) {
|
|
325
328
|
throw new Error(
|
|
326
|
-
"Page explícito exige exatamente um heading principal em PageHeader,
|
|
329
|
+
"Page explícito exige exatamente um heading principal em PageHeader, Surface level={1} ou PageState ativo dentro de PageBody.",
|
|
327
330
|
);
|
|
328
331
|
}
|
|
329
332
|
}
|
|
@@ -332,7 +335,7 @@ export function Page({
|
|
|
332
335
|
if (integralState !== hasActiveIntegralState) return;
|
|
333
336
|
if (headingCount.current !== 1) {
|
|
334
337
|
throw new Error(
|
|
335
|
-
"Page explícito exige exatamente um heading principal em PageHeader,
|
|
338
|
+
"Page explícito exige exatamente um heading principal em PageHeader, Surface level={1} ou PageState ativo dentro de PageBody.",
|
|
336
339
|
);
|
|
337
340
|
}
|
|
338
341
|
});
|
|
@@ -452,7 +455,7 @@ export function PageHeader({
|
|
|
452
455
|
}
|
|
453
456
|
return (
|
|
454
457
|
<PageHeaderContext.Provider value>
|
|
455
|
-
<
|
|
458
|
+
<StructuralHeader
|
|
456
459
|
name="PageHeader"
|
|
457
460
|
slot="page"
|
|
458
461
|
slots={{
|
|
@@ -472,7 +475,7 @@ export function PageHeader({
|
|
|
472
475
|
)}
|
|
473
476
|
{structuralChildren}
|
|
474
477
|
{mergedActions}
|
|
475
|
-
</
|
|
478
|
+
</StructuralHeader>
|
|
476
479
|
</PageHeaderContext.Provider>
|
|
477
480
|
);
|
|
478
481
|
}
|
|
@@ -487,7 +490,7 @@ export function PageTitle({
|
|
|
487
490
|
return (
|
|
488
491
|
<h1
|
|
489
492
|
data-slot="page-title"
|
|
490
|
-
className={cn(
|
|
493
|
+
className={cn(structuralHeaderClasses.page.title, className)}
|
|
491
494
|
{...props}
|
|
492
495
|
/>
|
|
493
496
|
);
|
|
@@ -503,7 +506,7 @@ export function PageActions({
|
|
|
503
506
|
const actions = (
|
|
504
507
|
<div
|
|
505
508
|
data-slot="page-actions"
|
|
506
|
-
className={cn(
|
|
509
|
+
className={cn(structuralHeaderClasses.page.actions, className)}
|
|
507
510
|
{...props}
|
|
508
511
|
/>
|
|
509
512
|
);
|
|
@@ -579,6 +582,7 @@ export function PageBody({
|
|
|
579
582
|
data-slot="page-body"
|
|
580
583
|
{...fill.attributes}
|
|
581
584
|
className={cn(
|
|
585
|
+
"flex flex-col gap-6 *:shrink-0",
|
|
582
586
|
(integralState || fill.filling) && fillRegionClassName,
|
|
583
587
|
className,
|
|
584
588
|
)}
|