@softize/opus 21.0.1 → 22.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.
Files changed (62) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/bin/cli.mjs +9 -1
  3. package/bin/lib/check.mjs +37 -29
  4. package/docs/adr/0004-page-content-state-is-composed.md +3 -2
  5. package/docs/adr/0005-structural-surfaces-share-an-explicit-anatomy.md +3 -0
  6. package/docs/adr/0006-semantic-context-precedes-visual-variant.md +23 -14
  7. package/docs/adr/0011-page-shell-coordinates-persistent-page-chrome.md +5 -0
  8. package/docs/adr/0013-presentation-is-a-portable-action-oriented-artifact.md +7 -4
  9. package/docs/adr/0016-list-collection-header-belongs-to-content.md +13 -8
  10. package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +27 -26
  11. package/docs/adr/0025-content-frame-treatment-does-not-change-semantics.md +1 -0
  12. package/docs/adr/0029-page-header-is-the-single-page-introduction.md +71 -0
  13. package/docs/adr/0030-framed-content-can-use-sectioned-anatomy.md +89 -0
  14. package/docs/adr/0031-detail-dividers-own-the-tabular-label-treatment.md +61 -0
  15. package/docs/adr/0032-tab-baseline-respects-the-container-inset.md +59 -0
  16. package/docs/adr/0033-drawer-uses-an-inset-structural-frame.md +72 -0
  17. package/docs/adr/0034-tabs-can-own-a-frame-without-owning-a-surface.md +60 -0
  18. package/docs/adr/0035-button-group-separates-frame-relationship-and-distribution.md +63 -0
  19. package/docs/adr/0036-tabs-declares-trigger-distribution.md +58 -0
  20. package/docs/adr/0037-design-data-lives-outside-the-contract.md +117 -0
  21. package/docs/radius-scale.md +5 -2
  22. package/package.json +5 -1
  23. package/registry/skills/build-opus-ui/SKILL.md +9 -6
  24. package/registry/skills/build-opus-ui/references/evaluations.md +5 -2
  25. package/registry/skills/build-opus-ui/references/ui-patterns.md +14 -16
  26. package/registry/skills/create-opus-action/references/contract-and-binding.md +5 -3
  27. package/registry/skills/create-opus-action/scripts/scaffold.mjs +0 -4
  28. package/registry/skills/test-opus-action/SKILL.md +2 -2
  29. package/registry/skills/test-opus-action/references/harness.md +2 -2
  30. package/registry/templates/app/src/App.tsx +1 -1
  31. package/registry/templates/app/src/domains/tasks/actions/list.test.ts +2 -19
  32. package/registry/templates/app/src/domains/tasks/actions/list.ts +3 -5
  33. package/registry/templates/app/vite.config.ts +3 -2
  34. package/src/core/domain.ts +2 -2
  35. package/src/core/types.ts +6 -20
  36. package/src/design/index.ts +179 -0
  37. package/src/testing/fake.ts +2 -2
  38. package/src/testing/index.ts +7 -3
  39. package/src/ui/components/patterns/content-header.tsx +51 -13
  40. package/src/ui/components/patterns/list.tsx +51 -33
  41. package/src/ui/components/patterns/page.tsx +139 -251
  42. package/src/ui/components/patterns/presentation.tsx +2 -6
  43. package/src/ui/components/primitives/button-group.tsx +80 -8
  44. package/src/ui/components/primitives/button.tsx +85 -60
  45. package/src/ui/components/primitives/detail.tsx +63 -57
  46. package/src/ui/components/primitives/dialog.tsx +8 -11
  47. package/src/ui/components/primitives/drawer.tsx +16 -12
  48. package/src/ui/components/primitives/tabs.tsx +123 -54
  49. package/src/ui/docs/content/actions.md +49 -43
  50. package/src/ui/docs/content/button.md +55 -29
  51. package/src/ui/docs/content/content.md +61 -36
  52. package/src/ui/docs/content/detail.md +28 -19
  53. package/src/ui/docs/content/drawer.md +53 -47
  54. package/src/ui/docs/content/input.md +51 -35
  55. package/src/ui/docs/content/page.md +84 -95
  56. package/src/ui/docs/content/pane.md +83 -63
  57. package/src/ui/docs/content/presentation.md +20 -20
  58. package/src/ui/docs/content/tabs.md +107 -36
  59. package/src/ui/docs/content/testing.md +9 -25
  60. package/src/ui/meta.ts +5 -5
  61. package/src/ui/react.tsx +0 -1
  62. package/src/vite/design.ts +133 -23
@@ -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 a barra persistente quando shell e rota
5
- * conhecem partes diferentes. Todas preservam o container centralizado com teto de 80rem.
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
- type PageHeadingRegion = "header" | "intro";
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
- const PageNavigationTargetContext = createContext<HTMLElement | null>(null);
48
- const PageShellContext = createContext(false);
49
- const pageBarClassName =
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 PageShellTitleSlot({
76
- targetRef,
77
- className,
78
- ...props
79
- }: HTMLAttributes<HTMLDivElement> & {
80
- targetRef?: (target: HTMLDivElement | null) => void;
81
- }): ReactElement {
82
- return (
83
- <div
84
- ref={targetRef}
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
- function PageShellActionsSlot({
93
- targetRef,
94
- className,
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 persistente que coordena navegação do shell e ações da Page ativa. */
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 [actionsTarget, setActionsTarget] = useState<HTMLDivElement | null>(
133
- null,
134
- );
135
- const [navigationTarget, setNavigationTarget] =
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
- <SurfaceHeader
148
- name="PageShell"
149
- slot="page"
150
- slots={{
151
- leading: PageShellNavigationSlot,
152
- title: PageShellTitleSlot,
153
- actions: PageShellActionsSlot,
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 Children.toArray(
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 insideShell = useContext(PageShellContext);
230
+ const shell = useContext(PageShellContext);
231
+ const insideShell = shell !== null;
296
232
  const shorthand = title !== undefined;
297
- const nodes = Children.toArray(children);
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, PageIntro, PageBody, PageFooter].includes(
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 + intros.length + bodies.length + footers.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, um PageIntro e um PageFooter como filhos diretos.",
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, PageIntro, Content level={1} ou PageState ativo dentro de PageBody.",
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, PageIntro, Content level={1} ou PageState ativo dentro de PageBody.",
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
- shorthand && insideShell ? (
416
- <>
417
- {actions !== undefined && (
418
- <PageHeader>
419
- <PageActions>{actions}</PageActions>
420
- </PageHeader>
421
- )}
422
- <PageIntro>
423
- <PageTitle>{title}</PageTitle>
424
- </PageIntro>
425
- <PageBody>{children}</PageBody>
426
- </>
427
- ) : shorthand ? (
428
- <>
429
- <PageHeader>
430
- <PageTitle>{title}</PageTitle>
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
- /** Título estrutural no conteúdo de PageShell ou introdução opcional de uma Page isolada. */
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 insideShell = useContext(PageShellContext);
403
+ const shell = useContext(PageShellContext);
535
404
  const integralState = useContext(PageIntegralStateContext);
536
405
  requireParent(page !== null, "PageHeader", "Page");
537
- if (integralState && !insideShell) return <></>;
538
- if (insideShell) {
539
- return (
540
- <PageHeaderContext.Provider value="header">
541
- {children}
542
- </PageHeaderContext.Provider>
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="header">
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="inline"
463
+ leadingPlacement="above"
556
464
  titleRequired={false}
557
- className={cn(
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
- {children}
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 variant = useContext(PageHeaderContext);
574
- requireParent(variant !== null, "PageTitle", "PageHeader ou PageIntro");
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 variant = useContext(PageHeaderContext);
595
- requireParent(variant !== null, "PageActions", "PageHeader ou PageIntro");
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 variant = useContext(PageHeaderContext);
631
- const insideShell = useContext(PageShellContext);
632
- const target = useContext(PageNavigationTargetContext);
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 variant = useContext(PageHeaderContext);
654
- const insideShell = useContext(PageShellContext);
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