@softize/opus 23.0.0 → 24.0.1
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 +99 -0
- package/bin/cli.mjs +2 -0
- package/bin/lib/check.mjs +241 -42
- package/bin/lib/copy.mjs +16 -34
- package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +6 -0
- package/docs/adr/0039-filter-presentation-is-independent-from-filter-type.md +3 -3
- package/docs/adr/0040-tabs-use-a-single-navigation-presentation.md +5 -6
- 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/adr/0049-inline-navigation-groups-with-the-heading.md +95 -0
- 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/templates/app/src/App.tsx +24 -12
- 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 +15 -19
- 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} +90 -27
- 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 +90 -73
- 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 +4 -2
- 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 +76 -70
- package/src/ui/docs/content/action-view.md +33 -18
- package/src/ui/docs/content/button.md +1 -1
- package/src/ui/docs/content/communication.md +1 -1
- package/src/ui/docs/content/compositions.md +479 -0
- 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 +105 -60
- package/src/ui/docs/content/drawer.md +68 -22
- 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 +44 -34
- package/src/ui/docs/content/pane.md +42 -290
- package/src/ui/docs/content/presentation.md +3 -3
- 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 +20 -20
- package/src/ui/docs/content/toggle.md +117 -65
- package/src/ui/docs/content/tokens.md +124 -40
- package/src/ui/docs/registry.tsx +10 -6
- package/src/ui/index.ts +30 -27
- package/src/ui/meta.ts +15 -27
- package/src/ui/react.tsx +40 -43
- package/src/ui/theme.css +311 -64
- package/src/ui/components/patterns/content-header.tsx +0 -528
- 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 -275
|
@@ -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
|
)
|
|
@@ -119,7 +119,7 @@ import {
|
|
|
119
119
|
FieldLabel,
|
|
120
120
|
type FieldSetVariant,
|
|
121
121
|
} from "../primitives/field.tsx";
|
|
122
|
-
import {
|
|
122
|
+
import { Surface } from "./surface.tsx";
|
|
123
123
|
import { Search } from "lucide-react";
|
|
124
124
|
import { Select, type SelectOption } from "../primitives/select.tsx";
|
|
125
125
|
import {
|
|
@@ -130,7 +130,8 @@ import {
|
|
|
130
130
|
TableHeader,
|
|
131
131
|
TableRow,
|
|
132
132
|
} from "../primitives/table.tsx";
|
|
133
|
-
import {
|
|
133
|
+
import { Toggle } from "../primitives/toggle.tsx";
|
|
134
|
+
import { ToggleGroup } from "../primitives/toggle-group.tsx";
|
|
134
135
|
import {
|
|
135
136
|
Tooltip,
|
|
136
137
|
TooltipContent,
|
|
@@ -348,7 +349,7 @@ export interface ActionListProps<TInput, TItem> {
|
|
|
348
349
|
/**
|
|
349
350
|
* `fill` ocupa a altura disponível e mantém a rolagem dentro da região de resultados.
|
|
350
351
|
* Sem valor, vale `fill` quando o elemento pai repassa a altura de uma `Page` ou
|
|
351
|
-
* `Presentation` em `fill` — `PageBody`, `
|
|
352
|
+
* `Presentation` em `fill` — `PageBody`, `SurfaceBody` ou o corpo da Presentation — e
|
|
352
353
|
* `flow` nos demais casos (ADR 0024).
|
|
353
354
|
*/
|
|
354
355
|
layout?: "flow" | "fill";
|
|
@@ -598,7 +599,7 @@ function FilterField({
|
|
|
598
599
|
return (
|
|
599
600
|
<ToggleGroup
|
|
600
601
|
type="single"
|
|
601
|
-
|
|
602
|
+
mode="segmented"
|
|
602
603
|
id={`filter-${name}`}
|
|
603
604
|
value={toggleValue}
|
|
604
605
|
onValueChange={(next) => {
|
|
@@ -612,14 +613,12 @@ function FilterField({
|
|
|
612
613
|
className={className ?? "max-w-full"}
|
|
613
614
|
>
|
|
614
615
|
{acceptsEmptyValue && (
|
|
615
|
-
<
|
|
616
|
-
{text(spec.placeholder, "Todos")}
|
|
617
|
-
</ToggleGroupItem>
|
|
616
|
+
<Toggle value={ALL}>{text(spec.placeholder, "Todos")}</Toggle>
|
|
618
617
|
)}
|
|
619
618
|
{base.map((option) => (
|
|
620
|
-
<
|
|
619
|
+
<Toggle key={option.value} value={option.value}>
|
|
621
620
|
{option.content ?? option.label}
|
|
622
|
-
</
|
|
621
|
+
</Toggle>
|
|
623
622
|
))}
|
|
624
623
|
</ToggleGroup>
|
|
625
624
|
);
|
|
@@ -1047,7 +1046,7 @@ function PeriodControl({
|
|
|
1047
1046
|
variant={embedded ? "ghost" : "outline"}
|
|
1048
1047
|
data-slot="action-list-period"
|
|
1049
1048
|
className={cn(
|
|
1050
|
-
"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",
|
|
1051
1050
|
// O X e o chevron ocupam posições explícitas na ponta do controle. O
|
|
1052
1051
|
// padding reserva os dois sem depender da largura do texto selecionado.
|
|
1053
1052
|
clearable && "pr-16",
|
|
@@ -1271,23 +1270,20 @@ function AdvancedFiltersDialog({
|
|
|
1271
1270
|
<DialogHeader>
|
|
1272
1271
|
<DialogTitle>Filtros</DialogTitle>
|
|
1273
1272
|
</DialogHeader>
|
|
1274
|
-
<DialogBody
|
|
1273
|
+
<DialogBody>
|
|
1275
1274
|
{sectioned ? (
|
|
1276
1275
|
sections.map(([section, entries]) => (
|
|
1277
|
-
<
|
|
1276
|
+
<Surface
|
|
1278
1277
|
key={section}
|
|
1279
1278
|
as="fieldset"
|
|
1280
1279
|
framed={sectionVariant === "framed"}
|
|
1281
|
-
frameVariant={
|
|
1282
|
-
sectionVariant === "framed" ? "legend" : "default"
|
|
1283
|
-
}
|
|
1284
1280
|
title={section}
|
|
1285
1281
|
media={sectionIcons?.[section]}
|
|
1286
1282
|
>
|
|
1287
1283
|
<FieldGroup className={cn("grid gap-4", gridColumns)}>
|
|
1288
1284
|
{entries.map(field)}
|
|
1289
1285
|
</FieldGroup>
|
|
1290
|
-
</
|
|
1286
|
+
</Surface>
|
|
1291
1287
|
))
|
|
1292
1288
|
) : (
|
|
1293
1289
|
<FieldGroup className={cn("grid gap-4", gridColumns)}>
|
|
@@ -1485,7 +1481,7 @@ export function ActionFilterBar({
|
|
|
1485
1481
|
<div
|
|
1486
1482
|
data-slot="action-filter-period-group"
|
|
1487
1483
|
className={cn(
|
|
1488
|
-
"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",
|
|
1489
1485
|
!periodGroupClearable && "pr-3",
|
|
1490
1486
|
)}
|
|
1491
1487
|
>
|
|
@@ -2035,9 +2031,9 @@ export function ActionList<TInput, TItem>({
|
|
|
2035
2031
|
{viewEntries.map(([key, label, icon]) => (
|
|
2036
2032
|
<Tooltip key={key}>
|
|
2037
2033
|
<TooltipTrigger asChild>
|
|
2038
|
-
<
|
|
2034
|
+
<Toggle value={key} aria-label={label}>
|
|
2039
2035
|
{icon ?? label}
|
|
2040
|
-
</
|
|
2036
|
+
</Toggle>
|
|
2041
2037
|
</TooltipTrigger>
|
|
2042
2038
|
<TooltipContent>{label}</TooltipContent>
|
|
2043
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
|
)}
|