@softize/opus 21.0.1 → 22.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 +47 -0
- package/bin/lib/check.mjs +31 -28
- package/docs/adr/0004-page-content-state-is-composed.md +3 -2
- package/docs/adr/0005-structural-surfaces-share-an-explicit-anatomy.md +3 -0
- package/docs/adr/0006-semantic-context-precedes-visual-variant.md +23 -14
- package/docs/adr/0011-page-shell-coordinates-persistent-page-chrome.md +5 -0
- package/docs/adr/0013-presentation-is-a-portable-action-oriented-artifact.md +7 -4
- package/docs/adr/0016-list-collection-header-belongs-to-content.md +13 -8
- package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +27 -26
- package/docs/adr/0025-content-frame-treatment-does-not-change-semantics.md +1 -0
- package/docs/adr/0029-page-header-is-the-single-page-introduction.md +71 -0
- package/docs/adr/0030-framed-content-can-use-sectioned-anatomy.md +89 -0
- package/docs/adr/0031-detail-dividers-own-the-tabular-label-treatment.md +61 -0
- package/docs/adr/0032-tab-baseline-respects-the-container-inset.md +59 -0
- package/docs/adr/0033-drawer-uses-an-inset-structural-frame.md +72 -0
- package/docs/adr/0034-tabs-can-own-a-frame-without-owning-a-surface.md +60 -0
- package/docs/adr/0035-button-group-separates-frame-relationship-and-distribution.md +63 -0
- package/docs/adr/0036-tabs-declares-trigger-distribution.md +58 -0
- package/docs/radius-scale.md +5 -2
- package/package.json +1 -1
- package/registry/skills/build-opus-ui/SKILL.md +9 -6
- package/registry/skills/build-opus-ui/references/evaluations.md +5 -2
- package/registry/skills/build-opus-ui/references/ui-patterns.md +14 -16
- package/src/ui/components/patterns/content-header.tsx +51 -13
- package/src/ui/components/patterns/list.tsx +51 -33
- package/src/ui/components/patterns/page.tsx +139 -251
- package/src/ui/components/patterns/presentation.tsx +2 -6
- package/src/ui/components/primitives/button-group.tsx +80 -8
- package/src/ui/components/primitives/button.tsx +85 -60
- package/src/ui/components/primitives/detail.tsx +63 -57
- package/src/ui/components/primitives/dialog.tsx +8 -11
- package/src/ui/components/primitives/drawer.tsx +16 -12
- package/src/ui/components/primitives/tabs.tsx +123 -54
- package/src/ui/docs/content/button.md +55 -29
- package/src/ui/docs/content/content.md +61 -36
- package/src/ui/docs/content/detail.md +28 -19
- package/src/ui/docs/content/drawer.md +53 -47
- package/src/ui/docs/content/input.md +51 -35
- package/src/ui/docs/content/page.md +84 -95
- package/src/ui/docs/content/pane.md +83 -63
- package/src/ui/docs/content/presentation.md +20 -20
- package/src/ui/docs/content/tabs.md +107 -36
- package/src/ui/meta.ts +5 -5
- package/src/ui/react.tsx +0 -1
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* <Page /> — esqueleto composto de página do back-office.
|
|
3
3
|
*
|
|
4
|
-
* A forma curta cobre o caso comum; PageShell coordena
|
|
5
|
-
*
|
|
4
|
+
* A forma curta cobre o caso comum; PageShell coordena contexto conhecido pelo shell sem criar
|
|
5
|
+
* uma segunda região visual. Todas preservam o container centralizado com teto de 80rem.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
9
|
Children,
|
|
10
|
+
Fragment,
|
|
10
11
|
createContext,
|
|
11
12
|
isValidElement,
|
|
12
13
|
useCallback,
|
|
@@ -30,80 +31,35 @@ import {
|
|
|
30
31
|
} from "./page-heading-context.tsx";
|
|
31
32
|
import { SurfaceHeader, surfaceHeaderClasses } from "./surface-header.tsx";
|
|
32
33
|
import { PageState, PageStatePresenceProvider } from "./page-state.tsx";
|
|
33
|
-
import {
|
|
34
|
-
FillRoot,
|
|
35
|
-
fillRegionClassName,
|
|
36
|
-
useFillRegion,
|
|
37
|
-
} from "./fill-region.ts";
|
|
34
|
+
import { FillRoot, fillRegionClassName, useFillRegion } from "./fill-region.ts";
|
|
38
35
|
|
|
39
36
|
interface PageContextValue {}
|
|
40
37
|
|
|
41
38
|
const PageContext = createContext<PageContextValue | null>(null);
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const PageHeaderContext = createContext<PageHeadingRegion | null>(null);
|
|
39
|
+
const PageHeaderContext = createContext(false);
|
|
45
40
|
const PageIntegralStateContext = createContext(false);
|
|
46
41
|
const PageActionsTargetContext = createContext<HTMLElement | null>(null);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
"flex h-[3.75rem] shrink-0 items-center border-b border-border bg-background text-foreground";
|
|
51
|
-
const pageBarContentClassName = "w-full p-3";
|
|
52
|
-
|
|
53
|
-
/** Uso interno de patterns que precisam adaptar a Page ao shell sem duplicar sua anatomia. */
|
|
54
|
-
export function useInsidePageShell(): boolean {
|
|
55
|
-
return useContext(PageShellContext);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function PageShellNavigationSlot({
|
|
59
|
-
targetRef,
|
|
60
|
-
className,
|
|
61
|
-
...props
|
|
62
|
-
}: HTMLAttributes<HTMLDivElement> & {
|
|
63
|
-
targetRef?: (target: HTMLDivElement | null) => void;
|
|
64
|
-
}): ReactElement {
|
|
65
|
-
return (
|
|
66
|
-
<div
|
|
67
|
-
ref={targetRef}
|
|
68
|
-
data-slot="page-shell-navigation"
|
|
69
|
-
className={cn("min-w-0 shrink-0 overflow-hidden", className)}
|
|
70
|
-
{...props}
|
|
71
|
-
/>
|
|
72
|
-
);
|
|
42
|
+
interface PageShellContextValue {
|
|
43
|
+
navigation?: ReactNode;
|
|
44
|
+
actions?: ReactNode;
|
|
73
45
|
}
|
|
46
|
+
const PageShellContext = createContext<PageShellContextValue | null>(null);
|
|
74
47
|
|
|
75
|
-
function
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
data-slot="page-shell-title"
|
|
86
|
-
className={cn("min-w-0 flex-1", className)}
|
|
87
|
-
{...props}
|
|
88
|
-
/>
|
|
89
|
-
);
|
|
48
|
+
function hasRenderableNode(node: ReactNode): boolean {
|
|
49
|
+
if (node === null || node === undefined || typeof node === "boolean") {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
if (typeof node === "string") return node.trim().length > 0;
|
|
53
|
+
if (Array.isArray(node)) return node.some(hasRenderableNode);
|
|
54
|
+
if (isValidElement(node) && node.type === Fragment) {
|
|
55
|
+
return hasRenderableNode((node.props as { children?: ReactNode }).children);
|
|
56
|
+
}
|
|
57
|
+
return true;
|
|
90
58
|
}
|
|
91
59
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
...props
|
|
96
|
-
}: HTMLAttributes<HTMLDivElement> & {
|
|
97
|
-
targetRef: (target: HTMLDivElement | null) => void;
|
|
98
|
-
}): ReactElement {
|
|
99
|
-
return (
|
|
100
|
-
<div
|
|
101
|
-
ref={targetRef}
|
|
102
|
-
data-slot="page-shell-actions"
|
|
103
|
-
className={cn("flex shrink-0 items-center gap-2", className)}
|
|
104
|
-
{...props}
|
|
105
|
-
/>
|
|
106
|
-
);
|
|
60
|
+
/** Uso interno de patterns que precisam adaptar a Page ao shell sem duplicar sua anatomia. */
|
|
61
|
+
export function useInsidePageShell(): boolean {
|
|
62
|
+
return useContext(PageShellContext) !== null;
|
|
107
63
|
}
|
|
108
64
|
|
|
109
65
|
export interface PageShellProps extends Omit<
|
|
@@ -116,25 +72,20 @@ export interface PageShellProps extends Omit<
|
|
|
116
72
|
actions?: ReactNode;
|
|
117
73
|
/** Uma Page descendente, diretamente ou através da rota ativa. */
|
|
118
74
|
children: ReactNode;
|
|
119
|
-
/** Classes do container interno da barra. */
|
|
120
|
-
headerClassName?: string;
|
|
121
75
|
}
|
|
122
76
|
|
|
123
|
-
/** Moldura
|
|
77
|
+
/** Moldura que coordena contexto do shell com o PageHeader da página ativa. */
|
|
124
78
|
export function PageShell({
|
|
125
79
|
navigation,
|
|
126
80
|
actions,
|
|
127
81
|
children,
|
|
128
82
|
className,
|
|
129
|
-
headerClassName,
|
|
130
83
|
...props
|
|
131
84
|
}: PageShellProps): ReactElement {
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
useState<HTMLDivElement | null>(null);
|
|
137
|
-
|
|
85
|
+
const shell = {
|
|
86
|
+
...(hasRenderableNode(navigation) ? { navigation } : {}),
|
|
87
|
+
...(hasRenderableNode(actions) ? { actions } : {}),
|
|
88
|
+
};
|
|
138
89
|
return (
|
|
139
90
|
<div
|
|
140
91
|
data-slot="page-shell"
|
|
@@ -144,39 +95,13 @@ export function PageShell({
|
|
|
144
95
|
)}
|
|
145
96
|
{...props}
|
|
146
97
|
>
|
|
147
|
-
<
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}}
|
|
155
|
-
leadingPlacement="inline"
|
|
156
|
-
titleRequired={false}
|
|
157
|
-
contentClassName={cn(pageBarContentClassName, "px-3", headerClassName)}
|
|
158
|
-
className={pageBarClassName}
|
|
159
|
-
data-variant="bar"
|
|
160
|
-
>
|
|
161
|
-
<PageShellNavigationSlot targetRef={setNavigationTarget}>
|
|
162
|
-
{navigation}
|
|
163
|
-
</PageShellNavigationSlot>
|
|
164
|
-
<PageShellTitleSlot />
|
|
165
|
-
<PageShellActionsSlot targetRef={setActionsTarget}>
|
|
166
|
-
{actions}
|
|
167
|
-
</PageShellActionsSlot>
|
|
168
|
-
</SurfaceHeader>
|
|
169
|
-
<PageShellContext.Provider value>
|
|
170
|
-
<PageNavigationTargetContext.Provider value={navigationTarget}>
|
|
171
|
-
<PageActionsTarget target={actionsTarget}>
|
|
172
|
-
<div
|
|
173
|
-
data-slot="page-shell-content"
|
|
174
|
-
className="min-h-0 min-w-0 flex-1 overflow-auto [&>*]:min-w-[64rem]"
|
|
175
|
-
>
|
|
176
|
-
{children}
|
|
177
|
-
</div>
|
|
178
|
-
</PageActionsTarget>
|
|
179
|
-
</PageNavigationTargetContext.Provider>
|
|
98
|
+
<PageShellContext.Provider value={shell}>
|
|
99
|
+
<div
|
|
100
|
+
data-slot="page-shell-content"
|
|
101
|
+
className="min-h-0 min-w-0 flex-1 overflow-auto [&>*]:min-w-[64rem]"
|
|
102
|
+
>
|
|
103
|
+
{children}
|
|
104
|
+
</div>
|
|
180
105
|
</PageShellContext.Provider>
|
|
181
106
|
</div>
|
|
182
107
|
);
|
|
@@ -232,11 +157,21 @@ interface PageComposedProps extends PageBaseProps {
|
|
|
232
157
|
|
|
233
158
|
export type PageProps = PageShorthandProps | PageComposedProps;
|
|
234
159
|
|
|
160
|
+
function flattenPageHeaderChildren(node: ReactNode): ReactNode[] {
|
|
161
|
+
return Children.toArray(node).flatMap((child) =>
|
|
162
|
+
isValidElement(child) && child.type === Fragment
|
|
163
|
+
? flattenPageHeaderChildren(
|
|
164
|
+
(child.props as { children?: ReactNode }).children,
|
|
165
|
+
)
|
|
166
|
+
: [child],
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
235
170
|
function countDirectChildrenOfType(
|
|
236
171
|
node: ReactElement,
|
|
237
172
|
type: typeof PageTitle,
|
|
238
173
|
): number {
|
|
239
|
-
return
|
|
174
|
+
return flattenPageHeaderChildren(
|
|
240
175
|
(node.props as { children?: ReactNode }).children,
|
|
241
176
|
).filter((child) => isValidElement(child) && child.type === type).length;
|
|
242
177
|
}
|
|
@@ -292,9 +227,10 @@ export function Page({
|
|
|
292
227
|
children,
|
|
293
228
|
...props
|
|
294
229
|
}: PageProps): ReactElement {
|
|
295
|
-
const
|
|
230
|
+
const shell = useContext(PageShellContext);
|
|
231
|
+
const insideShell = shell !== null;
|
|
296
232
|
const shorthand = title !== undefined;
|
|
297
|
-
const nodes =
|
|
233
|
+
const nodes = flattenPageHeaderChildren(children);
|
|
298
234
|
const headers = nodes.filter(
|
|
299
235
|
(node) => isValidElement(node) && node.type === PageHeader,
|
|
300
236
|
);
|
|
@@ -304,9 +240,6 @@ export function Page({
|
|
|
304
240
|
const footers = nodes.filter(
|
|
305
241
|
(node) => isValidElement(node) && node.type === PageFooter,
|
|
306
242
|
);
|
|
307
|
-
const intros = nodes.filter(
|
|
308
|
-
(node) => isValidElement(node) && node.type === PageIntro,
|
|
309
|
-
);
|
|
310
243
|
const activeStateCount = useRef(0);
|
|
311
244
|
const headingCount = useRef(0);
|
|
312
245
|
const [integralState, setIntegralState] = useState(false);
|
|
@@ -338,7 +271,7 @@ export function Page({
|
|
|
338
271
|
nodes.some(
|
|
339
272
|
(node) =>
|
|
340
273
|
isValidElement(node) &&
|
|
341
|
-
[PageHeader,
|
|
274
|
+
[PageHeader, PageBody, PageFooter].includes(
|
|
342
275
|
node.type as typeof PageHeader,
|
|
343
276
|
),
|
|
344
277
|
)
|
|
@@ -350,14 +283,12 @@ export function Page({
|
|
|
350
283
|
if (!shorthand) {
|
|
351
284
|
const validComposition =
|
|
352
285
|
headers.length <= 1 &&
|
|
353
|
-
intros.length <= 1 &&
|
|
354
286
|
bodies.length === 1 &&
|
|
355
287
|
footers.length <= 1 &&
|
|
356
|
-
headers.length +
|
|
357
|
-
nodes.length;
|
|
288
|
+
headers.length + bodies.length + footers.length === nodes.length;
|
|
358
289
|
if (!validComposition) {
|
|
359
290
|
throw new Error(
|
|
360
|
-
"Page explícito exige um PageBody e aceita no máximo um PageHeader
|
|
291
|
+
"Page explícito exige um PageBody e aceita no máximo um PageHeader e um PageFooter como filhos diretos.",
|
|
361
292
|
);
|
|
362
293
|
}
|
|
363
294
|
const bodyHeadingSources = bodies.reduce<{
|
|
@@ -380,13 +311,7 @@ export function Page({
|
|
|
380
311
|
(total, header) =>
|
|
381
312
|
total + countDirectChildrenOfType(header as ReactElement, PageTitle),
|
|
382
313
|
0,
|
|
383
|
-
) +
|
|
384
|
-
intros.reduce<number>(
|
|
385
|
-
(total, intro) =>
|
|
386
|
-
total + countDirectChildrenOfType(intro as ReactElement, PageTitle),
|
|
387
|
-
0,
|
|
388
|
-
) +
|
|
389
|
-
bodyHeadingSources.content;
|
|
314
|
+
) + bodyHeadingSources.content;
|
|
390
315
|
const headingSources =
|
|
391
316
|
bodyHeadingSources.state > 0
|
|
392
317
|
? bodyHeadingSources.state + bodyHeadingSources.content
|
|
@@ -398,7 +323,7 @@ export function Page({
|
|
|
398
323
|
);
|
|
399
324
|
if (headingSources > 1 || (headingSources === 0 && !hasOpaqueBodyContent)) {
|
|
400
325
|
throw new Error(
|
|
401
|
-
"Page explícito exige exatamente um heading principal em PageHeader,
|
|
326
|
+
"Page explícito exige exatamente um heading principal em PageHeader, Content level={1} ou PageState ativo dentro de PageBody.",
|
|
402
327
|
);
|
|
403
328
|
}
|
|
404
329
|
}
|
|
@@ -407,34 +332,27 @@ export function Page({
|
|
|
407
332
|
if (integralState !== hasActiveIntegralState) return;
|
|
408
333
|
if (headingCount.current !== 1) {
|
|
409
334
|
throw new Error(
|
|
410
|
-
"Page explícito exige exatamente um heading principal em PageHeader,
|
|
335
|
+
"Page explícito exige exatamente um heading principal em PageHeader, Content level={1} ou PageState ativo dentro de PageBody.",
|
|
411
336
|
);
|
|
412
337
|
}
|
|
413
338
|
});
|
|
414
|
-
const content =
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
{
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
{actions !== undefined && <PageActions>{actions}</PageActions>}
|
|
432
|
-
</PageHeader>
|
|
433
|
-
<PageBody>{children}</PageBody>
|
|
434
|
-
</>
|
|
435
|
-
) : (
|
|
436
|
-
children
|
|
437
|
-
);
|
|
339
|
+
const content = shorthand ? (
|
|
340
|
+
<>
|
|
341
|
+
<PageHeader>
|
|
342
|
+
<PageTitle>{title}</PageTitle>
|
|
343
|
+
{actions !== undefined && <PageActions>{actions}</PageActions>}
|
|
344
|
+
</PageHeader>
|
|
345
|
+
<PageBody>{children}</PageBody>
|
|
346
|
+
</>
|
|
347
|
+
) : (
|
|
348
|
+
<>
|
|
349
|
+
{headers.length === 0 &&
|
|
350
|
+
(shell?.navigation !== undefined || shell?.actions !== undefined) ? (
|
|
351
|
+
<PageHeader />
|
|
352
|
+
) : null}
|
|
353
|
+
{children}
|
|
354
|
+
</>
|
|
355
|
+
);
|
|
438
356
|
|
|
439
357
|
return (
|
|
440
358
|
<PageContext.Provider value={{}}>
|
|
@@ -473,56 +391,7 @@ export function Page({
|
|
|
473
391
|
);
|
|
474
392
|
}
|
|
475
393
|
|
|
476
|
-
/**
|
|
477
|
-
export function PageIntro({
|
|
478
|
-
className,
|
|
479
|
-
children,
|
|
480
|
-
...props
|
|
481
|
-
}: HTMLAttributes<HTMLDivElement>): ReactElement {
|
|
482
|
-
const page = useContext(PageContext);
|
|
483
|
-
const insideShell = useContext(PageShellContext);
|
|
484
|
-
const integralState = useContext(PageIntegralStateContext);
|
|
485
|
-
requireParent(page !== null, "PageIntro", "Page");
|
|
486
|
-
if (integralState) {
|
|
487
|
-
if (!insideShell) return <></>;
|
|
488
|
-
const persistentSlots = Children.toArray(children).filter(
|
|
489
|
-
(node) =>
|
|
490
|
-
isValidElement(node) &&
|
|
491
|
-
[PageBack, PageNavigation, PageActions].includes(
|
|
492
|
-
node.type as typeof PageBack,
|
|
493
|
-
),
|
|
494
|
-
);
|
|
495
|
-
return (
|
|
496
|
-
<PageHeaderContext.Provider value="intro">
|
|
497
|
-
{persistentSlots}
|
|
498
|
-
</PageHeaderContext.Provider>
|
|
499
|
-
);
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
return (
|
|
503
|
-
<PageHeaderContext.Provider value="intro">
|
|
504
|
-
<SurfaceHeader
|
|
505
|
-
name="PageIntro"
|
|
506
|
-
slot="page-intro"
|
|
507
|
-
slots={{
|
|
508
|
-
leading: [PageBack, PageNavigation],
|
|
509
|
-
title: PageTitle,
|
|
510
|
-
actions: PageActions,
|
|
511
|
-
}}
|
|
512
|
-
leadingPlacement="inline"
|
|
513
|
-
className={className}
|
|
514
|
-
{...props}
|
|
515
|
-
>
|
|
516
|
-
{children}
|
|
517
|
-
</SurfaceHeader>
|
|
518
|
-
</PageHeaderContext.Provider>
|
|
519
|
-
);
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
/**
|
|
523
|
-
* Chrome da Page: navegação e ações persistentes. Fora de PageShell também pode
|
|
524
|
-
* carregar o título; dentro dele, os slots são projetados na barra do shell.
|
|
525
|
-
*/
|
|
394
|
+
/** Cabeçalho único da Page: navegação contextual, título e ações. */
|
|
526
395
|
export interface PageHeaderProps extends HTMLAttributes<HTMLDivElement> {}
|
|
527
396
|
|
|
528
397
|
export function PageHeader({
|
|
@@ -531,19 +400,58 @@ export function PageHeader({
|
|
|
531
400
|
...props
|
|
532
401
|
}: PageHeaderProps): ReactElement {
|
|
533
402
|
const page = useContext(PageContext);
|
|
534
|
-
const
|
|
403
|
+
const shell = useContext(PageShellContext);
|
|
535
404
|
const integralState = useContext(PageIntegralStateContext);
|
|
536
405
|
requireParent(page !== null, "PageHeader", "Page");
|
|
537
|
-
if (integralState
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
406
|
+
if (integralState) return <></>;
|
|
407
|
+
|
|
408
|
+
const nodes = flattenPageHeaderChildren(children);
|
|
409
|
+
const localNavigation = nodes.filter(
|
|
410
|
+
(node) =>
|
|
411
|
+
isValidElement(node) &&
|
|
412
|
+
node.type === PageNavigation &&
|
|
413
|
+
hasRenderableNode((node.props as { children?: ReactNode }).children),
|
|
414
|
+
);
|
|
415
|
+
const localActions = nodes.filter(
|
|
416
|
+
(node) =>
|
|
417
|
+
isValidElement(node) &&
|
|
418
|
+
node.type === PageActions &&
|
|
419
|
+
hasRenderableNode((node.props as { children?: ReactNode }).children),
|
|
420
|
+
) as ReactElement<HTMLAttributes<HTMLDivElement>>[];
|
|
421
|
+
if (shell?.navigation !== undefined && localNavigation.length > 0) {
|
|
422
|
+
throw new Error(
|
|
423
|
+
"PageHeader não combina a navegação de PageShell com PageNavigation local.",
|
|
543
424
|
);
|
|
544
425
|
}
|
|
426
|
+
if (localActions.length > 1) {
|
|
427
|
+
throw new Error("PageHeader aceita no máximo um PageActions.");
|
|
428
|
+
}
|
|
429
|
+
const structuralChildren = nodes.filter(
|
|
430
|
+
(node) =>
|
|
431
|
+
!(
|
|
432
|
+
isValidElement(node) &&
|
|
433
|
+
[PageNavigation, PageActions].includes(
|
|
434
|
+
node.type as typeof PageNavigation,
|
|
435
|
+
)
|
|
436
|
+
),
|
|
437
|
+
);
|
|
438
|
+
const pageActions = localActions[0];
|
|
439
|
+
const mergedActions =
|
|
440
|
+
shell?.actions === undefined && pageActions === undefined ? null : (
|
|
441
|
+
<PageActions {...pageActions?.props}>
|
|
442
|
+
{shell?.actions}
|
|
443
|
+
{pageActions?.props.children}
|
|
444
|
+
</PageActions>
|
|
445
|
+
);
|
|
446
|
+
if (
|
|
447
|
+
structuralChildren.length === 0 &&
|
|
448
|
+
mergedActions === null &&
|
|
449
|
+
shell?.navigation === undefined
|
|
450
|
+
) {
|
|
451
|
+
return <></>;
|
|
452
|
+
}
|
|
545
453
|
return (
|
|
546
|
-
<PageHeaderContext.Provider value
|
|
454
|
+
<PageHeaderContext.Provider value>
|
|
547
455
|
<SurfaceHeader
|
|
548
456
|
name="PageHeader"
|
|
549
457
|
slot="page"
|
|
@@ -552,15 +460,18 @@ export function PageHeader({
|
|
|
552
460
|
title: PageTitle,
|
|
553
461
|
actions: PageActions,
|
|
554
462
|
}}
|
|
555
|
-
leadingPlacement="
|
|
463
|
+
leadingPlacement="above"
|
|
556
464
|
titleRequired={false}
|
|
557
|
-
className={
|
|
558
|
-
"flex h-[3.75rem] items-center border-b p-3 [&>*]:w-full",
|
|
559
|
-
className,
|
|
560
|
-
)}
|
|
465
|
+
className={className}
|
|
561
466
|
{...props}
|
|
562
467
|
>
|
|
563
|
-
{
|
|
468
|
+
{shell?.navigation === undefined ? (
|
|
469
|
+
localNavigation
|
|
470
|
+
) : (
|
|
471
|
+
<PageNavigation>{shell.navigation}</PageNavigation>
|
|
472
|
+
)}
|
|
473
|
+
{structuralChildren}
|
|
474
|
+
{mergedActions}
|
|
564
475
|
</SurfaceHeader>
|
|
565
476
|
</PageHeaderContext.Provider>
|
|
566
477
|
);
|
|
@@ -570,18 +481,13 @@ export function PageTitle({
|
|
|
570
481
|
className,
|
|
571
482
|
...props
|
|
572
483
|
}: HTMLAttributes<HTMLHeadingElement>): ReactElement {
|
|
573
|
-
const
|
|
574
|
-
requireParent(
|
|
484
|
+
const insideHeader = useContext(PageHeaderContext);
|
|
485
|
+
requireParent(insideHeader, "PageTitle", "PageHeader");
|
|
575
486
|
usePageHeadingPresence();
|
|
576
487
|
return (
|
|
577
488
|
<h1
|
|
578
489
|
data-slot="page-title"
|
|
579
|
-
className={cn(
|
|
580
|
-
variant === "intro"
|
|
581
|
-
? "text-3xl font-semibold tracking-tight"
|
|
582
|
-
: surfaceHeaderClasses.page.title,
|
|
583
|
-
className,
|
|
584
|
-
)}
|
|
490
|
+
className={cn(surfaceHeaderClasses.page.title, className)}
|
|
585
491
|
{...props}
|
|
586
492
|
/>
|
|
587
493
|
);
|
|
@@ -591,23 +497,16 @@ export function PageActions({
|
|
|
591
497
|
className,
|
|
592
498
|
...props
|
|
593
499
|
}: HTMLAttributes<HTMLDivElement>): ReactElement {
|
|
594
|
-
const
|
|
595
|
-
requireParent(
|
|
500
|
+
const insideHeader = useContext(PageHeaderContext);
|
|
501
|
+
requireParent(insideHeader, "PageActions", "PageHeader");
|
|
596
502
|
const target = useContext(PageActionsTargetContext);
|
|
597
|
-
const insideShell = useContext(PageShellContext);
|
|
598
503
|
const actions = (
|
|
599
504
|
<div
|
|
600
505
|
data-slot="page-actions"
|
|
601
|
-
className={cn(
|
|
602
|
-
insideShell || variant === "header"
|
|
603
|
-
? "flex shrink-0 items-center gap-2"
|
|
604
|
-
: surfaceHeaderClasses.page.actions,
|
|
605
|
-
className,
|
|
606
|
-
)}
|
|
506
|
+
className={cn(surfaceHeaderClasses.page.actions, className)}
|
|
607
507
|
{...props}
|
|
608
508
|
/>
|
|
609
509
|
);
|
|
610
|
-
if (insideShell && target === null) return <></>;
|
|
611
510
|
return target === null ? actions : createPortal(actions, target);
|
|
612
511
|
}
|
|
613
512
|
|
|
@@ -627,21 +526,15 @@ export function PageNavigation({
|
|
|
627
526
|
className,
|
|
628
527
|
...props
|
|
629
528
|
}: HTMLAttributes<HTMLDivElement>): ReactElement {
|
|
630
|
-
const
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
requireParent(variant !== null, "PageNavigation", "PageHeader ou PageIntro");
|
|
634
|
-
const navigation = (
|
|
529
|
+
const insideHeader = useContext(PageHeaderContext);
|
|
530
|
+
requireParent(insideHeader, "PageNavigation", "PageHeader");
|
|
531
|
+
return (
|
|
635
532
|
<div
|
|
636
533
|
data-slot="page-navigation-content"
|
|
637
534
|
className={cn("min-w-0", className)}
|
|
638
535
|
{...props}
|
|
639
536
|
/>
|
|
640
537
|
);
|
|
641
|
-
if (insideShell) {
|
|
642
|
-
return target === null ? <></> : createPortal(navigation, target);
|
|
643
|
-
}
|
|
644
|
-
return navigation;
|
|
645
538
|
}
|
|
646
539
|
|
|
647
540
|
export function PageBack({
|
|
@@ -650,10 +543,8 @@ export function PageBack({
|
|
|
650
543
|
"aria-label": ariaLabel,
|
|
651
544
|
...props
|
|
652
545
|
}: PageBackProps): ReactElement {
|
|
653
|
-
const
|
|
654
|
-
|
|
655
|
-
const target = useContext(PageNavigationTargetContext);
|
|
656
|
-
requireParent(variant !== null, "PageBack", "PageHeader ou PageIntro");
|
|
546
|
+
const insideHeader = useContext(PageHeaderContext);
|
|
547
|
+
requireParent(insideHeader, "PageBack", "PageHeader");
|
|
657
548
|
const destination =
|
|
658
549
|
typeof children === "string" ? children : "a página anterior";
|
|
659
550
|
const accessibleLabel = ariaLabel ?? `Voltar para ${destination}`;
|
|
@@ -671,9 +562,6 @@ export function PageBack({
|
|
|
671
562
|
</a>
|
|
672
563
|
</Button>
|
|
673
564
|
);
|
|
674
|
-
if (insideShell) {
|
|
675
|
-
return target === null ? <></> : createPortal(back, target);
|
|
676
|
-
}
|
|
677
565
|
return back;
|
|
678
566
|
}
|
|
679
567
|
|
|
@@ -66,7 +66,6 @@ import {
|
|
|
66
66
|
PageBody,
|
|
67
67
|
PageFooter,
|
|
68
68
|
PageHeader,
|
|
69
|
-
PageIntro,
|
|
70
69
|
PageNavigation,
|
|
71
70
|
PageTitle,
|
|
72
71
|
useInsidePageShell,
|
|
@@ -272,12 +271,14 @@ function PresentationFrame({
|
|
|
272
271
|
className={cn("flex min-h-full flex-col", className)}
|
|
273
272
|
>
|
|
274
273
|
{navigation === undefined &&
|
|
274
|
+
contentOwnsHeading &&
|
|
275
275
|
headerActions === undefined &&
|
|
276
276
|
assistantAction === undefined ? null : (
|
|
277
277
|
<PageHeader>
|
|
278
278
|
{navigation === undefined ? null : (
|
|
279
279
|
<PageNavigation>{navigation}</PageNavigation>
|
|
280
280
|
)}
|
|
281
|
+
{contentOwnsHeading ? null : <PageTitle>{title}</PageTitle>}
|
|
281
282
|
{headerActions === undefined &&
|
|
282
283
|
assistantAction === undefined ? null : (
|
|
283
284
|
<PageActions>
|
|
@@ -289,11 +290,6 @@ function PresentationFrame({
|
|
|
289
290
|
)}
|
|
290
291
|
</PageHeader>
|
|
291
292
|
)}
|
|
292
|
-
{contentOwnsHeading ? null : (
|
|
293
|
-
<PageIntro>
|
|
294
|
-
<PageTitle>{title}</PageTitle>
|
|
295
|
-
</PageIntro>
|
|
296
|
-
)}
|
|
297
293
|
<PageBody className="min-h-0 flex-1 overflow-hidden">
|
|
298
294
|
{withAssistant(
|
|
299
295
|
<div
|