@softize/opus 29.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 +21 -0
- package/bin/lib/check.mjs +86 -10
- package/docs/adr/0045-surface-replaces-content-and-card.md +3 -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 +4 -0
- package/package.json +1 -1
- package/registry/skills/build-opus-ui/SKILL.md +1 -1
- package/registry/skills/build-opus-ui/references/evaluations.md +10 -1
- package/registry/skills/build-opus-ui/references/ui-patterns.md +10 -2
- package/src/core/types.ts +1 -1
- package/src/ui/components/patterns/list.tsx +18 -9
- 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 +14 -7
- package/src/ui/docs/content/dialog.md +39 -0
- package/src/ui/docs/content/drawer.md +36 -0
- package/src/ui/docs/content/page.md +30 -1
- package/src/ui/docs/content/surface.md +41 -8
- package/src/ui/docs/content/table.md +7 -0
- package/src/ui/react.tsx +4 -0
|
@@ -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,
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
import { headingTags, type HeadingLevel } from "../patterns/heading-level.ts";
|
|
16
16
|
import {
|
|
17
17
|
StructuralHeader,
|
|
18
|
+
structuralMetaClasses,
|
|
18
19
|
structuralHeaderClasses,
|
|
19
20
|
validateSurfaceIntro,
|
|
20
21
|
} from "../patterns/structural-header.tsx";
|
|
@@ -50,6 +51,7 @@ const DrawerContentContext = React.createContext<DrawerContentContextValue>({
|
|
|
50
51
|
closeSize: "sm",
|
|
51
52
|
});
|
|
52
53
|
|
|
54
|
+
const DrawerMetaContext = React.createContext(false);
|
|
53
55
|
const DrawerIntroContext = React.createContext(false);
|
|
54
56
|
|
|
55
57
|
/** Estado de aberto que a pilha de modais da camada flutuante acompanha (ADR 0052). */
|
|
@@ -257,33 +259,36 @@ function DrawerHeader({
|
|
|
257
259
|
const { showCloseButton, closeDisabled, closeSize } =
|
|
258
260
|
React.useContext(DrawerContentContext);
|
|
259
261
|
return (
|
|
260
|
-
<
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
"
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
262
|
+
<DrawerMetaContext.Provider value>
|
|
263
|
+
<StructuralHeader
|
|
264
|
+
name="DrawerHeader"
|
|
265
|
+
slot="drawer"
|
|
266
|
+
slots={{
|
|
267
|
+
leading: DrawerNavigation,
|
|
268
|
+
media: DrawerMedia,
|
|
269
|
+
title: DrawerTitle,
|
|
270
|
+
description: DrawerDescription,
|
|
271
|
+
meta: DrawerMeta,
|
|
272
|
+
actions: DrawerActions,
|
|
273
|
+
}}
|
|
274
|
+
leadingPlacement="inline"
|
|
275
|
+
titleRequired={false}
|
|
276
|
+
rowClassName={surfaceHeaderRowClasses("drawer")}
|
|
277
|
+
className={cn(
|
|
278
|
+
"text-left",
|
|
279
|
+
surfaceRegionClasses("drawer", layout, "header"),
|
|
280
|
+
className,
|
|
281
|
+
)}
|
|
282
|
+
trailing={
|
|
283
|
+
showCloseButton ? (
|
|
284
|
+
<DrawerCloseButton disabled={closeDisabled} size={closeSize} />
|
|
285
|
+
) : undefined
|
|
286
|
+
}
|
|
287
|
+
{...props}
|
|
288
|
+
>
|
|
289
|
+
{children}
|
|
290
|
+
</StructuralHeader>
|
|
291
|
+
</DrawerMetaContext.Provider>
|
|
287
292
|
);
|
|
288
293
|
}
|
|
289
294
|
|
|
@@ -325,25 +330,47 @@ function DrawerIntro({
|
|
|
325
330
|
}: React.ComponentProps<"div">) {
|
|
326
331
|
return (
|
|
327
332
|
<DrawerIntroContext.Provider value>
|
|
328
|
-
<
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
333
|
+
<DrawerMetaContext.Provider value>
|
|
334
|
+
<StructuralHeader
|
|
335
|
+
name="DrawerIntro"
|
|
336
|
+
slot="drawer-intro"
|
|
337
|
+
slots={{
|
|
338
|
+
title: DrawerTitle,
|
|
339
|
+
description: DrawerDescription,
|
|
340
|
+
meta: DrawerMeta,
|
|
341
|
+
actions: DrawerIntroActions,
|
|
342
|
+
}}
|
|
343
|
+
leadingPlacement="inline"
|
|
344
|
+
titleRequired
|
|
345
|
+
className={cn("text-left", className)}
|
|
346
|
+
{...props}
|
|
347
|
+
>
|
|
348
|
+
{children}
|
|
349
|
+
</StructuralHeader>
|
|
350
|
+
</DrawerMetaContext.Provider>
|
|
343
351
|
</DrawerIntroContext.Provider>
|
|
344
352
|
);
|
|
345
353
|
}
|
|
346
354
|
|
|
355
|
+
/** Badges, datas e autoria abaixo do título e da orientação. */
|
|
356
|
+
export function DrawerMeta({
|
|
357
|
+
className,
|
|
358
|
+
...props
|
|
359
|
+
}: React.ComponentProps<"div">) {
|
|
360
|
+
const inside = React.useContext(DrawerMetaContext);
|
|
361
|
+
if (!inside)
|
|
362
|
+
throw new Error(
|
|
363
|
+
"DrawerMeta deve estar dentro de DrawerHeader ou DrawerIntro.",
|
|
364
|
+
);
|
|
365
|
+
return (
|
|
366
|
+
<div
|
|
367
|
+
data-slot="drawer-meta"
|
|
368
|
+
className={cn(structuralMetaClasses, className)}
|
|
369
|
+
{...props}
|
|
370
|
+
/>
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
|
|
347
374
|
/** Ações relacionadas ao título do conteúdo, separadas das ações de chrome do header. */
|
|
348
375
|
function DrawerIntroActions({
|
|
349
376
|
className,
|
|
@@ -24,7 +24,7 @@ function Table({
|
|
|
24
24
|
data-slot="table-container"
|
|
25
25
|
data-variant={variant}
|
|
26
26
|
className={cn(
|
|
27
|
-
"relative w-full overflow-x-auto",
|
|
27
|
+
"relative w-full min-w-0 max-w-full overflow-x-auto",
|
|
28
28
|
variant === "framed" && "rounded-lg border border-border",
|
|
29
29
|
containerClassName,
|
|
30
30
|
)}
|
|
@@ -95,7 +95,7 @@ function TableHead({ className, ...props }: React.ComponentProps<"th">) {
|
|
|
95
95
|
<th
|
|
96
96
|
data-slot="table-head"
|
|
97
97
|
className={cn(
|
|
98
|
-
"h-10 px-3 text-left align-middle font-medium whitespace-
|
|
98
|
+
"h-10 px-3 text-left align-middle font-medium whitespace-normal text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[0.125rem]",
|
|
99
99
|
className,
|
|
100
100
|
)}
|
|
101
101
|
{...props}
|
|
@@ -108,7 +108,7 @@ function TableCell({ className, ...props }: React.ComponentProps<"td">) {
|
|
|
108
108
|
<td
|
|
109
109
|
data-slot="table-cell"
|
|
110
110
|
className={cn(
|
|
111
|
-
"px-3 py-2 align-middle whitespace-
|
|
111
|
+
"px-3 py-2 align-middle whitespace-normal wrap-break-word [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[0.125rem]",
|
|
112
112
|
className,
|
|
113
113
|
)}
|
|
114
114
|
{...props}
|
|
@@ -10,13 +10,20 @@ limites visuais da coleção; use [`Table`](/ui/table) diretamente quando a comp
|
|
|
10
10
|
variante `framed`. Carregamento, erro e vazio usam o mesmo [`DataState`](/ui/data-state) das
|
|
11
11
|
demais coleções.
|
|
12
12
|
|
|
13
|
-
A
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
A tabela usa a largura disponível e permite quebra em células textuais. Colunas com `fit`
|
|
14
|
+
preservam valores compactos sem quebra. A largura das colunas de dados considera cabeçalhos e
|
|
15
|
+
valores, sem comprimir todas as colunas `fit` para ampliar a primeira. Os cabeçalhos quebram
|
|
16
|
+
quando falta espaço; seleção, ações de linha e células contendo apenas um switch
|
|
17
|
+
(direto ou envolvido por TooltipTrigger) mantêm a largura compacta.
|
|
18
|
+
Se identificadores sem espaços ou larguras mínimas explícitas excederem o espaço, a rolagem
|
|
19
|
+
fica nos resultados, sem alargar a toolbar ou o rodapé. Em `layout="fill"`, o cabeçalho continua fixo nessa região.
|
|
20
|
+
|
|
21
|
+
A barra distribui os controles `inline` em mais de uma linha quando necessário, preservando sua
|
|
22
|
+
ordem. Ela não move filtros automaticamente para o modal. Seletores de valor único acompanham o
|
|
23
|
+
valor escolhido até `20rem` e truncam o excedente; a busca varia entre `8rem` e `16rem`.
|
|
24
|
+
Declare como `advanced` os filtros que não precisam ficar sempre à vista, como descreve a seção
|
|
25
|
+
“Onde cada filtro aparece”. Recarregar e exibição formam um `ButtonGroup` espaçado;
|
|
26
|
+
`toolbarActions` acrescenta, depois deles, operações ligadas ao recorte atual.
|
|
20
27
|
|
|
21
28
|
```tsx preview col
|
|
22
29
|
render(
|
|
@@ -460,6 +460,32 @@ botão. O texto informa o efeito real, como `Excluir`, `Revogar acesso` ou `Arqu
|
|
|
460
460
|
age onde a pessoa está. O modo `alert` cobre a camada até a resposta.
|
|
461
461
|
- `DialogHost` apresenta uma solicitação por vez para que cada resposta tenha um alvo inequívoco.
|
|
462
462
|
|
|
463
|
+
## Metadados junto à identidade
|
|
464
|
+
|
|
465
|
+
Use `DialogMeta` para badges, autoria e datas relacionados ao registro. A faixa fica
|
|
466
|
+
abaixo da linha de título e ações, com espaço próprio; `DialogDescription` continua
|
|
467
|
+
reservado à orientação textual breve. Os itens quebram em várias linhas quando necessário.
|
|
468
|
+
|
|
469
|
+
```tsx
|
|
470
|
+
<Dialog defaultOpen>
|
|
471
|
+
<DialogContent>
|
|
472
|
+
<DialogHeader />
|
|
473
|
+
<DialogBody>
|
|
474
|
+
<DialogIntro>
|
|
475
|
+
<DialogTitle>Manual de atendimento</DialogTitle>
|
|
476
|
+
<DialogDescription>Orientações para a equipe.</DialogDescription>
|
|
477
|
+
<DialogMeta><Badge>Publicado</Badge><span>Por Ana</span></DialogMeta>
|
|
478
|
+
</DialogIntro>
|
|
479
|
+
<p>Conteúdo do manual.</p>
|
|
480
|
+
</DialogBody>
|
|
481
|
+
</DialogContent>
|
|
482
|
+
</Dialog>
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
A composição aceita um único `DialogMeta` e exige um `DialogTitle`. Na forma explícita, coloque-o em `DialogIntro` ou no `DialogHeader` compacto. Não coloque o slot solto no corpo.
|
|
486
|
+
|
|
487
|
+
O modo `alert` conserva sua composição especializada e não recebe `DialogMeta`.
|
|
488
|
+
|
|
463
489
|
## Propriedades de Dialog
|
|
464
490
|
|
|
465
491
|
| Propriedade | Tipo | Padrão | Descrição |
|
|
@@ -668,3 +694,16 @@ Use `flush` quando o conteúdo controlar seus próprios recuos.
|
|
|
668
694
|
|
|
669
695
|
Na intro, `DialogTitle` usa a mesma escala de `PageTitle` (`1.875rem`). No header
|
|
670
696
|
compacto, o título conserva a escala de `1.125rem`. O nível semântico não altera essas medidas.
|
|
697
|
+
|
|
698
|
+
## Propriedades de DialogMeta
|
|
699
|
+
|
|
700
|
+
| Propriedade | Tipo | Descrição |
|
|
701
|
+
| --- | --- | --- |
|
|
702
|
+
| `children` | `ReactNode` | Badges, datas, autoria ou outros metadados do registro. |
|
|
703
|
+
| `className` | `string` | Ajusta a faixa de metadados. |
|
|
704
|
+
|
|
705
|
+
Aceita atributos nativos de `div`. O intervalo superior é de 0,75rem; os itens têm
|
|
706
|
+
0,75rem entre colunas e 0,5rem entre linhas. O componente não insere separadores nem
|
|
707
|
+
carrega dados. Omitir o slot não reserva espaço. Metadados não substituem orientações
|
|
708
|
+
ou condições necessárias à decisão e não são anunciados como descrição do modal.
|
|
709
|
+
`DialogMeta` não se aplica a `mode="alert"`; informações necessárias à resposta ficam no corpo do alerta.
|