@softize/opus 16.0.0 → 17.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 +39 -0
- package/bin/lib/check.mjs +9 -13
- package/bin/lib/copy.mjs +29 -4
- package/docs/adr/0005-structural-surfaces-share-an-explicit-anatomy.md +6 -3
- package/docs/adr/0009-page-title-does-not-carry-a-counter.md +5 -2
- package/docs/adr/0011-page-shell-coordinates-persistent-page-chrome.md +12 -14
- package/docs/adr/0012-modal-header-only-names-the-surface.md +8 -4
- package/docs/adr/0013-presentation-is-a-portable-action-oriented-artifact.md +7 -4
- package/docs/adr/0014-structural-headers-do-not-carry-description.md +35 -0
- package/package.json +2 -2
- package/registry/skills/build-opus-ui/references/evaluations.md +1 -1
- package/registry/skills/build-opus-ui/references/ui-patterns.md +16 -17
- package/registry/templates/app/package.json +1 -1
- package/src/core/presentation.ts +142 -13
- package/src/ui/components/patterns/form.tsx +29 -10
- package/src/ui/components/patterns/page.tsx +93 -48
- package/src/ui/components/patterns/presentation.tsx +487 -134
- package/src/ui/components/patterns/surface-header.tsx +4 -3
- package/src/ui/components/patterns/trigger.tsx +8 -1
- package/src/ui/components/patterns/view.tsx +2 -2
- package/src/ui/components/primitives/command.tsx +82 -42
- package/src/ui/components/primitives/dialog.tsx +180 -97
- package/src/ui/components/primitives/drawer.tsx +63 -22
- package/src/ui/docs/content/command.md +3 -1
- package/src/ui/docs/content/content.md +1 -1
- package/src/ui/docs/content/dialog.md +9 -9
- package/src/ui/docs/content/drawer.md +4 -4
- package/src/ui/docs/content/page.md +14 -29
- package/src/ui/docs/content/presentation.md +54 -46
- package/src/ui/meta.ts +2 -2
- package/src/ui/react.tsx +1 -2
|
@@ -1,80 +1,111 @@
|
|
|
1
|
-
import * as React from
|
|
2
|
-
import * as DialogPrimitive from
|
|
3
|
-
import { AlertDialog as AlertDialogPrimitive } from
|
|
4
|
-
import { XIcon } from
|
|
5
|
-
import { cn } from
|
|
6
|
-
import {
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
3
|
+
import { AlertDialog as AlertDialogPrimitive } from "radix-ui";
|
|
4
|
+
import { XIcon } from "lucide-react";
|
|
5
|
+
import { cn } from "../../lib/cn.ts";
|
|
6
|
+
import { Button } from "./button.tsx";
|
|
7
7
|
|
|
8
|
-
export type DialogMode =
|
|
8
|
+
export type DialogMode = "default" | "alert";
|
|
9
9
|
|
|
10
10
|
interface DialogContextValue {
|
|
11
|
-
mode: DialogMode
|
|
12
|
-
onResult?: (result: string) => void
|
|
11
|
+
mode: DialogMode;
|
|
12
|
+
onResult?: (result: string) => void;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
const DialogContext = React.createContext<DialogContextValue>({
|
|
16
|
-
mode:
|
|
17
|
-
})
|
|
16
|
+
mode: "default",
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
interface DialogContentContextValue {
|
|
20
|
+
showCloseButton: boolean;
|
|
21
|
+
closeDisabled: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const DialogContentContext = React.createContext<DialogContentContextValue>({
|
|
25
|
+
showCloseButton: false,
|
|
26
|
+
closeDisabled: false,
|
|
27
|
+
});
|
|
18
28
|
|
|
19
29
|
function useDialogContext(): DialogContextValue {
|
|
20
|
-
return React.useContext(DialogContext)
|
|
30
|
+
return React.useContext(DialogContext);
|
|
21
31
|
}
|
|
22
32
|
|
|
23
|
-
export interface DialogProps extends React.ComponentProps<
|
|
33
|
+
export interface DialogProps extends React.ComponentProps<
|
|
34
|
+
typeof DialogPrimitive.Root
|
|
35
|
+
> {
|
|
24
36
|
/** `alert` exige uma resposta explícita e deriva `role="alertdialog"`. */
|
|
25
|
-
mode?: DialogMode
|
|
37
|
+
mode?: DialogMode;
|
|
26
38
|
/** Recebe o `result` do `DialogClose` acionado. */
|
|
27
|
-
onResult?: (result: string) => void
|
|
39
|
+
onResult?: (result: string) => void;
|
|
28
40
|
}
|
|
29
41
|
|
|
30
|
-
export function Dialog({
|
|
31
|
-
|
|
42
|
+
export function Dialog({
|
|
43
|
+
mode = "default",
|
|
44
|
+
onResult,
|
|
45
|
+
modal,
|
|
46
|
+
...props
|
|
47
|
+
}: DialogProps) {
|
|
48
|
+
const context = React.useMemo(() => ({ mode, onResult }), [mode, onResult]);
|
|
32
49
|
|
|
33
50
|
return (
|
|
34
51
|
<DialogContext.Provider value={context}>
|
|
35
|
-
{mode ===
|
|
52
|
+
{mode === "alert" ? (
|
|
36
53
|
<AlertDialogPrimitive.Root data-slot="dialog" {...props} />
|
|
37
54
|
) : (
|
|
38
55
|
<DialogPrimitive.Root data-slot="dialog" modal={modal} {...props} />
|
|
39
56
|
)}
|
|
40
57
|
</DialogContext.Provider>
|
|
41
|
-
)
|
|
58
|
+
);
|
|
42
59
|
}
|
|
43
60
|
|
|
44
|
-
export function DialogTrigger({
|
|
45
|
-
|
|
46
|
-
|
|
61
|
+
export function DialogTrigger({
|
|
62
|
+
...props
|
|
63
|
+
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
|
|
64
|
+
const { mode } = useDialogContext();
|
|
65
|
+
return mode === "alert" ? (
|
|
47
66
|
<AlertDialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
|
|
48
67
|
) : (
|
|
49
68
|
<DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
|
|
50
|
-
)
|
|
69
|
+
);
|
|
51
70
|
}
|
|
52
71
|
|
|
53
|
-
export function DialogPortal({
|
|
54
|
-
|
|
55
|
-
|
|
72
|
+
export function DialogPortal({
|
|
73
|
+
...props
|
|
74
|
+
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
|
|
75
|
+
const { mode } = useDialogContext();
|
|
76
|
+
return mode === "alert" ? (
|
|
56
77
|
<AlertDialogPrimitive.Portal data-slot="dialog-portal" {...props} />
|
|
57
78
|
) : (
|
|
58
79
|
<DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
|
|
59
|
-
)
|
|
80
|
+
);
|
|
60
81
|
}
|
|
61
82
|
|
|
62
|
-
export interface DialogCloseProps extends React.ComponentProps<
|
|
83
|
+
export interface DialogCloseProps extends React.ComponentProps<
|
|
84
|
+
typeof DialogPrimitive.Close
|
|
85
|
+
> {
|
|
63
86
|
/** No modo `alert`, registra este controle como destino inicial seguro do foco. */
|
|
64
|
-
initialFocus?: boolean
|
|
87
|
+
initialFocus?: boolean;
|
|
65
88
|
/** Resposta entregue a `Dialog.onResult` antes do fechamento. */
|
|
66
|
-
result?: string
|
|
89
|
+
result?: string;
|
|
67
90
|
}
|
|
68
91
|
|
|
69
|
-
export function DialogClose({
|
|
70
|
-
|
|
92
|
+
export function DialogClose({
|
|
93
|
+
initialFocus = false,
|
|
94
|
+
result,
|
|
95
|
+
onClick,
|
|
96
|
+
...props
|
|
97
|
+
}: DialogCloseProps) {
|
|
98
|
+
const root = useDialogContext();
|
|
71
99
|
const handleClick: React.MouseEventHandler<HTMLButtonElement> = (event) => {
|
|
72
|
-
onClick?.(event)
|
|
73
|
-
if (!event.defaultPrevented && result !== undefined)
|
|
74
|
-
|
|
100
|
+
onClick?.(event);
|
|
101
|
+
if (!event.defaultPrevented && result !== undefined)
|
|
102
|
+
root.onResult?.(result);
|
|
103
|
+
};
|
|
75
104
|
|
|
76
|
-
if (root.mode ===
|
|
77
|
-
const ClosePrimitive = initialFocus
|
|
105
|
+
if (root.mode === "alert") {
|
|
106
|
+
const ClosePrimitive = initialFocus
|
|
107
|
+
? AlertDialogPrimitive.Cancel
|
|
108
|
+
: AlertDialogPrimitive.Action;
|
|
78
109
|
return (
|
|
79
110
|
<ClosePrimitive
|
|
80
111
|
data-slot="dialog-close"
|
|
@@ -82,7 +113,7 @@ export function DialogClose({ initialFocus = false, result, onClick, ...props }:
|
|
|
82
113
|
onClick={handleClick}
|
|
83
114
|
{...props}
|
|
84
115
|
/>
|
|
85
|
-
)
|
|
116
|
+
);
|
|
86
117
|
}
|
|
87
118
|
|
|
88
119
|
return (
|
|
@@ -92,53 +123,85 @@ export function DialogClose({ initialFocus = false, result, onClick, ...props }:
|
|
|
92
123
|
onClick={handleClick}
|
|
93
124
|
{...props}
|
|
94
125
|
/>
|
|
95
|
-
)
|
|
126
|
+
);
|
|
96
127
|
}
|
|
97
128
|
|
|
98
129
|
/** Véu compartilhado por Dialog e Drawer: mesmo escurecimento, desfoque e animação. */
|
|
99
130
|
export const overlayClasses =
|
|
100
|
-
|
|
131
|
+
"fixed inset-0 z-50 bg-black/30 backdrop-blur-xs data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0";
|
|
101
132
|
|
|
102
|
-
export function DialogOverlay({
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
133
|
+
export function DialogOverlay({
|
|
134
|
+
className,
|
|
135
|
+
...props
|
|
136
|
+
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
|
|
137
|
+
const { mode } = useDialogContext();
|
|
138
|
+
const overlay = cn(overlayClasses, className);
|
|
139
|
+
return mode === "alert" ? (
|
|
140
|
+
<AlertDialogPrimitive.Overlay
|
|
141
|
+
data-slot="dialog-overlay"
|
|
142
|
+
className={overlay}
|
|
143
|
+
{...props}
|
|
144
|
+
/>
|
|
107
145
|
) : (
|
|
108
|
-
<DialogPrimitive.Overlay
|
|
109
|
-
|
|
146
|
+
<DialogPrimitive.Overlay
|
|
147
|
+
data-slot="dialog-overlay"
|
|
148
|
+
className={overlay}
|
|
149
|
+
{...props}
|
|
150
|
+
/>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function DialogCloseButton({ disabled = false }: { disabled?: boolean }) {
|
|
155
|
+
return (
|
|
156
|
+
<DialogPrimitive.Close data-slot="dialog-close" asChild>
|
|
157
|
+
<Button
|
|
158
|
+
type="button"
|
|
159
|
+
size="icon-sm"
|
|
160
|
+
variant="ghost"
|
|
161
|
+
disabled={disabled}
|
|
162
|
+
aria-label="Fechar"
|
|
163
|
+
>
|
|
164
|
+
<XIcon aria-hidden />
|
|
165
|
+
</Button>
|
|
166
|
+
</DialogPrimitive.Close>
|
|
167
|
+
);
|
|
110
168
|
}
|
|
111
169
|
|
|
112
170
|
export function DialogContent({
|
|
113
171
|
className,
|
|
114
172
|
children,
|
|
115
173
|
showCloseButton,
|
|
116
|
-
|
|
174
|
+
closeDisabled = false,
|
|
175
|
+
"aria-describedby": ariaDescribedBy,
|
|
117
176
|
...props
|
|
118
177
|
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
|
|
119
|
-
/** Mostra
|
|
120
|
-
showCloseButton?: boolean
|
|
178
|
+
/** Mostra a ação “Fechar” no modo padrão. O modo `alert` exige uma resposta identificável. */
|
|
179
|
+
showCloseButton?: boolean;
|
|
180
|
+
/** Mantém a ação visível, mas impede fechar enquanto a superfície está bloqueada. */
|
|
181
|
+
closeDisabled?: boolean;
|
|
121
182
|
}) {
|
|
122
|
-
const { mode } = useDialogContext()
|
|
123
|
-
const showClose = showCloseButton ?? mode !==
|
|
183
|
+
const { mode } = useDialogContext();
|
|
184
|
+
const showClose = showCloseButton ?? mode !== "alert";
|
|
124
185
|
const contentClasses =
|
|
125
|
-
|
|
186
|
+
"fixed left-[50%] top-[50%] z-50 w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] overflow-clip rounded-xl border border-border bg-background text-foreground shadow-lg duration-200 outline-none data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95";
|
|
126
187
|
|
|
127
|
-
if (mode ===
|
|
188
|
+
if (mode === "alert") {
|
|
128
189
|
return (
|
|
129
190
|
<DialogPortal>
|
|
130
191
|
<DialogOverlay />
|
|
131
192
|
<AlertDialogPrimitive.Content
|
|
132
193
|
data-slot="dialog-content"
|
|
133
194
|
data-mode="alert"
|
|
134
|
-
className={cn(contentClasses,
|
|
195
|
+
className={cn(contentClasses, "grid max-w-xs gap-4 p-6", className)}
|
|
135
196
|
aria-describedby={ariaDescribedBy}
|
|
136
197
|
{...props}
|
|
137
198
|
>
|
|
138
|
-
{
|
|
199
|
+
<DialogContentContext.Provider value={{ showCloseButton: false, closeDisabled: true }}>
|
|
200
|
+
{children}
|
|
201
|
+
</DialogContentContext.Provider>
|
|
139
202
|
</AlertDialogPrimitive.Content>
|
|
140
203
|
</DialogPortal>
|
|
141
|
-
)
|
|
204
|
+
);
|
|
142
205
|
}
|
|
143
206
|
|
|
144
207
|
return (
|
|
@@ -147,45 +210,50 @@ export function DialogContent({
|
|
|
147
210
|
<DialogPrimitive.Content
|
|
148
211
|
data-slot="dialog-content"
|
|
149
212
|
data-mode="default"
|
|
150
|
-
className={cn(
|
|
213
|
+
className={cn(
|
|
214
|
+
contentClasses,
|
|
215
|
+
"flex max-h-[85vh] flex-col sm:max-w-lg",
|
|
216
|
+
className,
|
|
217
|
+
)}
|
|
151
218
|
aria-describedby={ariaDescribedBy}
|
|
152
219
|
{...props}
|
|
153
220
|
>
|
|
154
|
-
{
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
data-slot="dialog-close"
|
|
158
|
-
className={cn(
|
|
159
|
-
"absolute right-4 top-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus-visible:outline-hidden disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
160
|
-
focusRing,
|
|
161
|
-
)}
|
|
162
|
-
>
|
|
163
|
-
<XIcon />
|
|
164
|
-
<span className="sr-only">Fechar</span>
|
|
165
|
-
</DialogPrimitive.Close>
|
|
166
|
-
)}
|
|
221
|
+
<DialogContentContext.Provider value={{ showCloseButton: showClose, closeDisabled }}>
|
|
222
|
+
{children}
|
|
223
|
+
</DialogContentContext.Provider>
|
|
167
224
|
</DialogPrimitive.Content>
|
|
168
225
|
</DialogPortal>
|
|
169
|
-
)
|
|
226
|
+
);
|
|
170
227
|
}
|
|
171
228
|
|
|
172
|
-
export function DialogHeader({
|
|
173
|
-
|
|
229
|
+
export function DialogHeader({
|
|
230
|
+
className,
|
|
231
|
+
children,
|
|
232
|
+
...props
|
|
233
|
+
}: React.ComponentProps<"div">) {
|
|
234
|
+
const { mode } = useDialogContext();
|
|
235
|
+
const { showCloseButton, closeDisabled } = React.useContext(DialogContentContext);
|
|
174
236
|
return (
|
|
175
237
|
<div
|
|
176
238
|
data-slot="dialog-header"
|
|
177
239
|
className={cn(
|
|
178
|
-
mode ===
|
|
179
|
-
?
|
|
180
|
-
:
|
|
240
|
+
mode === "alert"
|
|
241
|
+
? "grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=dialog-media]:grid-rows-[auto_auto_1fr]"
|
|
242
|
+
: "flex shrink-0 items-center gap-4 border-b p-5 text-left [&:has(+[data-slot=dialog-footer])]:border-b-0",
|
|
181
243
|
className,
|
|
182
244
|
)}
|
|
183
245
|
{...props}
|
|
184
|
-
|
|
185
|
-
|
|
246
|
+
>
|
|
247
|
+
{children}
|
|
248
|
+
{mode !== "alert" && showCloseButton && <DialogCloseButton disabled={closeDisabled} />}
|
|
249
|
+
</div>
|
|
250
|
+
);
|
|
186
251
|
}
|
|
187
252
|
|
|
188
|
-
export function DialogMedia({
|
|
253
|
+
export function DialogMedia({
|
|
254
|
+
className,
|
|
255
|
+
...props
|
|
256
|
+
}: React.ComponentProps<"div">) {
|
|
189
257
|
return (
|
|
190
258
|
<div
|
|
191
259
|
data-slot="dialog-media"
|
|
@@ -195,49 +263,64 @@ export function DialogMedia({ className, ...props }: React.ComponentProps<'div'>
|
|
|
195
263
|
)}
|
|
196
264
|
{...props}
|
|
197
265
|
/>
|
|
198
|
-
)
|
|
266
|
+
);
|
|
199
267
|
}
|
|
200
268
|
|
|
201
|
-
export function DialogBody({
|
|
202
|
-
|
|
269
|
+
export function DialogBody({
|
|
270
|
+
className,
|
|
271
|
+
...props
|
|
272
|
+
}: React.ComponentProps<"div">) {
|
|
273
|
+
const { mode } = useDialogContext();
|
|
203
274
|
return (
|
|
204
275
|
<div
|
|
205
276
|
data-slot="dialog-body"
|
|
206
|
-
className={cn(
|
|
277
|
+
className={cn(
|
|
278
|
+
mode === "alert" ? "min-w-0" : "min-h-0 flex-1 overflow-y-auto p-5",
|
|
279
|
+
className,
|
|
280
|
+
)}
|
|
207
281
|
{...props}
|
|
208
282
|
/>
|
|
209
|
-
)
|
|
283
|
+
);
|
|
210
284
|
}
|
|
211
285
|
|
|
212
|
-
export function DialogFooter({
|
|
213
|
-
|
|
286
|
+
export function DialogFooter({
|
|
287
|
+
className,
|
|
288
|
+
...props
|
|
289
|
+
}: React.ComponentProps<"div">) {
|
|
290
|
+
const { mode } = useDialogContext();
|
|
214
291
|
return (
|
|
215
292
|
<div
|
|
216
293
|
data-slot="dialog-footer"
|
|
217
294
|
className={cn(
|
|
218
|
-
mode ===
|
|
219
|
-
?
|
|
220
|
-
:
|
|
295
|
+
mode === "alert"
|
|
296
|
+
? "grid grid-cols-2 gap-2 [&>*:only-child]:col-span-full"
|
|
297
|
+
: "flex shrink-0 flex-col-reverse gap-2 border-t bg-muted/30 px-5 py-4 sm:flex-row sm:justify-end",
|
|
221
298
|
className,
|
|
222
299
|
)}
|
|
223
300
|
{...props}
|
|
224
301
|
/>
|
|
225
|
-
)
|
|
302
|
+
);
|
|
226
303
|
}
|
|
227
304
|
|
|
228
|
-
export function DialogTitle({
|
|
229
|
-
|
|
230
|
-
|
|
305
|
+
export function DialogTitle({
|
|
306
|
+
className,
|
|
307
|
+
...props
|
|
308
|
+
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
|
|
309
|
+
const { mode } = useDialogContext();
|
|
310
|
+
return mode === "alert" ? (
|
|
231
311
|
<AlertDialogPrimitive.Title
|
|
232
312
|
data-slot="dialog-title"
|
|
233
|
-
className={cn(
|
|
313
|
+
className={cn("text-lg font-semibold", className)}
|
|
234
314
|
{...props}
|
|
235
315
|
/>
|
|
236
316
|
) : (
|
|
237
317
|
<DialogPrimitive.Title
|
|
238
318
|
data-slot="dialog-title"
|
|
239
|
-
className={cn(
|
|
319
|
+
className={cn(
|
|
320
|
+
"min-w-0 flex-1 text-lg font-semibold leading-none",
|
|
321
|
+
className,
|
|
322
|
+
)}
|
|
240
323
|
{...props}
|
|
241
324
|
/>
|
|
242
|
-
)
|
|
325
|
+
);
|
|
243
326
|
}
|
|
@@ -3,22 +3,40 @@ import { XIcon } from "lucide-react";
|
|
|
3
3
|
import { Dialog as DrawerPrimitive } from "radix-ui";
|
|
4
4
|
|
|
5
5
|
import { cn } from "../../lib/cn.ts";
|
|
6
|
-
import {
|
|
6
|
+
import { Button } from "./button.tsx";
|
|
7
7
|
import { overlayClasses } from "./dialog.tsx";
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
interface DrawerContentContextValue {
|
|
10
|
+
showCloseButton: boolean;
|
|
11
|
+
closeDisabled: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const DrawerContentContext = React.createContext<DrawerContentContextValue>({
|
|
15
|
+
showCloseButton: false,
|
|
16
|
+
closeDisabled: false,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
function Drawer({
|
|
20
|
+
...props
|
|
21
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Root>) {
|
|
10
22
|
return <DrawerPrimitive.Root data-slot="drawer" {...props} />;
|
|
11
23
|
}
|
|
12
24
|
|
|
13
|
-
function DrawerTrigger({
|
|
25
|
+
function DrawerTrigger({
|
|
26
|
+
...props
|
|
27
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Trigger>) {
|
|
14
28
|
return <DrawerPrimitive.Trigger data-slot="drawer-trigger" {...props} />;
|
|
15
29
|
}
|
|
16
30
|
|
|
17
|
-
function DrawerClose({
|
|
31
|
+
function DrawerClose({
|
|
32
|
+
...props
|
|
33
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Close>) {
|
|
18
34
|
return <DrawerPrimitive.Close data-slot="drawer-close" {...props} />;
|
|
19
35
|
}
|
|
20
36
|
|
|
21
|
-
function DrawerPortal({
|
|
37
|
+
function DrawerPortal({
|
|
38
|
+
...props
|
|
39
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Portal>) {
|
|
22
40
|
return <DrawerPrimitive.Portal data-slot="drawer-portal" {...props} />;
|
|
23
41
|
}
|
|
24
42
|
|
|
@@ -35,16 +53,34 @@ function DrawerOverlay({
|
|
|
35
53
|
);
|
|
36
54
|
}
|
|
37
55
|
|
|
56
|
+
function DrawerCloseButton({ disabled = false }: { disabled?: boolean }) {
|
|
57
|
+
return (
|
|
58
|
+
<DrawerPrimitive.Close data-slot="drawer-close" asChild>
|
|
59
|
+
<Button
|
|
60
|
+
type="button"
|
|
61
|
+
size="icon-sm"
|
|
62
|
+
variant="ghost"
|
|
63
|
+
disabled={disabled}
|
|
64
|
+
aria-label="Fechar"
|
|
65
|
+
>
|
|
66
|
+
<XIcon aria-hidden />
|
|
67
|
+
</Button>
|
|
68
|
+
</DrawerPrimitive.Close>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
38
72
|
function DrawerContent({
|
|
39
73
|
className,
|
|
40
74
|
children,
|
|
41
75
|
side = "right",
|
|
42
76
|
showCloseButton = true,
|
|
77
|
+
closeDisabled = false,
|
|
43
78
|
"aria-describedby": ariaDescribedBy,
|
|
44
79
|
...props
|
|
45
80
|
}: React.ComponentProps<typeof DrawerPrimitive.Content> & {
|
|
46
81
|
side?: "top" | "right" | "bottom" | "left";
|
|
47
82
|
showCloseButton?: boolean;
|
|
83
|
+
closeDisabled?: boolean;
|
|
48
84
|
}) {
|
|
49
85
|
return (
|
|
50
86
|
<DrawerPortal>
|
|
@@ -66,30 +102,29 @@ function DrawerContent({
|
|
|
66
102
|
aria-describedby={ariaDescribedBy}
|
|
67
103
|
{...props}
|
|
68
104
|
>
|
|
69
|
-
{
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
className={cn(
|
|
73
|
-
"absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus-visible:outline-hidden disabled:pointer-events-none data-[state=open]:bg-secondary",
|
|
74
|
-
focusRing,
|
|
75
|
-
)}
|
|
76
|
-
>
|
|
77
|
-
<XIcon className="size-4" />
|
|
78
|
-
<span className="sr-only">Fechar</span>
|
|
79
|
-
</DrawerPrimitive.Close>
|
|
80
|
-
)}
|
|
105
|
+
<DrawerContentContext.Provider value={{ showCloseButton, closeDisabled }}>
|
|
106
|
+
{children}
|
|
107
|
+
</DrawerContentContext.Provider>
|
|
81
108
|
</DrawerPrimitive.Content>
|
|
82
109
|
</DrawerPortal>
|
|
83
110
|
);
|
|
84
111
|
}
|
|
85
112
|
|
|
86
|
-
function DrawerHeader({
|
|
113
|
+
function DrawerHeader({
|
|
114
|
+
className,
|
|
115
|
+
children,
|
|
116
|
+
...props
|
|
117
|
+
}: React.ComponentProps<"div">) {
|
|
118
|
+
const { showCloseButton, closeDisabled } = React.useContext(DrawerContentContext);
|
|
87
119
|
return (
|
|
88
120
|
<div
|
|
89
121
|
data-slot="drawer-header"
|
|
90
|
-
className={cn("flex shrink-0
|
|
122
|
+
className={cn("flex shrink-0 items-center gap-4 border-b p-5", className)}
|
|
91
123
|
{...props}
|
|
92
|
-
|
|
124
|
+
>
|
|
125
|
+
{children}
|
|
126
|
+
{showCloseButton && <DrawerCloseButton disabled={closeDisabled} />}
|
|
127
|
+
</div>
|
|
93
128
|
);
|
|
94
129
|
}
|
|
95
130
|
|
|
@@ -116,11 +151,17 @@ function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
116
151
|
);
|
|
117
152
|
}
|
|
118
153
|
|
|
119
|
-
function DrawerTitle({
|
|
154
|
+
function DrawerTitle({
|
|
155
|
+
className,
|
|
156
|
+
...props
|
|
157
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Title>) {
|
|
120
158
|
return (
|
|
121
159
|
<DrawerPrimitive.Title
|
|
122
160
|
data-slot="drawer-title"
|
|
123
|
-
className={cn(
|
|
161
|
+
className={cn(
|
|
162
|
+
"min-w-0 flex-1 text-lg font-semibold leading-none text-foreground",
|
|
163
|
+
className,
|
|
164
|
+
)}
|
|
124
165
|
{...props}
|
|
125
166
|
/>
|
|
126
167
|
);
|
|
@@ -23,7 +23,9 @@ do Select pesquisável; para uma escolha em formulário, use Select.
|
|
|
23
23
|
|
|
24
24
|
## Palette modal (CommandDialog)
|
|
25
25
|
|
|
26
|
-
O Command embrulhado em um Dialog
|
|
26
|
+
O Command embrulhado em um Dialog mantém um header compacto com título e close. A descrição
|
|
27
|
+
permanece disponível para tecnologias assistivas. O atalho ⌘K (keydown no app) só troca o open —
|
|
28
|
+
o conteúdo é o mesmo do inline.
|
|
27
29
|
|
|
28
30
|
```tsx preview
|
|
29
31
|
const [open, setOpen] = useState(false)
|
|
@@ -23,7 +23,7 @@ render(
|
|
|
23
23
|
Use os slots quando o header precisar de composição própria. A árvore aceita exatamente um
|
|
24
24
|
`ContentHeader` e um `ContentBody` como filhos diretos. O header exige um `ContentTitle` e aceita
|
|
25
25
|
uma descrição, um contador (`ContentMeta`) e uma região de ações. O contador é próprio de
|
|
26
|
-
`Content`; `PageHeader` reconhece somente
|
|
26
|
+
`Content`; `PageHeader` reconhece somente navegação, título e ações.
|
|
27
27
|
|
|
28
28
|
```tsx preview col
|
|
29
29
|
render(
|
|
@@ -264,18 +264,18 @@ botão. O texto informa o efeito real, como `Excluir`, `Revogar acesso` ou `Ence
|
|
|
264
264
|
|
|
265
265
|
## Propriedades de DialogContent
|
|
266
266
|
|
|
267
|
-
| Propriedade | Tipo | Padrão | Descrição
|
|
268
|
-
| ----------------- | ----------- | ------ |
|
|
269
|
-
| `showCloseButton` | `boolean` | `true` | No modo padrão,
|
|
270
|
-
| `className` | `string` | | Ajusta a superfície.
|
|
271
|
-
| `children` | `ReactNode` | | Cabeçalho, corpo, rodapé ou conteúdo próprio.
|
|
267
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
268
|
+
| ----------------- | ----------- | ------ | --------------------------------------------------------------------------------------------------------- |
|
|
269
|
+
| `showCloseButton` | `boolean` | `true` | No modo padrão, inclui a ação de fechamento no header; o modo de alerta exige uma resposta identificável. |
|
|
270
|
+
| `className` | `string` | | Ajusta a superfície. |
|
|
271
|
+
| `children` | `ReactNode` | | Cabeçalho, corpo, rodapé ou conteúdo próprio. |
|
|
272
272
|
|
|
273
273
|
## Propriedades de DialogHeader
|
|
274
274
|
|
|
275
|
-
| Propriedade | Tipo | Padrão | Descrição
|
|
276
|
-
| ----------- | ----------- | ------ |
|
|
277
|
-
| `className` | `string` | | Ajusta a faixa superior.
|
|
278
|
-
| `children` | `ReactNode` | | Título e
|
|
275
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
276
|
+
| ----------- | ----------- | ------ | ---------------------------------------------------------- |
|
|
277
|
+
| `className` | `string` | | Ajusta a faixa superior. |
|
|
278
|
+
| `children` | `ReactNode` | | Título e ações opcionais; o close padrão entra por último. |
|
|
279
279
|
|
|
280
280
|
## Propriedades de DialogBody
|
|
281
281
|
|
|
@@ -60,10 +60,10 @@ mesmo alinhamento horizontal entre título, conteúdo e ações.
|
|
|
60
60
|
|
|
61
61
|
## Propriedades de DrawerContent
|
|
62
62
|
|
|
63
|
-
| Propriedade | Tipo | Padrão | Descrição
|
|
64
|
-
| ----------------- | ---------------------------------------- | --------- |
|
|
65
|
-
| `side` | `'top' \| 'right' \| 'bottom' \| 'left'` | `'right'` | Borda de onde o painel aparece.
|
|
66
|
-
| `showCloseButton` | `boolean` | `true` |
|
|
63
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
64
|
+
| ----------------- | ---------------------------------------- | --------- | ------------------------------------------------------------------------------------------ |
|
|
65
|
+
| `side` | `'top' \| 'right' \| 'bottom' \| 'left'` | `'right'` | Borda de onde o painel aparece. |
|
|
66
|
+
| `showCloseButton` | `boolean` | `true` | Inclui a ação de fechamento no header. Desative quando o painel exigir uma ação explícita. |
|
|
67
67
|
|
|
68
68
|
## Propriedades de DrawerBody
|
|
69
69
|
|