@softize/opus 15.2.2 → 16.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 +51 -18
- package/bin/lib/check.mjs +98 -15
- package/bin/lib/copy.mjs +811 -314
- package/bin/lib/gen-manifest.mjs +24 -23
- package/bin/lib/gen-runner.mjs +188 -148
- package/docs/adr/0010-page-header-owns-page-chrome.md +3 -4
- package/docs/adr/0011-page-shell-coordinates-persistent-page-chrome.md +5 -3
- package/docs/adr/0012-modal-header-only-names-the-surface.md +45 -0
- package/docs/adr/0013-presentation-is-a-portable-action-oriented-artifact.md +92 -0
- package/docs/code-style.md +24 -19
- package/package.json +5 -1
- package/registry/skills/build-opus-ui/references/ui-patterns.md +43 -26
- package/src/core/presentation.ts +512 -0
- package/src/presentation/index.ts +1 -0
- package/src/ui/components/patterns/action-list-dialog.tsx +26 -9
- package/src/ui/components/patterns/confirm.tsx +34 -29
- package/src/ui/components/patterns/form-dialog.tsx +20 -8
- package/src/ui/components/patterns/list.tsx +26 -9
- package/src/ui/components/patterns/page.tsx +99 -139
- package/src/ui/components/patterns/presentation.tsx +316 -0
- package/src/ui/components/patterns/sidebar.tsx +3 -3
- package/src/ui/components/patterns/trigger.tsx +38 -17
- package/src/ui/components/primitives/button-group.tsx +53 -43
- package/src/ui/components/primitives/command.tsx +30 -72
- package/src/ui/components/primitives/dialog.tsx +23 -89
- package/src/ui/components/primitives/drawer.tsx +8 -34
- package/src/ui/docs/content/action-form-dialog.md +12 -10
- package/src/ui/docs/content/action-list-dialog.md +22 -17
- package/src/ui/docs/content/button.md +52 -35
- package/src/ui/docs/content/communication.md +26 -26
- package/src/ui/docs/content/dialog.md +173 -154
- package/src/ui/docs/content/drawer.md +12 -11
- package/src/ui/docs/content/page.md +72 -91
- package/src/ui/docs/content/presentation.md +158 -0
- package/src/ui/docs/registry.tsx +6 -0
- package/src/ui/meta.ts +8 -2
- package/src/ui/react.tsx +10 -3
|
@@ -1,26 +1,17 @@
|
|
|
1
|
-
import * as React from
|
|
2
|
-
import { Command as CommandPrimitive } from
|
|
3
|
-
import { SearchIcon } from
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { Command as CommandPrimitive } from 'cmdk'
|
|
3
|
+
import { SearchIcon } from 'lucide-react'
|
|
4
4
|
|
|
5
5
|
import { cn } from '../../lib/cn.ts'
|
|
6
|
-
import {
|
|
7
|
-
Dialog,
|
|
8
|
-
DialogContent,
|
|
9
|
-
DialogDescription,
|
|
10
|
-
DialogHeader,
|
|
11
|
-
DialogTitle,
|
|
12
|
-
} from './dialog.tsx'
|
|
6
|
+
import { Dialog, DialogContent, DialogHeader, DialogTitle } from './dialog.tsx'
|
|
13
7
|
|
|
14
|
-
function Command({
|
|
15
|
-
className,
|
|
16
|
-
...props
|
|
17
|
-
}: React.ComponentProps<typeof CommandPrimitive>) {
|
|
8
|
+
function Command({ className, ...props }: React.ComponentProps<typeof CommandPrimitive>) {
|
|
18
9
|
return (
|
|
19
10
|
<CommandPrimitive
|
|
20
11
|
data-slot="command"
|
|
21
12
|
className={cn(
|
|
22
|
-
|
|
23
|
-
className
|
|
13
|
+
'flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground',
|
|
14
|
+
className,
|
|
24
15
|
)}
|
|
25
16
|
{...props}
|
|
26
17
|
/>
|
|
@@ -28,8 +19,8 @@ function Command({
|
|
|
28
19
|
}
|
|
29
20
|
|
|
30
21
|
function CommandDialog({
|
|
31
|
-
title =
|
|
32
|
-
description =
|
|
22
|
+
title = 'Paleta de comandos',
|
|
23
|
+
description = 'Busque um comando para executar…',
|
|
33
24
|
children,
|
|
34
25
|
className,
|
|
35
26
|
showCloseButton = true,
|
|
@@ -40,15 +31,17 @@ function CommandDialog({
|
|
|
40
31
|
className?: string
|
|
41
32
|
showCloseButton?: boolean
|
|
42
33
|
}) {
|
|
34
|
+
const descriptionId = React.useId()
|
|
43
35
|
return (
|
|
44
36
|
<Dialog {...props}>
|
|
45
37
|
<DialogHeader className="sr-only">
|
|
46
38
|
<DialogTitle>{title}</DialogTitle>
|
|
47
|
-
<
|
|
39
|
+
<p id={descriptionId}>{description}</p>
|
|
48
40
|
</DialogHeader>
|
|
49
41
|
<DialogContent
|
|
50
|
-
className={cn(
|
|
42
|
+
className={cn('overflow-hidden p-0', className)}
|
|
51
43
|
showCloseButton={showCloseButton}
|
|
44
|
+
aria-describedby={descriptionId}
|
|
52
45
|
>
|
|
53
46
|
<Command className="**:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
|
|
54
47
|
{children}
|
|
@@ -58,21 +51,15 @@ function CommandDialog({
|
|
|
58
51
|
)
|
|
59
52
|
}
|
|
60
53
|
|
|
61
|
-
function CommandInput({
|
|
62
|
-
className,
|
|
63
|
-
...props
|
|
64
|
-
}: React.ComponentProps<typeof CommandPrimitive.Input>) {
|
|
54
|
+
function CommandInput({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Input>) {
|
|
65
55
|
return (
|
|
66
|
-
<div
|
|
67
|
-
data-slot="command-input-wrapper"
|
|
68
|
-
className="flex h-9 items-center gap-2 border-b px-3"
|
|
69
|
-
>
|
|
56
|
+
<div data-slot="command-input-wrapper" className="flex h-9 items-center gap-2 border-b px-3">
|
|
70
57
|
<SearchIcon className="size-4 shrink-0 opacity-50" />
|
|
71
58
|
<CommandPrimitive.Input
|
|
72
59
|
data-slot="command-input"
|
|
73
60
|
className={cn(
|
|
74
|
-
|
|
75
|
-
className
|
|
61
|
+
'flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50',
|
|
62
|
+
className,
|
|
76
63
|
)}
|
|
77
64
|
{...props}
|
|
78
65
|
/>
|
|
@@ -80,90 +67,61 @@ function CommandInput({
|
|
|
80
67
|
)
|
|
81
68
|
}
|
|
82
69
|
|
|
83
|
-
function CommandList({
|
|
84
|
-
className,
|
|
85
|
-
...props
|
|
86
|
-
}: React.ComponentProps<typeof CommandPrimitive.List>) {
|
|
70
|
+
function CommandList({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.List>) {
|
|
87
71
|
return (
|
|
88
72
|
<CommandPrimitive.List
|
|
89
73
|
data-slot="command-list"
|
|
90
|
-
className={cn(
|
|
91
|
-
"max-h-[18.75rem] scroll-py-1 overflow-x-hidden overflow-y-auto",
|
|
92
|
-
className
|
|
93
|
-
)}
|
|
74
|
+
className={cn('max-h-[18.75rem] scroll-py-1 overflow-x-hidden overflow-y-auto', className)}
|
|
94
75
|
{...props}
|
|
95
76
|
/>
|
|
96
77
|
)
|
|
97
78
|
}
|
|
98
79
|
|
|
99
|
-
function CommandEmpty({
|
|
100
|
-
...props
|
|
101
|
-
}: React.ComponentProps<typeof CommandPrimitive.Empty>) {
|
|
102
|
-
return (
|
|
103
|
-
<CommandPrimitive.Empty
|
|
104
|
-
data-slot="command-empty"
|
|
105
|
-
className="py-6 text-center text-sm"
|
|
106
|
-
{...props}
|
|
107
|
-
/>
|
|
108
|
-
)
|
|
80
|
+
function CommandEmpty({ ...props }: React.ComponentProps<typeof CommandPrimitive.Empty>) {
|
|
81
|
+
return <CommandPrimitive.Empty data-slot="command-empty" className="py-6 text-center text-sm" {...props} />
|
|
109
82
|
}
|
|
110
83
|
|
|
111
|
-
function CommandGroup({
|
|
112
|
-
className,
|
|
113
|
-
...props
|
|
114
|
-
}: React.ComponentProps<typeof CommandPrimitive.Group>) {
|
|
84
|
+
function CommandGroup({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Group>) {
|
|
115
85
|
return (
|
|
116
86
|
<CommandPrimitive.Group
|
|
117
87
|
data-slot="command-group"
|
|
118
88
|
className={cn(
|
|
119
|
-
|
|
120
|
-
className
|
|
89
|
+
'overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground',
|
|
90
|
+
className,
|
|
121
91
|
)}
|
|
122
92
|
{...props}
|
|
123
93
|
/>
|
|
124
94
|
)
|
|
125
95
|
}
|
|
126
96
|
|
|
127
|
-
function CommandSeparator({
|
|
128
|
-
className,
|
|
129
|
-
...props
|
|
130
|
-
}: React.ComponentProps<typeof CommandPrimitive.Separator>) {
|
|
97
|
+
function CommandSeparator({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Separator>) {
|
|
131
98
|
return (
|
|
132
99
|
<CommandPrimitive.Separator
|
|
133
100
|
data-slot="command-separator"
|
|
134
|
-
className={cn(
|
|
101
|
+
className={cn('-mx-1 h-px bg-border', className)}
|
|
135
102
|
{...props}
|
|
136
103
|
/>
|
|
137
104
|
)
|
|
138
105
|
}
|
|
139
106
|
|
|
140
|
-
function CommandItem({
|
|
141
|
-
className,
|
|
142
|
-
...props
|
|
143
|
-
}: React.ComponentProps<typeof CommandPrimitive.Item>) {
|
|
107
|
+
function CommandItem({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Item>) {
|
|
144
108
|
return (
|
|
145
109
|
<CommandPrimitive.Item
|
|
146
110
|
data-slot="command-item"
|
|
147
111
|
className={cn(
|
|
148
112
|
"relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
|
|
149
|
-
className
|
|
113
|
+
className,
|
|
150
114
|
)}
|
|
151
115
|
{...props}
|
|
152
116
|
/>
|
|
153
117
|
)
|
|
154
118
|
}
|
|
155
119
|
|
|
156
|
-
function CommandShortcut({
|
|
157
|
-
className,
|
|
158
|
-
...props
|
|
159
|
-
}: React.ComponentProps<"span">) {
|
|
120
|
+
function CommandShortcut({ className, ...props }: React.ComponentProps<'span'>) {
|
|
160
121
|
return (
|
|
161
122
|
<span
|
|
162
123
|
data-slot="command-shortcut"
|
|
163
|
-
className={cn(
|
|
164
|
-
"ml-auto text-xs tracking-widest text-muted-foreground",
|
|
165
|
-
className
|
|
166
|
-
)}
|
|
124
|
+
className={cn('ml-auto text-xs tracking-widest text-muted-foreground', className)}
|
|
167
125
|
{...props}
|
|
168
126
|
/>
|
|
169
127
|
)
|
|
@@ -12,26 +12,22 @@ interface DialogContextValue {
|
|
|
12
12
|
onResult?: (result: string) => void
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
const DialogContext = React.createContext<DialogContextValue>({
|
|
15
|
+
const DialogContext = React.createContext<DialogContextValue>({
|
|
16
|
+
mode: 'default',
|
|
17
|
+
})
|
|
16
18
|
|
|
17
19
|
function useDialogContext(): DialogContextValue {
|
|
18
20
|
return React.useContext(DialogContext)
|
|
19
21
|
}
|
|
20
22
|
|
|
21
|
-
export interface DialogProps
|
|
22
|
-
extends React.ComponentProps<typeof DialogPrimitive.Root> {
|
|
23
|
+
export interface DialogProps extends React.ComponentProps<typeof DialogPrimitive.Root> {
|
|
23
24
|
/** `alert` exige uma resposta explícita e deriva `role="alertdialog"`. */
|
|
24
25
|
mode?: DialogMode
|
|
25
26
|
/** Recebe o `result` do `DialogClose` acionado. */
|
|
26
27
|
onResult?: (result: string) => void
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
export function Dialog({
|
|
30
|
-
mode = 'default',
|
|
31
|
-
onResult,
|
|
32
|
-
modal,
|
|
33
|
-
...props
|
|
34
|
-
}: DialogProps) {
|
|
30
|
+
export function Dialog({ mode = 'default', onResult, modal, ...props }: DialogProps) {
|
|
35
31
|
const context = React.useMemo(() => ({ mode, onResult }), [mode, onResult])
|
|
36
32
|
|
|
37
33
|
return (
|
|
@@ -45,9 +41,7 @@ export function Dialog({
|
|
|
45
41
|
)
|
|
46
42
|
}
|
|
47
43
|
|
|
48
|
-
export function DialogTrigger({
|
|
49
|
-
...props
|
|
50
|
-
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
|
|
44
|
+
export function DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
|
|
51
45
|
const { mode } = useDialogContext()
|
|
52
46
|
return mode === 'alert' ? (
|
|
53
47
|
<AlertDialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
|
|
@@ -56,9 +50,7 @@ export function DialogTrigger({
|
|
|
56
50
|
)
|
|
57
51
|
}
|
|
58
52
|
|
|
59
|
-
export function DialogPortal({
|
|
60
|
-
...props
|
|
61
|
-
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
|
|
53
|
+
export function DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
|
|
62
54
|
const { mode } = useDialogContext()
|
|
63
55
|
return mode === 'alert' ? (
|
|
64
56
|
<AlertDialogPrimitive.Portal data-slot="dialog-portal" {...props} />
|
|
@@ -67,20 +59,14 @@ export function DialogPortal({
|
|
|
67
59
|
)
|
|
68
60
|
}
|
|
69
61
|
|
|
70
|
-
export interface DialogCloseProps
|
|
71
|
-
extends React.ComponentProps<typeof DialogPrimitive.Close> {
|
|
62
|
+
export interface DialogCloseProps extends React.ComponentProps<typeof DialogPrimitive.Close> {
|
|
72
63
|
/** No modo `alert`, registra este controle como destino inicial seguro do foco. */
|
|
73
64
|
initialFocus?: boolean
|
|
74
65
|
/** Resposta entregue a `Dialog.onResult` antes do fechamento. */
|
|
75
66
|
result?: string
|
|
76
67
|
}
|
|
77
68
|
|
|
78
|
-
export function DialogClose({
|
|
79
|
-
initialFocus = false,
|
|
80
|
-
result,
|
|
81
|
-
onClick,
|
|
82
|
-
...props
|
|
83
|
-
}: DialogCloseProps) {
|
|
69
|
+
export function DialogClose({ initialFocus = false, result, onClick, ...props }: DialogCloseProps) {
|
|
84
70
|
const root = useDialogContext()
|
|
85
71
|
const handleClick: React.MouseEventHandler<HTMLButtonElement> = (event) => {
|
|
86
72
|
onClick?.(event)
|
|
@@ -88,9 +74,7 @@ export function DialogClose({
|
|
|
88
74
|
}
|
|
89
75
|
|
|
90
76
|
if (root.mode === 'alert') {
|
|
91
|
-
const ClosePrimitive = initialFocus
|
|
92
|
-
? AlertDialogPrimitive.Cancel
|
|
93
|
-
: AlertDialogPrimitive.Action
|
|
77
|
+
const ClosePrimitive = initialFocus ? AlertDialogPrimitive.Cancel : AlertDialogPrimitive.Action
|
|
94
78
|
return (
|
|
95
79
|
<ClosePrimitive
|
|
96
80
|
data-slot="dialog-close"
|
|
@@ -115,24 +99,13 @@ export function DialogClose({
|
|
|
115
99
|
export const overlayClasses =
|
|
116
100
|
'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'
|
|
117
101
|
|
|
118
|
-
export function DialogOverlay({
|
|
119
|
-
className,
|
|
120
|
-
...props
|
|
121
|
-
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
|
|
102
|
+
export function DialogOverlay({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
|
|
122
103
|
const { mode } = useDialogContext()
|
|
123
104
|
const overlay = cn(overlayClasses, className)
|
|
124
105
|
return mode === 'alert' ? (
|
|
125
|
-
<AlertDialogPrimitive.Overlay
|
|
126
|
-
data-slot="dialog-overlay"
|
|
127
|
-
className={overlay}
|
|
128
|
-
{...props}
|
|
129
|
-
/>
|
|
106
|
+
<AlertDialogPrimitive.Overlay data-slot="dialog-overlay" className={overlay} {...props} />
|
|
130
107
|
) : (
|
|
131
|
-
<DialogPrimitive.Overlay
|
|
132
|
-
data-slot="dialog-overlay"
|
|
133
|
-
className={overlay}
|
|
134
|
-
{...props}
|
|
135
|
-
/>
|
|
108
|
+
<DialogPrimitive.Overlay data-slot="dialog-overlay" className={overlay} {...props} />
|
|
136
109
|
)
|
|
137
110
|
}
|
|
138
111
|
|
|
@@ -140,6 +113,7 @@ export function DialogContent({
|
|
|
140
113
|
className,
|
|
141
114
|
children,
|
|
142
115
|
showCloseButton,
|
|
116
|
+
'aria-describedby': ariaDescribedBy,
|
|
143
117
|
...props
|
|
144
118
|
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
|
|
145
119
|
/** Mostra o “X” no modo padrão. O modo `alert` exige uma resposta identificável. */
|
|
@@ -158,6 +132,7 @@ export function DialogContent({
|
|
|
158
132
|
data-slot="dialog-content"
|
|
159
133
|
data-mode="alert"
|
|
160
134
|
className={cn(contentClasses, 'grid max-w-xs gap-4 p-6', className)}
|
|
135
|
+
aria-describedby={ariaDescribedBy}
|
|
161
136
|
{...props}
|
|
162
137
|
>
|
|
163
138
|
{children}
|
|
@@ -172,11 +147,8 @@ export function DialogContent({
|
|
|
172
147
|
<DialogPrimitive.Content
|
|
173
148
|
data-slot="dialog-content"
|
|
174
149
|
data-mode="default"
|
|
175
|
-
className={cn(
|
|
176
|
-
|
|
177
|
-
'flex max-h-[85vh] flex-col sm:max-w-lg',
|
|
178
|
-
className,
|
|
179
|
-
)}
|
|
150
|
+
className={cn(contentClasses, 'flex max-h-[85vh] flex-col sm:max-w-lg', className)}
|
|
151
|
+
aria-describedby={ariaDescribedBy}
|
|
180
152
|
{...props}
|
|
181
153
|
>
|
|
182
154
|
{children}
|
|
@@ -197,10 +169,7 @@ export function DialogContent({
|
|
|
197
169
|
)
|
|
198
170
|
}
|
|
199
171
|
|
|
200
|
-
export function DialogHeader({
|
|
201
|
-
className,
|
|
202
|
-
...props
|
|
203
|
-
}: React.ComponentProps<'div'>) {
|
|
172
|
+
export function DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {
|
|
204
173
|
const { mode } = useDialogContext()
|
|
205
174
|
return (
|
|
206
175
|
<div
|
|
@@ -216,10 +185,7 @@ export function DialogHeader({
|
|
|
216
185
|
)
|
|
217
186
|
}
|
|
218
187
|
|
|
219
|
-
export function DialogMedia({
|
|
220
|
-
className,
|
|
221
|
-
...props
|
|
222
|
-
}: React.ComponentProps<'div'>) {
|
|
188
|
+
export function DialogMedia({ className, ...props }: React.ComponentProps<'div'>) {
|
|
223
189
|
return (
|
|
224
190
|
<div
|
|
225
191
|
data-slot="dialog-media"
|
|
@@ -232,27 +198,18 @@ export function DialogMedia({
|
|
|
232
198
|
)
|
|
233
199
|
}
|
|
234
200
|
|
|
235
|
-
export function DialogBody({
|
|
236
|
-
className,
|
|
237
|
-
...props
|
|
238
|
-
}: React.ComponentProps<'div'>) {
|
|
201
|
+
export function DialogBody({ className, ...props }: React.ComponentProps<'div'>) {
|
|
239
202
|
const { mode } = useDialogContext()
|
|
240
203
|
return (
|
|
241
204
|
<div
|
|
242
205
|
data-slot="dialog-body"
|
|
243
|
-
className={cn(
|
|
244
|
-
mode === 'alert' ? 'min-w-0' : 'min-h-0 flex-1 overflow-y-auto p-5',
|
|
245
|
-
className,
|
|
246
|
-
)}
|
|
206
|
+
className={cn(mode === 'alert' ? 'min-w-0' : 'min-h-0 flex-1 overflow-y-auto p-5', className)}
|
|
247
207
|
{...props}
|
|
248
208
|
/>
|
|
249
209
|
)
|
|
250
210
|
}
|
|
251
211
|
|
|
252
|
-
export function DialogFooter({
|
|
253
|
-
className,
|
|
254
|
-
...props
|
|
255
|
-
}: React.ComponentProps<'div'>) {
|
|
212
|
+
export function DialogFooter({ className, ...props }: React.ComponentProps<'div'>) {
|
|
256
213
|
const { mode } = useDialogContext()
|
|
257
214
|
return (
|
|
258
215
|
<div
|
|
@@ -268,10 +225,7 @@ export function DialogFooter({
|
|
|
268
225
|
)
|
|
269
226
|
}
|
|
270
227
|
|
|
271
|
-
export function DialogTitle({
|
|
272
|
-
className,
|
|
273
|
-
...props
|
|
274
|
-
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
|
|
228
|
+
export function DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {
|
|
275
229
|
const { mode } = useDialogContext()
|
|
276
230
|
return mode === 'alert' ? (
|
|
277
231
|
<AlertDialogPrimitive.Title
|
|
@@ -287,23 +241,3 @@ export function DialogTitle({
|
|
|
287
241
|
/>
|
|
288
242
|
)
|
|
289
243
|
}
|
|
290
|
-
|
|
291
|
-
export function DialogDescription({
|
|
292
|
-
className,
|
|
293
|
-
...props
|
|
294
|
-
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
|
|
295
|
-
const { mode } = useDialogContext()
|
|
296
|
-
return mode === 'alert' ? (
|
|
297
|
-
<AlertDialogPrimitive.Description
|
|
298
|
-
data-slot="dialog-description"
|
|
299
|
-
className={cn('text-sm text-muted-foreground', className)}
|
|
300
|
-
{...props}
|
|
301
|
-
/>
|
|
302
|
-
) : (
|
|
303
|
-
<DialogPrimitive.Description
|
|
304
|
-
data-slot="dialog-description"
|
|
305
|
-
className={cn('text-sm text-muted-foreground', className)}
|
|
306
|
-
{...props}
|
|
307
|
-
/>
|
|
308
|
-
)
|
|
309
|
-
}
|
|
@@ -6,27 +6,19 @@ import { cn } from "../../lib/cn.ts";
|
|
|
6
6
|
import { focusRing } from "./control.ts";
|
|
7
7
|
import { overlayClasses } from "./dialog.tsx";
|
|
8
8
|
|
|
9
|
-
function Drawer({
|
|
10
|
-
...props
|
|
11
|
-
}: React.ComponentProps<typeof DrawerPrimitive.Root>) {
|
|
9
|
+
function Drawer({ ...props }: React.ComponentProps<typeof DrawerPrimitive.Root>) {
|
|
12
10
|
return <DrawerPrimitive.Root data-slot="drawer" {...props} />;
|
|
13
11
|
}
|
|
14
12
|
|
|
15
|
-
function DrawerTrigger({
|
|
16
|
-
...props
|
|
17
|
-
}: React.ComponentProps<typeof DrawerPrimitive.Trigger>) {
|
|
13
|
+
function DrawerTrigger({ ...props }: React.ComponentProps<typeof DrawerPrimitive.Trigger>) {
|
|
18
14
|
return <DrawerPrimitive.Trigger data-slot="drawer-trigger" {...props} />;
|
|
19
15
|
}
|
|
20
16
|
|
|
21
|
-
function DrawerClose({
|
|
22
|
-
...props
|
|
23
|
-
}: React.ComponentProps<typeof DrawerPrimitive.Close>) {
|
|
17
|
+
function DrawerClose({ ...props }: React.ComponentProps<typeof DrawerPrimitive.Close>) {
|
|
24
18
|
return <DrawerPrimitive.Close data-slot="drawer-close" {...props} />;
|
|
25
19
|
}
|
|
26
20
|
|
|
27
|
-
function DrawerPortal({
|
|
28
|
-
...props
|
|
29
|
-
}: React.ComponentProps<typeof DrawerPrimitive.Portal>) {
|
|
21
|
+
function DrawerPortal({ ...props }: React.ComponentProps<typeof DrawerPrimitive.Portal>) {
|
|
30
22
|
return <DrawerPrimitive.Portal data-slot="drawer-portal" {...props} />;
|
|
31
23
|
}
|
|
32
24
|
|
|
@@ -37,10 +29,7 @@ function DrawerOverlay({
|
|
|
37
29
|
return (
|
|
38
30
|
<DrawerPrimitive.Overlay
|
|
39
31
|
data-slot="drawer-overlay"
|
|
40
|
-
className={cn(
|
|
41
|
-
overlayClasses,
|
|
42
|
-
className,
|
|
43
|
-
)}
|
|
32
|
+
className={cn(overlayClasses, className)}
|
|
44
33
|
{...props}
|
|
45
34
|
/>
|
|
46
35
|
);
|
|
@@ -51,6 +40,7 @@ function DrawerContent({
|
|
|
51
40
|
children,
|
|
52
41
|
side = "right",
|
|
53
42
|
showCloseButton = true,
|
|
43
|
+
"aria-describedby": ariaDescribedBy,
|
|
54
44
|
...props
|
|
55
45
|
}: React.ComponentProps<typeof DrawerPrimitive.Content> & {
|
|
56
46
|
side?: "top" | "right" | "bottom" | "left";
|
|
@@ -73,6 +63,7 @@ function DrawerContent({
|
|
|
73
63
|
"inset-x-0 bottom-0 h-auto border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
|
|
74
64
|
className,
|
|
75
65
|
)}
|
|
66
|
+
aria-describedby={ariaDescribedBy}
|
|
76
67
|
{...props}
|
|
77
68
|
>
|
|
78
69
|
{children}
|
|
@@ -125,10 +116,7 @@ function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
125
116
|
);
|
|
126
117
|
}
|
|
127
118
|
|
|
128
|
-
function DrawerTitle({
|
|
129
|
-
className,
|
|
130
|
-
...props
|
|
131
|
-
}: React.ComponentProps<typeof DrawerPrimitive.Title>) {
|
|
119
|
+
function DrawerTitle({ className, ...props }: React.ComponentProps<typeof DrawerPrimitive.Title>) {
|
|
132
120
|
return (
|
|
133
121
|
<DrawerPrimitive.Title
|
|
134
122
|
data-slot="drawer-title"
|
|
@@ -138,19 +126,6 @@ function DrawerTitle({
|
|
|
138
126
|
);
|
|
139
127
|
}
|
|
140
128
|
|
|
141
|
-
function DrawerDescription({
|
|
142
|
-
className,
|
|
143
|
-
...props
|
|
144
|
-
}: React.ComponentProps<typeof DrawerPrimitive.Description>) {
|
|
145
|
-
return (
|
|
146
|
-
<DrawerPrimitive.Description
|
|
147
|
-
data-slot="drawer-description"
|
|
148
|
-
className={cn("text-sm text-muted-foreground", className)}
|
|
149
|
-
{...props}
|
|
150
|
-
/>
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
129
|
export {
|
|
155
130
|
Drawer,
|
|
156
131
|
DrawerTrigger,
|
|
@@ -160,5 +135,4 @@ export {
|
|
|
160
135
|
DrawerBody,
|
|
161
136
|
DrawerFooter,
|
|
162
137
|
DrawerTitle,
|
|
163
|
-
DrawerDescription,
|
|
164
138
|
};
|
|
@@ -5,28 +5,30 @@ para outra página. O consumidor controla `open`; depois de uma execução bem-s
|
|
|
5
5
|
fecha o modal. O cabeçalho e o rodapé permanecem visíveis enquanto os campos podem rolar.
|
|
6
6
|
|
|
7
7
|
```tsx preview
|
|
8
|
-
const [open, setOpen] = useState(false)
|
|
8
|
+
const [open, setOpen] = useState(false);
|
|
9
9
|
|
|
10
10
|
render(
|
|
11
11
|
<DocBrowserActionProvider>
|
|
12
|
-
<Button onClick={() => setOpen(true)}
|
|
12
|
+
<Button onClick={() => setOpen(true)}>
|
|
13
|
+
<Plus /> Novo workspace
|
|
14
|
+
</Button>
|
|
13
15
|
<ActionFormDialog
|
|
14
16
|
open={open}
|
|
15
17
|
onOpenChange={setOpen}
|
|
16
18
|
title="Novo workspace"
|
|
17
|
-
description="O workspace agrupa os repositórios e agentes do cliente."
|
|
18
19
|
action={docWorkspaceCreate}
|
|
19
20
|
submitLabel="Criar workspace"
|
|
20
21
|
/>
|
|
21
22
|
</DocBrowserActionProvider>,
|
|
22
|
-
)
|
|
23
|
+
);
|
|
23
24
|
```
|
|
24
25
|
|
|
25
26
|
## Propriedades de ActionFormDialog
|
|
26
27
|
|
|
27
|
-
| Propriedade
|
|
28
|
-
|
|
29
|
-
| `open / onOpenChange` | `boolean / (open: boolean) => void`
|
|
30
|
-
| `title
|
|
31
|
-
| `
|
|
32
|
-
|
|
|
28
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
29
|
+
| --------------------- | ------------------------------------------------- | ---------- | --------------------------------------------------------------------------------------------------- |
|
|
30
|
+
| `open / onOpenChange` | `boolean / (open: boolean) => void` | | Estado controlado do modal. `onOpenChange(false)` é chamado no sucesso e ao cancelar. |
|
|
31
|
+
| `title` | `string` | | Nome da tarefa exibido no cabeçalho. |
|
|
32
|
+
| `intro` | `ReactNode` | | Contexto relevante no início do corpo, como texto ou `Alert`. |
|
|
33
|
+
| `submitLabel` | `string` | `'Salvar'` | Resultado da ação principal. Em criação, informe `Criar {recurso}`; em edição, `Salvar alterações`. |
|
|
34
|
+
| `…ActionFormProps` | `action, defaultValues, onSuccess, fieldOptions…` | | Demais propriedades repassadas ao `ActionForm` interno. |
|
|
@@ -6,7 +6,7 @@ outras actions. O consumidor compõe os itens por `children`; o modal mantém os
|
|
|
6
6
|
ações.
|
|
7
7
|
|
|
8
8
|
```tsx preview col
|
|
9
|
-
const [open, setOpen] = useState(false)
|
|
9
|
+
const [open, setOpen] = useState(false);
|
|
10
10
|
|
|
11
11
|
render(
|
|
12
12
|
<DocBrowserActionProvider>
|
|
@@ -17,7 +17,6 @@ render(
|
|
|
17
17
|
open={open}
|
|
18
18
|
onOpenChange={setOpen}
|
|
19
19
|
title="Workspaces"
|
|
20
|
-
description="Back-office"
|
|
21
20
|
actions={
|
|
22
21
|
<Button size="sm" variant="outline">
|
|
23
22
|
<Plus /> Workspace
|
|
@@ -28,17 +27,22 @@ render(
|
|
|
28
27
|
{(workspaces) => (
|
|
29
28
|
<div className="space-y-2">
|
|
30
29
|
{workspaces.map((w) => (
|
|
31
|
-
<div
|
|
30
|
+
<div
|
|
31
|
+
key={w.id}
|
|
32
|
+
className="flex items-center gap-2 rounded-md border border-border p-3"
|
|
33
|
+
>
|
|
32
34
|
<span className="text-sm font-semibold">{w.name}</span>
|
|
33
35
|
<DictionaryValue dict={docWorkspaceStatus} value={w.status} />
|
|
34
|
-
<span className="ml-auto text-xs text-muted-foreground">
|
|
36
|
+
<span className="ml-auto text-xs text-muted-foreground">
|
|
37
|
+
{w.agents} agentes
|
|
38
|
+
</span>
|
|
35
39
|
</div>
|
|
36
40
|
))}
|
|
37
41
|
</div>
|
|
38
42
|
)}
|
|
39
43
|
</ActionListDialog>
|
|
40
44
|
</DocBrowserActionProvider>,
|
|
41
|
-
)
|
|
45
|
+
);
|
|
42
46
|
```
|
|
43
47
|
|
|
44
48
|
## Estado adicional do consumidor
|
|
@@ -59,15 +63,16 @@ corpo do modal.
|
|
|
59
63
|
|
|
60
64
|
## Propriedades de ActionListDialog
|
|
61
65
|
|
|
62
|
-
| Propriedade
|
|
63
|
-
|
|
64
|
-
| `action / input`
|
|
65
|
-
| `open / onOpenChange`
|
|
66
|
-
| `title
|
|
67
|
-
| `
|
|
68
|
-
| `
|
|
69
|
-
| `
|
|
70
|
-
| `
|
|
71
|
-
| `
|
|
72
|
-
| `
|
|
73
|
-
| `
|
|
66
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
67
|
+
| ------------------------------------------ | ----------------------------------- | -------------------- | ------------------------------------------------------------------------------------------------- |
|
|
68
|
+
| `action / input` | `ListAction / TInput` | | O contrato e os filtros — mudou o input, re-busca; `invalidates` de forms/triggers refaz sozinho. |
|
|
69
|
+
| `open / onOpenChange` | `boolean / (open) => void` | | Controle do modal — de quem orquestra. |
|
|
70
|
+
| `title` | `string` | | Nome da lista exibido no cabeçalho fixo. |
|
|
71
|
+
| `intro` | `ReactNode` | | Contexto relevante no início do corpo, como texto ou `Alert`. |
|
|
72
|
+
| `children` | `(items, refetch) => ReactNode` | | O layout dos itens (cards/linhas) — os estados já saíram daqui. |
|
|
73
|
+
| `note` | `ReactNode \| (items) => ReactNode` | `"N no total"` | Nota à esquerda da toolbar — o default é derivado dos itens. |
|
|
74
|
+
| `actions` | `ReactNode` | | Ação à direita da toolbar — em geral o botão de criar. |
|
|
75
|
+
| `empty` | `(items) => boolean` | `items.length === 0` | Sobrepõe o vazio derivado. |
|
|
76
|
+
| `loading` | `boolean` | | Carga extra agregada à do fetch (query irmã). |
|
|
77
|
+
| `emptyMessage / errorMessage / retryLabel` | `string` | | Textos dos estados; `retryLabel` nomeia a ação somente com ícone e seu tooltip. |
|
|
78
|
+
| `className` | `string` | `sm:max-w-3xl` | Largura do DialogContent. |
|