@softize/opus 28.0.0 → 30.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 +79 -0
- package/bin/cli.mjs +68 -3
- package/bin/lib/check.mjs +86 -10
- package/bin/lib/copy.mjs +505 -67
- package/bin/lib/materialize.mjs +99 -1
- package/docs/adr/0028-schedules-run-actions-as-the-system-by-action-opt-in.md +1 -1
- package/docs/adr/0037-design-data-lives-outside-the-contract.md +1 -1
- package/docs/adr/0045-surface-replaces-content-and-card.md +3 -0
- package/docs/adr/0061-claude-md-imports-agents-md.md +41 -0
- package/docs/adr/0062-listas-contidas-e-texto-flexivel.md +68 -0
- package/docs/adr/0063-metadados-das-superficies.md +44 -0
- package/docs/adr/README.md +6 -0
- package/docs/code-style.md +47 -8
- package/docs/protocol.md +1 -1
- package/docs/releasing.md +7 -0
- package/package.json +1 -1
- package/registry/instructions/opus.md +3 -1
- package/registry/skills/build-opus-ui/SKILL.md +6 -4
- package/registry/skills/build-opus-ui/references/evaluations.md +10 -1
- package/registry/skills/build-opus-ui/references/ui-patterns.md +10 -2
- package/registry/skills/maintain-opus-docs/SKILL.md +2 -1
- package/registry/skills/maintain-opus-docs/references/editorial-standard.md +9 -0
- package/registry/skills/maintain-opus-docs/references/evaluations.md +8 -0
- package/registry/templates/app/pnpm-workspace.yaml +1 -1
- package/src/core/system-actor.ts +1 -1
- package/src/core/types.ts +1 -1
- package/src/ui/components/patterns/list.tsx +19 -10
- package/src/ui/components/patterns/page.tsx +27 -0
- package/src/ui/components/patterns/structural-header.tsx +25 -3
- package/src/ui/components/patterns/surface.tsx +36 -8
- package/src/ui/components/primitives/dialog.tsx +74 -42
- package/src/ui/components/primitives/drawer.tsx +69 -42
- package/src/ui/components/primitives/table.tsx +3 -3
- package/src/ui/docs/content/action-list.md +15 -8
- package/src/ui/docs/content/actions.md +1 -1
- package/src/ui/docs/content/auth.md +3 -3
- package/src/ui/docs/content/cli.md +6 -0
- package/src/ui/docs/content/communication.md +10 -8
- package/src/ui/docs/content/dialog.md +39 -0
- package/src/ui/docs/content/drawer.md +36 -0
- package/src/ui/docs/content/events.md +13 -13
- package/src/ui/docs/content/installation.md +4 -0
- package/src/ui/docs/content/log.md +1 -1
- package/src/ui/docs/content/page.md +30 -1
- package/src/ui/docs/content/queue.md +2 -2
- package/src/ui/docs/content/router.md +1 -1
- package/src/ui/docs/content/scheduler.md +15 -15
- package/src/ui/docs/content/surface.md +41 -8
- package/src/ui/docs/content/table.md +7 -0
- package/src/ui/docs/content/testing.md +4 -4
- package/src/ui/react.tsx +4 -0
- package/src/vite/design.ts +1 -1
|
@@ -1524,7 +1524,7 @@ export function ActionFilterBar({
|
|
|
1524
1524
|
return (
|
|
1525
1525
|
<TooltipProvider>
|
|
1526
1526
|
<div data-slot="action-filter-bar" className="space-y-2">
|
|
1527
|
-
<div className="flex min-w-0 items-end gap-3">
|
|
1527
|
+
<div className="flex min-w-0 flex-wrap items-end gap-3">
|
|
1528
1528
|
{periodPosition === "before" && periodControl}
|
|
1529
1529
|
{inlineSpecs.map(([name, spec]) => {
|
|
1530
1530
|
if (name === periodBasisEntry?.[0]) {
|
|
@@ -1636,7 +1636,7 @@ export function ActionFilterBar({
|
|
|
1636
1636
|
{!searchAtActionsEnd && searchBox}
|
|
1637
1637
|
<div
|
|
1638
1638
|
data-slot="action-filter-actions"
|
|
1639
|
-
className="ml-auto flex min-w-0 items-end gap-2"
|
|
1639
|
+
className="ml-auto flex min-w-0 max-w-full flex-wrap items-end gap-2"
|
|
1640
1640
|
>
|
|
1641
1641
|
{searchAtActionsEnd && searchBox}
|
|
1642
1642
|
{(modalSpecs.length > 0 ||
|
|
@@ -2074,7 +2074,7 @@ export function ActionList<TInput, TItem>({
|
|
|
2074
2074
|
header: text(c.label, c.key),
|
|
2075
2075
|
sortable: c.sortable === true,
|
|
2076
2076
|
spec: c,
|
|
2077
|
-
className: cn(c.fit === true && "
|
|
2077
|
+
className: cn(c.fit === true && "whitespace-nowrap wrap-normal"),
|
|
2078
2078
|
cell:
|
|
2079
2079
|
cells?.[c.key] ??
|
|
2080
2080
|
((item: TItem): ReactNode => {
|
|
@@ -2534,7 +2534,13 @@ export function ActionList<TInput, TItem>({
|
|
|
2534
2534
|
</TableCell>
|
|
2535
2535
|
)}
|
|
2536
2536
|
{derived.map((col) => (
|
|
2537
|
-
<TableCell
|
|
2537
|
+
<TableCell
|
|
2538
|
+
key={col.key}
|
|
2539
|
+
className={cn(
|
|
2540
|
+
"has-[>[role=switch]:only-child]:w-0 has-[>[data-slot=tooltip-trigger]:only-child>[role=switch]:only-child]:w-0",
|
|
2541
|
+
col.className,
|
|
2542
|
+
)}
|
|
2543
|
+
>
|
|
2538
2544
|
{col.cell(item)}
|
|
2539
2545
|
</TableCell>
|
|
2540
2546
|
))}
|
|
@@ -2597,7 +2603,10 @@ export function ActionList<TInput, TItem>({
|
|
|
2597
2603
|
</TableHead>
|
|
2598
2604
|
)}
|
|
2599
2605
|
{derived.map((col) => (
|
|
2600
|
-
<TableHead
|
|
2606
|
+
<TableHead
|
|
2607
|
+
key={col.key}
|
|
2608
|
+
className={cn(col.className, col.spec && "whitespace-normal")}
|
|
2609
|
+
>
|
|
2601
2610
|
{col.sortable === true ? (
|
|
2602
2611
|
<button
|
|
2603
2612
|
type="button"
|
|
@@ -2959,8 +2968,8 @@ export function ActionList<TInput, TItem>({
|
|
|
2959
2968
|
data-layout={layout}
|
|
2960
2969
|
className={cn(
|
|
2961
2970
|
layout === "fill"
|
|
2962
|
-
? "flex h-full min-h-0 flex-1 flex-col gap-3"
|
|
2963
|
-
: "space-y-3",
|
|
2971
|
+
? "flex h-full min-h-0 min-w-0 max-w-full flex-1 flex-col gap-3"
|
|
2972
|
+
: "min-w-0 max-w-full space-y-3",
|
|
2964
2973
|
)}
|
|
2965
2974
|
>
|
|
2966
2975
|
{/* A lista é a folha da cadeia de altura: o que ela hospeda decide a própria. */}
|
|
@@ -2975,7 +2984,7 @@ export function ActionList<TInput, TItem>({
|
|
|
2975
2984
|
tem altura própria e ela vira a altura do conteúdo. */}
|
|
2976
2985
|
<div
|
|
2977
2986
|
className={cn(
|
|
2978
|
-
"grid min-h-0 grid-rows-[1fr_auto]",
|
|
2987
|
+
"grid min-h-0 min-w-0 grid-cols-[minmax(0,1fr)] grid-rows-[1fr_auto]",
|
|
2979
2988
|
layout === "fill" && "flex-1",
|
|
2980
2989
|
)}
|
|
2981
2990
|
>
|
|
@@ -2986,7 +2995,7 @@ export function ActionList<TInput, TItem>({
|
|
|
2986
2995
|
<div
|
|
2987
2996
|
aria-busy={busy}
|
|
2988
2997
|
className={cn(
|
|
2989
|
-
"min-h-0",
|
|
2998
|
+
"min-h-0 min-w-0",
|
|
2990
2999
|
layout === "fill" && "overflow-auto",
|
|
2991
3000
|
busy && items.length > 0 && "pointer-events-none opacity-60",
|
|
2992
3001
|
)}
|
|
@@ -3058,7 +3067,7 @@ export function ActionList<TInput, TItem>({
|
|
|
3058
3067
|
// URL sync — a serialização padrão do estado da toolbar (querystring compacta)
|
|
3059
3068
|
// =============================================================================
|
|
3060
3069
|
//
|
|
3061
|
-
// Convenção: `q` · `sort=
|
|
3070
|
+
// Convenção: `q` · `sort=key:dir` (omitido quando é o default do contrato) ·
|
|
3062
3071
|
// `period=preset` relativo ou `period=from..to` (o custom é implícito; default
|
|
3063
3072
|
// omitido) · `view` (omitida quando é a primeira) · `cols=a,b,c` (omitida no
|
|
3064
3073
|
// default) · filtros pelo PRÓPRIO nome (multiple → CSV). Quem embala liga:
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
} from "./page-heading-context.tsx";
|
|
32
32
|
import {
|
|
33
33
|
StructuralHeader,
|
|
34
|
+
structuralMetaClasses,
|
|
34
35
|
validateSurfaceIntro,
|
|
35
36
|
structuralHeaderClasses,
|
|
36
37
|
} from "./structural-header.tsx";
|
|
@@ -165,6 +166,8 @@ interface PageShorthandProps extends PageBaseProps {
|
|
|
165
166
|
title: ReactNode;
|
|
166
167
|
/** Orientação opcional sobre a finalidade da página; regras pertencem ao corpo. */
|
|
167
168
|
description?: ReactNode;
|
|
169
|
+
/** Badges, datas e autoria sob a orientação. */
|
|
170
|
+
meta?: ReactNode;
|
|
168
171
|
actions?: ReactNode;
|
|
169
172
|
children: ReactNode;
|
|
170
173
|
}
|
|
@@ -172,6 +175,7 @@ interface PageShorthandProps extends PageBaseProps {
|
|
|
172
175
|
interface PageComposedProps extends PageBaseProps {
|
|
173
176
|
title?: never;
|
|
174
177
|
description?: never;
|
|
178
|
+
meta?: never;
|
|
175
179
|
actions?: never;
|
|
176
180
|
children: ReactNode;
|
|
177
181
|
}
|
|
@@ -246,6 +250,7 @@ function hasOpaquePageBodyContent(node: ReactNode): boolean {
|
|
|
246
250
|
export function Page({
|
|
247
251
|
title,
|
|
248
252
|
description,
|
|
253
|
+
meta,
|
|
249
254
|
actions,
|
|
250
255
|
layout = "flow",
|
|
251
256
|
surfaceLayout = "sectioned",
|
|
@@ -377,6 +382,7 @@ export function Page({
|
|
|
377
382
|
{description !== undefined && (
|
|
378
383
|
<PageDescription>{description}</PageDescription>
|
|
379
384
|
)}
|
|
385
|
+
{meta !== undefined && <PageMeta>{meta}</PageMeta>}
|
|
380
386
|
</PageIntro>
|
|
381
387
|
{children}
|
|
382
388
|
</PageBody>
|
|
@@ -490,6 +496,7 @@ export function PageHeader({
|
|
|
490
496
|
leading: [PageBack, PageNavigation],
|
|
491
497
|
title: PageTitle,
|
|
492
498
|
description: PageDescription,
|
|
499
|
+
meta: PageMeta,
|
|
493
500
|
actions: PageActions,
|
|
494
501
|
}}
|
|
495
502
|
leadingPlacement={
|
|
@@ -542,6 +549,7 @@ export function PageIntro({
|
|
|
542
549
|
slots={{
|
|
543
550
|
title: PageTitle,
|
|
544
551
|
description: PageDescription,
|
|
552
|
+
meta: PageMeta,
|
|
545
553
|
actions: PageIntroActions,
|
|
546
554
|
}}
|
|
547
555
|
leadingPlacement="inline"
|
|
@@ -555,6 +563,25 @@ export function PageIntro({
|
|
|
555
563
|
);
|
|
556
564
|
}
|
|
557
565
|
|
|
566
|
+
/** Badges, datas e autoria abaixo do título e da orientação. */
|
|
567
|
+
export function PageMeta({
|
|
568
|
+
className,
|
|
569
|
+
...props
|
|
570
|
+
}: HTMLAttributes<HTMLDivElement>) {
|
|
571
|
+
const header = useContext(PageHeaderContext);
|
|
572
|
+
const intro = useContext(PageIntroContext);
|
|
573
|
+
const inside = header || intro;
|
|
574
|
+
if (!inside)
|
|
575
|
+
throw new Error("PageMeta deve estar dentro de PageHeader ou PageIntro.");
|
|
576
|
+
return (
|
|
577
|
+
<div
|
|
578
|
+
data-slot="page-meta"
|
|
579
|
+
className={cn(structuralMetaClasses, className)}
|
|
580
|
+
{...props}
|
|
581
|
+
/>
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
|
|
558
585
|
/** Ações locais do conteúdo, distintas das ações da superfície em PageActions. */
|
|
559
586
|
export function PageIntroActions({
|
|
560
587
|
className,
|
|
@@ -18,6 +18,10 @@ import {
|
|
|
18
18
|
} from "react";
|
|
19
19
|
import { cn } from "../../lib/cn.ts";
|
|
20
20
|
|
|
21
|
+
/** Metadados contextuais, separados da orientação textual e da linha de ações. */
|
|
22
|
+
export const structuralMetaClasses =
|
|
23
|
+
"mt-3 flex min-w-0 flex-wrap items-center gap-x-3 gap-y-2 text-sm text-muted-foreground [overflow-wrap:anywhere] [&>*]:max-w-full";
|
|
24
|
+
|
|
21
25
|
export type StructuralHeaderVariant = "page" | "section";
|
|
22
26
|
|
|
23
27
|
/** As classes por slot, por hierarquia visual — PageTitle e SurfaceTitle no nível 1 são o mesmo. */
|
|
@@ -46,6 +50,7 @@ interface StructuralHeaderSlots {
|
|
|
46
50
|
title: ElementType;
|
|
47
51
|
count?: ElementType;
|
|
48
52
|
description?: ElementType;
|
|
53
|
+
meta?: ElementType;
|
|
49
54
|
actions?: ElementType;
|
|
50
55
|
}
|
|
51
56
|
|
|
@@ -127,6 +132,7 @@ export function StructuralHeader({
|
|
|
127
132
|
const counts = slots.count === undefined ? [] : of(slots.count);
|
|
128
133
|
const descriptions =
|
|
129
134
|
slots.description === undefined ? [] : of(slots.description);
|
|
135
|
+
const metas = slots.meta === undefined ? [] : of(slots.meta);
|
|
130
136
|
const actionSlots = slots.actions === undefined ? [] : of(slots.actions);
|
|
131
137
|
const slotTypes = [
|
|
132
138
|
...leadingTypes,
|
|
@@ -134,6 +140,7 @@ export function StructuralHeader({
|
|
|
134
140
|
slots.title,
|
|
135
141
|
slots.count,
|
|
136
142
|
slots.description,
|
|
143
|
+
slots.meta,
|
|
137
144
|
slots.actions,
|
|
138
145
|
].filter((type): type is ElementType => type !== undefined);
|
|
139
146
|
const free = allowFreeContent
|
|
@@ -151,6 +158,7 @@ export function StructuralHeader({
|
|
|
151
158
|
titles.length +
|
|
152
159
|
counts.length +
|
|
153
160
|
descriptions.length +
|
|
161
|
+
metas.length +
|
|
154
162
|
actionSlots.length;
|
|
155
163
|
|
|
156
164
|
if (
|
|
@@ -159,6 +167,7 @@ export function StructuralHeader({
|
|
|
159
167
|
medias.length > 1 ||
|
|
160
168
|
counts.length > 1 ||
|
|
161
169
|
descriptions.length > 1 ||
|
|
170
|
+
metas.length > 1 ||
|
|
162
171
|
actionSlots.length > 1 ||
|
|
163
172
|
recognized !== nodes.length
|
|
164
173
|
) {
|
|
@@ -170,6 +179,7 @@ export function StructuralHeader({
|
|
|
170
179
|
...leadingTypes,
|
|
171
180
|
slots.media,
|
|
172
181
|
slots.description,
|
|
182
|
+
slots.meta,
|
|
173
183
|
slots.count,
|
|
174
184
|
slots.actions,
|
|
175
185
|
]
|
|
@@ -193,6 +203,10 @@ export function StructuralHeader({
|
|
|
193
203
|
);
|
|
194
204
|
}
|
|
195
205
|
|
|
206
|
+
if (metas.length > 0 && titles.length === 0) {
|
|
207
|
+
throw new Error(`${name} exige título para exibir metadados.`);
|
|
208
|
+
}
|
|
209
|
+
|
|
196
210
|
const titleLine = titles.length + counts.length > 0 && (
|
|
197
211
|
<Wrapper className="flex items-baseline gap-2">
|
|
198
212
|
{titles}
|
|
@@ -342,11 +356,19 @@ export function StructuralHeader({
|
|
|
342
356
|
</>
|
|
343
357
|
);
|
|
344
358
|
|
|
359
|
+
const withMeta = (
|
|
360
|
+
<>
|
|
361
|
+
{layout}
|
|
362
|
+
{metas}
|
|
363
|
+
</>
|
|
364
|
+
);
|
|
365
|
+
|
|
345
366
|
return (
|
|
346
367
|
<Root
|
|
347
368
|
data-slot={`${slot}-header`}
|
|
348
369
|
className={cn(
|
|
349
|
-
|
|
370
|
+
metas.length === 0 &&
|
|
371
|
+
contentClassName === undefined &&
|
|
350
372
|
leadingPlacement !== "inline" &&
|
|
351
373
|
leading.length === 0 &&
|
|
352
374
|
actionSlots.length > 0 &&
|
|
@@ -359,13 +381,13 @@ export function StructuralHeader({
|
|
|
359
381
|
{...props}
|
|
360
382
|
>
|
|
361
383
|
{contentClassName === undefined ? (
|
|
362
|
-
|
|
384
|
+
withMeta
|
|
363
385
|
) : (
|
|
364
386
|
<Wrapper
|
|
365
387
|
data-slot={`${slot}-header-content`}
|
|
366
388
|
className={contentClassName}
|
|
367
389
|
>
|
|
368
|
-
{
|
|
390
|
+
{withMeta}
|
|
369
391
|
</Wrapper>
|
|
370
392
|
)}
|
|
371
393
|
</Root>
|
|
@@ -42,6 +42,7 @@ import { headingTags, type HeadingLevel } from "./heading-level.ts";
|
|
|
42
42
|
import { usePageHeadingPresence } from "./page-heading-context.tsx";
|
|
43
43
|
import {
|
|
44
44
|
StructuralHeader,
|
|
45
|
+
structuralMetaClasses,
|
|
45
46
|
structuralHeaderClasses,
|
|
46
47
|
} from "./structural-header.tsx";
|
|
47
48
|
import {
|
|
@@ -113,6 +114,8 @@ interface SurfaceHeaderShorthandProps {
|
|
|
113
114
|
count?: number;
|
|
114
115
|
/** Forma curta: frase de apoio sob o título. */
|
|
115
116
|
description?: ReactNode;
|
|
117
|
+
/** Forma curta: badges, datas e autoria sob a orientação. */
|
|
118
|
+
meta?: ReactNode;
|
|
116
119
|
/** Forma curta: ações sobre a superfície inteira, no extremo oposto ao título. */
|
|
117
120
|
actions?: ReactNode;
|
|
118
121
|
}
|
|
@@ -124,6 +127,7 @@ interface SurfaceLegendShorthandProps {
|
|
|
124
127
|
media?: ReactNode;
|
|
125
128
|
count?: never;
|
|
126
129
|
description?: never;
|
|
130
|
+
meta?: never;
|
|
127
131
|
actions?: never;
|
|
128
132
|
}
|
|
129
133
|
|
|
@@ -132,6 +136,7 @@ interface SurfaceNoShorthandProps {
|
|
|
132
136
|
media?: never;
|
|
133
137
|
count?: never;
|
|
134
138
|
description?: never;
|
|
139
|
+
meta?: never;
|
|
135
140
|
actions?: never;
|
|
136
141
|
}
|
|
137
142
|
|
|
@@ -204,6 +209,7 @@ export function Surface({
|
|
|
204
209
|
media,
|
|
205
210
|
count,
|
|
206
211
|
description,
|
|
212
|
+
meta,
|
|
207
213
|
actions,
|
|
208
214
|
className,
|
|
209
215
|
children,
|
|
@@ -230,10 +236,11 @@ export function Surface({
|
|
|
230
236
|
(hasMedia(media) ||
|
|
231
237
|
count !== undefined ||
|
|
232
238
|
description !== undefined ||
|
|
239
|
+
meta !== undefined ||
|
|
233
240
|
actions !== undefined)
|
|
234
241
|
) {
|
|
235
242
|
throw new Error(
|
|
236
|
-
"Surface exige title para usar media, count, description ou actions.",
|
|
243
|
+
"Surface exige title para usar media, count, description, meta ou actions.",
|
|
237
244
|
);
|
|
238
245
|
}
|
|
239
246
|
if (asChild && shorthand) {
|
|
@@ -303,10 +310,11 @@ export function Surface({
|
|
|
303
310
|
<SurfaceHeader>
|
|
304
311
|
{hasMedia(media) && <SurfaceMedia>{media}</SurfaceMedia>}
|
|
305
312
|
<SurfaceTitle>{title}</SurfaceTitle>
|
|
306
|
-
{count !== undefined && <
|
|
313
|
+
{count !== undefined && <SurfaceCount>{count}</SurfaceCount>}
|
|
307
314
|
{description !== undefined && (
|
|
308
315
|
<SurfaceDescription>{description}</SurfaceDescription>
|
|
309
316
|
)}
|
|
317
|
+
{meta !== undefined && <SurfaceMeta>{meta}</SurfaceMeta>}
|
|
310
318
|
{actions !== undefined && <SurfaceActions>{actions}</SurfaceActions>}
|
|
311
319
|
</SurfaceHeader>
|
|
312
320
|
{regions > 0
|
|
@@ -370,7 +378,7 @@ export function Surface({
|
|
|
370
378
|
export type SurfaceHeaderProps = Omit<HTMLAttributes<HTMLDivElement>, "title">;
|
|
371
379
|
|
|
372
380
|
const legendInvalidMessage =
|
|
373
|
-
"SurfaceHeader de fieldset aceita somente SurfaceMedia e SurfaceTitle; contador, descrição e ações ficam fora da legenda.";
|
|
381
|
+
"SurfaceHeader de fieldset aceita somente SurfaceMedia e SurfaceTitle; contador, descrição, metadados e ações ficam fora da legenda.";
|
|
374
382
|
|
|
375
383
|
/**
|
|
376
384
|
* Header da superfície; não existe fora dela. Monta a linha sozinho — mídia, título com
|
|
@@ -401,7 +409,8 @@ export function SurfaceHeader({
|
|
|
401
409
|
: {
|
|
402
410
|
media: SurfaceMedia,
|
|
403
411
|
title: SurfaceTitle,
|
|
404
|
-
count:
|
|
412
|
+
count: SurfaceCount,
|
|
413
|
+
meta: SurfaceMeta,
|
|
405
414
|
description: SurfaceDescription,
|
|
406
415
|
actions: SurfaceActions,
|
|
407
416
|
}
|
|
@@ -509,25 +518,44 @@ export function SurfaceDescription({
|
|
|
509
518
|
);
|
|
510
519
|
}
|
|
511
520
|
|
|
512
|
-
/**
|
|
513
|
-
export function
|
|
521
|
+
/** Contador ao lado do título — o slot do `count` da forma curta. */
|
|
522
|
+
export function SurfaceCount({
|
|
514
523
|
className,
|
|
515
524
|
...props
|
|
516
525
|
}: HTMLAttributes<HTMLSpanElement>): ReactElement {
|
|
517
526
|
const surface = requiredContext(
|
|
518
527
|
useContext(SurfaceHeaderContext),
|
|
519
|
-
"
|
|
528
|
+
"SurfaceCount",
|
|
520
529
|
"SurfaceHeader",
|
|
521
530
|
);
|
|
522
531
|
return (
|
|
523
532
|
<span
|
|
524
|
-
data-slot="surface-
|
|
533
|
+
data-slot="surface-count"
|
|
525
534
|
className={cn(structuralHeaderClasses[surface.variant].count, className)}
|
|
526
535
|
{...props}
|
|
527
536
|
/>
|
|
528
537
|
);
|
|
529
538
|
}
|
|
530
539
|
|
|
540
|
+
/** Badges, datas e autoria sob o título e a orientação da superfície. */
|
|
541
|
+
export function SurfaceMeta({
|
|
542
|
+
className,
|
|
543
|
+
...props
|
|
544
|
+
}: HTMLAttributes<HTMLDivElement>): ReactElement {
|
|
545
|
+
requiredContext(
|
|
546
|
+
useContext(SurfaceHeaderContext),
|
|
547
|
+
"SurfaceMeta",
|
|
548
|
+
"SurfaceHeader",
|
|
549
|
+
);
|
|
550
|
+
return (
|
|
551
|
+
<div
|
|
552
|
+
data-slot="surface-meta"
|
|
553
|
+
className={cn(structuralMetaClasses, className)}
|
|
554
|
+
{...props}
|
|
555
|
+
/>
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
|
|
531
559
|
/** Ações do header — a família as posiciona no extremo oposto ao título. */
|
|
532
560
|
export function SurfaceActions({
|
|
533
561
|
className,
|
|
@@ -16,6 +16,7 @@ import { FillBoundary } from "../patterns/fill-region.ts";
|
|
|
16
16
|
import { headingTags, type HeadingLevel } from "../patterns/heading-level.ts";
|
|
17
17
|
import {
|
|
18
18
|
StructuralHeader,
|
|
19
|
+
structuralMetaClasses,
|
|
19
20
|
structuralHeaderClasses,
|
|
20
21
|
validateSurfaceIntro,
|
|
21
22
|
} from "../patterns/structural-header.tsx";
|
|
@@ -67,6 +68,7 @@ const DialogContentContext = React.createContext<DialogContentContextValue>({
|
|
|
67
68
|
closeSize: "sm",
|
|
68
69
|
});
|
|
69
70
|
|
|
71
|
+
const DialogMetaContext = React.createContext(false);
|
|
70
72
|
const DialogIntroContext = React.createContext(false);
|
|
71
73
|
|
|
72
74
|
/**
|
|
@@ -589,33 +591,36 @@ export function DialogHeader({
|
|
|
589
591
|
}
|
|
590
592
|
|
|
591
593
|
return (
|
|
592
|
-
<
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
"
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
594
|
+
<DialogMetaContext.Provider value>
|
|
595
|
+
<StructuralHeader
|
|
596
|
+
name="DialogHeader"
|
|
597
|
+
slot="dialog"
|
|
598
|
+
slots={{
|
|
599
|
+
leading: DialogNavigation,
|
|
600
|
+
media: DialogMedia,
|
|
601
|
+
title: DialogTitle,
|
|
602
|
+
description: DialogDescription,
|
|
603
|
+
meta: DialogMeta,
|
|
604
|
+
actions: DialogActions,
|
|
605
|
+
}}
|
|
606
|
+
leadingPlacement="inline"
|
|
607
|
+
titleRequired={false}
|
|
608
|
+
rowClassName={surfaceHeaderRowClasses("dialog")}
|
|
609
|
+
className={cn(
|
|
610
|
+
"text-left",
|
|
611
|
+
surfaceRegionClasses("dialog", layout, "header"),
|
|
612
|
+
className,
|
|
613
|
+
)}
|
|
614
|
+
trailing={
|
|
615
|
+
showCloseButton ? (
|
|
616
|
+
<DialogCloseButton disabled={closeDisabled} size={closeSize} />
|
|
617
|
+
) : undefined
|
|
618
|
+
}
|
|
619
|
+
{...props}
|
|
620
|
+
>
|
|
621
|
+
{children}
|
|
622
|
+
</StructuralHeader>
|
|
623
|
+
</DialogMetaContext.Provider>
|
|
619
624
|
);
|
|
620
625
|
}
|
|
621
626
|
|
|
@@ -655,25 +660,52 @@ export function DialogIntro({
|
|
|
655
660
|
}: React.ComponentProps<"div">) {
|
|
656
661
|
return (
|
|
657
662
|
<DialogIntroContext.Provider value>
|
|
658
|
-
<
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
663
|
+
<DialogMetaContext.Provider value>
|
|
664
|
+
<StructuralHeader
|
|
665
|
+
name="DialogIntro"
|
|
666
|
+
slot="dialog-intro"
|
|
667
|
+
slots={{
|
|
668
|
+
title: DialogTitle,
|
|
669
|
+
description: DialogDescription,
|
|
670
|
+
meta: DialogMeta,
|
|
671
|
+
actions: DialogIntroActions,
|
|
672
|
+
}}
|
|
673
|
+
leadingPlacement="inline"
|
|
674
|
+
titleRequired
|
|
675
|
+
className={cn("text-left", className)}
|
|
676
|
+
{...props}
|
|
677
|
+
>
|
|
678
|
+
{children}
|
|
679
|
+
</StructuralHeader>
|
|
680
|
+
</DialogMetaContext.Provider>
|
|
673
681
|
</DialogIntroContext.Provider>
|
|
674
682
|
);
|
|
675
683
|
}
|
|
676
684
|
|
|
685
|
+
/** Badges, datas e autoria abaixo do título e da orientação. */
|
|
686
|
+
export function DialogMeta({
|
|
687
|
+
className,
|
|
688
|
+
...props
|
|
689
|
+
}: React.ComponentProps<"div">) {
|
|
690
|
+
const { mode } = useDialogContext();
|
|
691
|
+
const inside = React.useContext(DialogMetaContext);
|
|
692
|
+
if (mode === "alert")
|
|
693
|
+
throw new Error(
|
|
694
|
+
'DialogMeta não está disponível em Dialog com mode="alert"; coloque as informações no corpo do alerta.',
|
|
695
|
+
);
|
|
696
|
+
if (!inside)
|
|
697
|
+
throw new Error(
|
|
698
|
+
"DialogMeta deve estar dentro de DialogHeader ou DialogIntro.",
|
|
699
|
+
);
|
|
700
|
+
return (
|
|
701
|
+
<div
|
|
702
|
+
data-slot="dialog-meta"
|
|
703
|
+
className={cn(structuralMetaClasses, className)}
|
|
704
|
+
{...props}
|
|
705
|
+
/>
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
|
|
677
709
|
/** Ações relacionadas ao título do conteúdo, separadas das ações de chrome do header. */
|
|
678
710
|
export function DialogIntroActions({
|
|
679
711
|
className,
|