@softize/opus 12.11.0 → 13.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 +29 -0
- package/bin/lib/check.mjs +2 -7
- package/bin/lib/copy.mjs +1 -5
- package/docs/adr/0005-structural-surfaces-share-an-explicit-anatomy.md +93 -10
- package/docs/adr/0007-toast-actions-form-an-ordered-collection.md +63 -0
- package/docs/adr/0008-hierarchical-navigation-is-composed-at-the-consumer-boundary.md +71 -0
- package/docs/radius-scale.md +1 -1
- package/package.json +1 -1
- package/registry/skills/maintain-opus-docs/SKILL.md +83 -0
- package/registry/skills/maintain-opus-docs/agents/openai.yaml +4 -0
- package/registry/skills/maintain-opus-docs/references/editorial-standard.md +85 -0
- package/registry/skills/maintain-opus-docs/references/evaluations.md +34 -0
- package/registry/skills/maintain-opus-docs/scripts/audit-docs.mjs +81 -0
- package/src/ui/components/patterns/confirm.tsx +140 -40
- package/src/ui/components/patterns/list.tsx +35 -40
- package/src/ui/components/patterns/page-state.tsx +2 -2
- package/src/ui/components/patterns/sidebar.tsx +26 -26
- package/src/ui/components/patterns/trigger.tsx +25 -22
- package/src/ui/components/primitives/alert.tsx +3 -3
- package/src/ui/components/primitives/dialog.tsx +196 -39
- package/src/ui/components/primitives/drawer.tsx +8 -5
- package/src/ui/components/primitives/empty.tsx +3 -3
- package/src/ui/components/primitives/item.tsx +3 -3
- package/src/ui/components/primitives/sonner.tsx +187 -8
- package/src/ui/docs/DocBrowser.tsx +102 -23
- package/src/ui/docs/content/accordion.md +22 -16
- package/src/ui/docs/content/action-form-card.md +8 -8
- package/src/ui/docs/content/action-form-dialog.md +9 -9
- package/src/ui/docs/content/action-form.md +28 -34
- package/src/ui/docs/content/action-list-dialog.md +11 -6
- package/src/ui/docs/content/action-list.md +64 -39
- package/src/ui/docs/content/action-trigger.md +21 -14
- package/src/ui/docs/content/action-view.md +8 -8
- package/src/ui/docs/content/actions.md +9 -9
- package/src/ui/docs/content/ai.md +3 -3
- package/src/ui/docs/content/alert.md +14 -12
- package/src/ui/docs/content/aspect-ratio.md +4 -4
- package/src/ui/docs/content/audit.md +2 -2
- package/src/ui/docs/content/auth.md +3 -3
- package/src/ui/docs/content/avatar.md +34 -14
- package/src/ui/docs/content/badge.md +3 -3
- package/src/ui/docs/content/breadcrumb.md +13 -8
- package/src/ui/docs/content/button.md +81 -6
- package/src/ui/docs/content/calendar.md +5 -5
- package/src/ui/docs/content/card.md +1 -1
- package/src/ui/docs/content/carousel.md +16 -11
- package/src/ui/docs/content/chat.md +3 -3
- package/src/ui/docs/content/checkbox.md +7 -7
- package/src/ui/docs/content/cli.md +5 -5
- package/src/ui/docs/content/collapsible.md +8 -8
- package/src/ui/docs/content/command.md +16 -8
- package/src/ui/docs/content/composer.md +2 -2
- package/src/ui/docs/content/content.md +2 -2
- package/src/ui/docs/content/copyable.md +4 -3
- package/src/ui/docs/content/customization.md +5 -5
- package/src/ui/docs/content/cycle.md +3 -3
- package/src/ui/docs/content/data-state.md +11 -12
- package/src/ui/docs/content/data.md +26 -33
- package/src/ui/docs/content/detail.md +3 -3
- package/src/ui/docs/content/dialog.md +339 -31
- package/src/ui/docs/content/dictionary-value.md +8 -8
- package/src/ui/docs/content/dock.md +3 -3
- package/src/ui/docs/content/drawer.md +27 -14
- package/src/ui/docs/content/empty-value.md +2 -2
- package/src/ui/docs/content/empty.md +19 -12
- package/src/ui/docs/content/events.md +4 -4
- package/src/ui/docs/content/field.md +34 -12
- package/src/ui/docs/content/getting-started.md +1 -1
- package/src/ui/docs/content/icon-picker.md +8 -4
- package/src/ui/docs/content/input-otp.md +20 -12
- package/src/ui/docs/content/input.md +121 -9
- package/src/ui/docs/content/item.md +27 -13
- package/src/ui/docs/content/kbd.md +19 -11
- package/src/ui/docs/content/label.md +5 -3
- package/src/ui/docs/content/log.md +4 -4
- package/src/ui/docs/content/markdown.md +7 -6
- package/src/ui/docs/content/mcp.md +13 -15
- package/src/ui/docs/content/menu.md +34 -16
- package/src/ui/docs/content/observability.md +2 -2
- package/src/ui/docs/content/page.md +51 -6
- package/src/ui/docs/content/pagination.md +22 -17
- package/src/ui/docs/content/popover.md +16 -8
- package/src/ui/docs/content/progress.md +7 -5
- package/src/ui/docs/content/queue.md +5 -5
- package/src/ui/docs/content/radio-group.md +20 -12
- package/src/ui/docs/content/router.md +11 -6
- package/src/ui/docs/content/scheduler.md +4 -5
- package/src/ui/docs/content/scroll-area.md +12 -7
- package/src/ui/docs/content/select.md +42 -29
- package/src/ui/docs/content/separator.md +5 -5
- package/src/ui/docs/content/sidebar.md +323 -54
- package/src/ui/docs/content/skeleton.md +3 -2
- package/src/ui/docs/content/slider.md +8 -7
- package/src/ui/docs/content/spinner.md +8 -8
- package/src/ui/docs/content/split.md +8 -5
- package/src/ui/docs/content/storage.md +6 -8
- package/src/ui/docs/content/switch.md +8 -7
- package/src/ui/docs/content/table.md +13 -3
- package/src/ui/docs/content/tabs.md +28 -14
- package/src/ui/docs/content/testing.md +9 -11
- package/src/ui/docs/content/textarea.md +5 -4
- package/src/ui/docs/content/toast.md +47 -13
- package/src/ui/docs/content/toggle.md +75 -7
- package/src/ui/docs/content/tokens.md +3 -3
- package/src/ui/docs/content/tooltip.md +19 -11
- package/src/ui/docs/content/truncate.md +7 -8
- package/src/ui/docs/content/ui.md +10 -9
- package/src/ui/docs/content/upgrading.md +7 -8
- package/src/ui/docs/registry.tsx +20 -37
- package/src/ui/meta.ts +64 -94
- package/src/ui/react.tsx +15 -16
- package/src/ui/theme.css +50 -0
- package/src/ui/components/primitives/alert-dialog.tsx +0 -192
- package/src/ui/docs/content/alert-dialog.md +0 -73
- package/src/ui/docs/content/button-group.md +0 -71
- package/src/ui/docs/content/confirm.md +0 -120
- package/src/ui/docs/content/input-group.md +0 -79
- package/src/ui/docs/content/page-state.md +0 -45
- package/src/ui/docs/content/toggle-group.md +0 -81
|
@@ -1,35 +1,134 @@
|
|
|
1
1
|
import * as React from 'react'
|
|
2
2
|
import * as DialogPrimitive from '@radix-ui/react-dialog'
|
|
3
|
+
import { AlertDialog as AlertDialogPrimitive } from 'radix-ui'
|
|
3
4
|
import { XIcon } from 'lucide-react'
|
|
4
5
|
import { cn } from '../../lib/cn.ts'
|
|
5
6
|
|
|
6
|
-
export
|
|
7
|
-
|
|
7
|
+
export type DialogMode = 'default' | 'alert'
|
|
8
|
+
|
|
9
|
+
interface DialogContextValue {
|
|
10
|
+
mode: DialogMode
|
|
11
|
+
onResult?: (result: string) => void
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const DialogContext = React.createContext<DialogContextValue>({ mode: 'default' })
|
|
15
|
+
|
|
16
|
+
function useDialogContext(): DialogContextValue {
|
|
17
|
+
return React.useContext(DialogContext)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface DialogProps
|
|
21
|
+
extends React.ComponentProps<typeof DialogPrimitive.Root> {
|
|
22
|
+
/** `alert` exige uma resposta explícita e deriva `role="alertdialog"`. */
|
|
23
|
+
mode?: DialogMode
|
|
24
|
+
/** Recebe o `result` do `DialogClose` acionado. */
|
|
25
|
+
onResult?: (result: string) => void
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function Dialog({
|
|
29
|
+
mode = 'default',
|
|
30
|
+
onResult,
|
|
31
|
+
modal,
|
|
32
|
+
...props
|
|
33
|
+
}: DialogProps) {
|
|
34
|
+
const context = React.useMemo(() => ({ mode, onResult }), [mode, onResult])
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<DialogContext.Provider value={context}>
|
|
38
|
+
{mode === 'alert' ? (
|
|
39
|
+
<AlertDialogPrimitive.Root data-slot="dialog" {...props} />
|
|
40
|
+
) : (
|
|
41
|
+
<DialogPrimitive.Root data-slot="dialog" modal={modal} {...props} />
|
|
42
|
+
)}
|
|
43
|
+
</DialogContext.Provider>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function DialogTrigger({
|
|
48
|
+
...props
|
|
49
|
+
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
|
|
50
|
+
const { mode } = useDialogContext()
|
|
51
|
+
return mode === 'alert' ? (
|
|
52
|
+
<AlertDialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
|
|
53
|
+
) : (
|
|
54
|
+
<DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
|
|
55
|
+
)
|
|
8
56
|
}
|
|
9
57
|
|
|
10
|
-
export function
|
|
11
|
-
|
|
58
|
+
export function DialogPortal({
|
|
59
|
+
...props
|
|
60
|
+
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
|
|
61
|
+
const { mode } = useDialogContext()
|
|
62
|
+
return mode === 'alert' ? (
|
|
63
|
+
<AlertDialogPrimitive.Portal data-slot="dialog-portal" {...props} />
|
|
64
|
+
) : (
|
|
65
|
+
<DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
|
|
66
|
+
)
|
|
12
67
|
}
|
|
13
68
|
|
|
14
|
-
export
|
|
15
|
-
|
|
69
|
+
export interface DialogCloseProps
|
|
70
|
+
extends React.ComponentProps<typeof DialogPrimitive.Close> {
|
|
71
|
+
/** No modo `alert`, registra este controle como destino inicial seguro do foco. */
|
|
72
|
+
initialFocus?: boolean
|
|
73
|
+
/** Resposta entregue a `Dialog.onResult` antes do fechamento. */
|
|
74
|
+
result?: string
|
|
16
75
|
}
|
|
17
76
|
|
|
18
|
-
export function DialogClose({
|
|
19
|
-
|
|
77
|
+
export function DialogClose({
|
|
78
|
+
initialFocus = false,
|
|
79
|
+
result,
|
|
80
|
+
onClick,
|
|
81
|
+
...props
|
|
82
|
+
}: DialogCloseProps) {
|
|
83
|
+
const root = useDialogContext()
|
|
84
|
+
const handleClick: React.MouseEventHandler<HTMLButtonElement> = (event) => {
|
|
85
|
+
onClick?.(event)
|
|
86
|
+
if (!event.defaultPrevented && result !== undefined) root.onResult?.(result)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (root.mode === 'alert') {
|
|
90
|
+
const ClosePrimitive = initialFocus
|
|
91
|
+
? AlertDialogPrimitive.Cancel
|
|
92
|
+
: AlertDialogPrimitive.Action
|
|
93
|
+
return (
|
|
94
|
+
<ClosePrimitive
|
|
95
|
+
data-slot="dialog-close"
|
|
96
|
+
data-initial-focus={initialFocus || undefined}
|
|
97
|
+
onClick={handleClick}
|
|
98
|
+
{...props}
|
|
99
|
+
/>
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<DialogPrimitive.Close
|
|
105
|
+
data-slot="dialog-close"
|
|
106
|
+
data-initial-focus={initialFocus || undefined}
|
|
107
|
+
onClick={handleClick}
|
|
108
|
+
{...props}
|
|
109
|
+
/>
|
|
110
|
+
)
|
|
20
111
|
}
|
|
21
112
|
|
|
22
113
|
export function DialogOverlay({
|
|
23
114
|
className,
|
|
24
115
|
...props
|
|
25
116
|
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
|
|
26
|
-
|
|
117
|
+
const { mode } = useDialogContext()
|
|
118
|
+
const overlayClasses = cn(
|
|
119
|
+
'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',
|
|
120
|
+
className,
|
|
121
|
+
)
|
|
122
|
+
return mode === 'alert' ? (
|
|
123
|
+
<AlertDialogPrimitive.Overlay
|
|
124
|
+
data-slot="dialog-overlay"
|
|
125
|
+
className={overlayClasses}
|
|
126
|
+
{...props}
|
|
127
|
+
/>
|
|
128
|
+
) : (
|
|
27
129
|
<DialogPrimitive.Overlay
|
|
28
130
|
data-slot="dialog-overlay"
|
|
29
|
-
className={
|
|
30
|
-
'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',
|
|
31
|
-
className,
|
|
32
|
-
)}
|
|
131
|
+
className={overlayClasses}
|
|
33
132
|
{...props}
|
|
34
133
|
/>
|
|
35
134
|
)
|
|
@@ -38,29 +137,48 @@ export function DialogOverlay({
|
|
|
38
137
|
export function DialogContent({
|
|
39
138
|
className,
|
|
40
139
|
children,
|
|
41
|
-
showCloseButton
|
|
140
|
+
showCloseButton,
|
|
42
141
|
...props
|
|
43
142
|
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
|
|
44
|
-
/** Mostra o
|
|
143
|
+
/** Mostra o “X” no modo padrão. O modo `alert` exige uma resposta identificável. */
|
|
45
144
|
showCloseButton?: boolean
|
|
46
145
|
}) {
|
|
146
|
+
const { mode } = useDialogContext()
|
|
147
|
+
const showClose = showCloseButton ?? mode !== 'alert'
|
|
148
|
+
const contentClasses =
|
|
149
|
+
'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'
|
|
150
|
+
|
|
151
|
+
if (mode === 'alert') {
|
|
152
|
+
return (
|
|
153
|
+
<DialogPortal>
|
|
154
|
+
<DialogOverlay />
|
|
155
|
+
<AlertDialogPrimitive.Content
|
|
156
|
+
data-slot="dialog-content"
|
|
157
|
+
data-mode="alert"
|
|
158
|
+
className={cn(contentClasses, 'grid max-w-xs gap-4 p-6', className)}
|
|
159
|
+
{...props}
|
|
160
|
+
>
|
|
161
|
+
{children}
|
|
162
|
+
</AlertDialogPrimitive.Content>
|
|
163
|
+
</DialogPortal>
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
|
|
47
167
|
return (
|
|
48
168
|
<DialogPortal>
|
|
49
169
|
<DialogOverlay />
|
|
50
|
-
{/* Superfície elevada: mantemos bg-card (divergência da casa; o shadcn usa bg-background). */}
|
|
51
170
|
<DialogPrimitive.Content
|
|
52
171
|
data-slot="dialog-content"
|
|
172
|
+
data-mode="default"
|
|
53
173
|
className={cn(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
// precisa negar margem pra escapar de um p-6 embutido.
|
|
57
|
-
"fixed left-[50%] top-[50%] z-50 flex max-h-[85vh] w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] flex-col overflow-clip rounded-xl bg-card text-card-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 sm:max-w-lg",
|
|
174
|
+
contentClasses,
|
|
175
|
+
'flex max-h-[85vh] flex-col sm:max-w-lg',
|
|
58
176
|
className,
|
|
59
177
|
)}
|
|
60
178
|
{...props}
|
|
61
179
|
>
|
|
62
180
|
{children}
|
|
63
|
-
{
|
|
181
|
+
{showClose && (
|
|
64
182
|
<DialogPrimitive.Close
|
|
65
183
|
data-slot="dialog-close"
|
|
66
184
|
className="absolute right-4 top-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 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"
|
|
@@ -74,17 +192,18 @@ export function DialogContent({
|
|
|
74
192
|
)
|
|
75
193
|
}
|
|
76
194
|
|
|
77
|
-
export function DialogHeader({
|
|
195
|
+
export function DialogHeader({
|
|
196
|
+
className,
|
|
197
|
+
...props
|
|
198
|
+
}: React.ComponentProps<'div'>) {
|
|
199
|
+
const { mode } = useDialogContext()
|
|
78
200
|
return (
|
|
79
201
|
<div
|
|
80
202
|
data-slot="dialog-header"
|
|
81
|
-
// Fixo (shrink-0), com divisor embaixo (border-b). p-5: padding igual nos quatro
|
|
82
|
-
// lados, casando com o body. O :has tira o divisor quando o header é seguido
|
|
83
|
-
// DIRETO pelo footer (confirm sem body) — senão o border-b dele encostaria no
|
|
84
|
-
// A compensação coincide com a soma das hairlines; o divisor fica no footer.
|
|
85
203
|
className={cn(
|
|
86
|
-
|
|
87
|
-
|
|
204
|
+
mode === 'alert'
|
|
205
|
+
? 'grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=dialog-media]:grid-rows-[auto_auto_1fr]'
|
|
206
|
+
: 'flex shrink-0 flex-col gap-2 border-b p-5 text-center sm:text-left [&:has(+[data-slot=dialog-footer])]:border-b-0',
|
|
88
207
|
className,
|
|
89
208
|
)}
|
|
90
209
|
{...props}
|
|
@@ -92,27 +211,51 @@ export function DialogHeader({ className, ...props }: React.ComponentProps<'div'
|
|
|
92
211
|
)
|
|
93
212
|
}
|
|
94
213
|
|
|
95
|
-
export function
|
|
214
|
+
export function DialogMedia({
|
|
215
|
+
className,
|
|
216
|
+
...props
|
|
217
|
+
}: React.ComponentProps<'div'>) {
|
|
218
|
+
return (
|
|
219
|
+
<div
|
|
220
|
+
data-slot="dialog-media"
|
|
221
|
+
className={cn(
|
|
222
|
+
'mb-2 inline-flex size-16 items-center justify-center rounded-md bg-muted *:[svg:not([class*="size-"])]:size-8',
|
|
223
|
+
className,
|
|
224
|
+
)}
|
|
225
|
+
{...props}
|
|
226
|
+
/>
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function DialogBody({
|
|
231
|
+
className,
|
|
232
|
+
...props
|
|
233
|
+
}: React.ComponentProps<'div'>) {
|
|
234
|
+
const { mode } = useDialogContext()
|
|
96
235
|
return (
|
|
97
236
|
<div
|
|
98
237
|
data-slot="dialog-body"
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
238
|
+
className={cn(
|
|
239
|
+
mode === 'alert' ? 'min-w-0' : 'min-h-0 flex-1 overflow-y-auto p-5',
|
|
240
|
+
className,
|
|
241
|
+
)}
|
|
102
242
|
{...props}
|
|
103
243
|
/>
|
|
104
244
|
)
|
|
105
245
|
}
|
|
106
246
|
|
|
107
|
-
export function DialogFooter({
|
|
247
|
+
export function DialogFooter({
|
|
248
|
+
className,
|
|
249
|
+
...props
|
|
250
|
+
}: React.ComponentProps<'div'>) {
|
|
251
|
+
const { mode } = useDialogContext()
|
|
108
252
|
return (
|
|
109
253
|
<div
|
|
110
254
|
data-slot="dialog-footer"
|
|
111
|
-
// Faixa destacada (divergência da casa, ao estilo do site do shadcn): borda em cima
|
|
112
|
-
// + fundo muted. Sangra até as bordas sozinha — o content não tem padding, então não
|
|
113
|
-
// há -mx pra compensar; o recorte do content arredonda o canto de baixo.
|
|
114
255
|
className={cn(
|
|
115
|
-
|
|
256
|
+
mode === 'alert'
|
|
257
|
+
? 'grid grid-cols-2 gap-2 [&>*:only-child]:col-span-full'
|
|
258
|
+
: 'flex shrink-0 flex-col-reverse gap-2 border-t bg-muted/30 px-5 py-4 sm:flex-row sm:justify-end',
|
|
116
259
|
className,
|
|
117
260
|
)}
|
|
118
261
|
{...props}
|
|
@@ -124,7 +267,14 @@ export function DialogTitle({
|
|
|
124
267
|
className,
|
|
125
268
|
...props
|
|
126
269
|
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
|
|
127
|
-
|
|
270
|
+
const { mode } = useDialogContext()
|
|
271
|
+
return mode === 'alert' ? (
|
|
272
|
+
<AlertDialogPrimitive.Title
|
|
273
|
+
data-slot="dialog-title"
|
|
274
|
+
className={cn('text-lg font-semibold', className)}
|
|
275
|
+
{...props}
|
|
276
|
+
/>
|
|
277
|
+
) : (
|
|
128
278
|
<DialogPrimitive.Title
|
|
129
279
|
data-slot="dialog-title"
|
|
130
280
|
className={cn('text-lg font-semibold leading-none', className)}
|
|
@@ -137,7 +287,14 @@ export function DialogDescription({
|
|
|
137
287
|
className,
|
|
138
288
|
...props
|
|
139
289
|
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
|
|
140
|
-
|
|
290
|
+
const { mode } = useDialogContext()
|
|
291
|
+
return mode === 'alert' ? (
|
|
292
|
+
<AlertDialogPrimitive.Description
|
|
293
|
+
data-slot="dialog-description"
|
|
294
|
+
className={cn('text-sm text-muted-foreground', className)}
|
|
295
|
+
{...props}
|
|
296
|
+
/>
|
|
297
|
+
) : (
|
|
141
298
|
<DialogPrimitive.Description
|
|
142
299
|
data-slot="dialog-description"
|
|
143
300
|
className={cn('text-sm text-muted-foreground', className)}
|
|
@@ -60,7 +60,7 @@ function DrawerContent({
|
|
|
60
60
|
<DrawerPrimitive.Content
|
|
61
61
|
data-slot="drawer-content"
|
|
62
62
|
className={cn(
|
|
63
|
-
"fixed z-50 flex flex-col
|
|
63
|
+
"fixed z-50 flex flex-col bg-background shadow-lg transition ease-in-out data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:animate-in data-[state=open]:duration-500",
|
|
64
64
|
side === "right" &&
|
|
65
65
|
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
|
|
66
66
|
side === "left" &&
|
|
@@ -89,7 +89,7 @@ function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
89
89
|
return (
|
|
90
90
|
<div
|
|
91
91
|
data-slot="drawer-header"
|
|
92
|
-
className={cn("flex flex-col gap-
|
|
92
|
+
className={cn("flex shrink-0 flex-col gap-2 border-b p-5", className)}
|
|
93
93
|
{...props}
|
|
94
94
|
/>
|
|
95
95
|
);
|
|
@@ -99,7 +99,7 @@ function DrawerBody({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
99
99
|
return (
|
|
100
100
|
<div
|
|
101
101
|
data-slot="drawer-body"
|
|
102
|
-
className={cn("min-h-0 flex-1 overflow-y-auto p-
|
|
102
|
+
className={cn("min-h-0 flex-1 overflow-y-auto p-5", className)}
|
|
103
103
|
{...props}
|
|
104
104
|
/>
|
|
105
105
|
);
|
|
@@ -109,7 +109,10 @@ function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
109
109
|
return (
|
|
110
110
|
<div
|
|
111
111
|
data-slot="drawer-footer"
|
|
112
|
-
className={cn(
|
|
112
|
+
className={cn(
|
|
113
|
+
"mt-auto flex shrink-0 flex-col-reverse gap-2 border-t bg-muted/30 px-5 py-4 sm:flex-row sm:justify-end",
|
|
114
|
+
className,
|
|
115
|
+
)}
|
|
113
116
|
{...props}
|
|
114
117
|
/>
|
|
115
118
|
);
|
|
@@ -122,7 +125,7 @@ function DrawerTitle({
|
|
|
122
125
|
return (
|
|
123
126
|
<DrawerPrimitive.Title
|
|
124
127
|
data-slot="drawer-title"
|
|
125
|
-
className={cn("font-semibold text-foreground", className)}
|
|
128
|
+
className={cn("text-lg font-semibold leading-none text-foreground", className)}
|
|
126
129
|
{...props}
|
|
127
130
|
/>
|
|
128
131
|
);
|
|
@@ -81,10 +81,10 @@ function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
|
|
|
81
81
|
)
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
function
|
|
84
|
+
function EmptyActions({ className, ...props }: React.ComponentProps<"div">) {
|
|
85
85
|
return (
|
|
86
86
|
<div
|
|
87
|
-
data-slot="empty-
|
|
87
|
+
data-slot="empty-actions"
|
|
88
88
|
className={cn(
|
|
89
89
|
"flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance",
|
|
90
90
|
className
|
|
@@ -99,6 +99,6 @@ export {
|
|
|
99
99
|
EmptyHeader,
|
|
100
100
|
EmptyTitle,
|
|
101
101
|
EmptyDescription,
|
|
102
|
-
|
|
102
|
+
EmptyActions,
|
|
103
103
|
EmptyMedia,
|
|
104
104
|
}
|
|
@@ -116,14 +116,14 @@ function Item({
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
const itemMediaVariants = cva(
|
|
119
|
-
"flex shrink-0 self-
|
|
119
|
+
"flex shrink-0 self-start items-center justify-center gap-2 [&_svg]:pointer-events-none",
|
|
120
120
|
{
|
|
121
121
|
variants: {
|
|
122
122
|
variant: {
|
|
123
123
|
default: "bg-transparent",
|
|
124
|
-
icon: "
|
|
124
|
+
icon: "size-8 rounded-sm border bg-muted [&_svg:not([class*='size-'])]:size-4",
|
|
125
125
|
image:
|
|
126
|
-
"
|
|
126
|
+
"size-10 overflow-hidden rounded-sm [&_img]:size-full [&_img]:object-cover",
|
|
127
127
|
},
|
|
128
128
|
},
|
|
129
129
|
defaultVariants: { variant: "default" },
|
|
@@ -6,7 +6,149 @@ import {
|
|
|
6
6
|
OctagonXIcon,
|
|
7
7
|
TriangleAlertIcon,
|
|
8
8
|
} from 'lucide-react'
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
Toaster as Sonner,
|
|
11
|
+
toast as sonnerToast,
|
|
12
|
+
type ExternalToast,
|
|
13
|
+
type ToasterProps,
|
|
14
|
+
} from 'sonner'
|
|
15
|
+
import { cn } from '../../lib/cn.ts'
|
|
16
|
+
import {
|
|
17
|
+
Button,
|
|
18
|
+
type ButtonContext,
|
|
19
|
+
type ButtonVariant,
|
|
20
|
+
} from './button.tsx'
|
|
21
|
+
|
|
22
|
+
type ToastId = string | number
|
|
23
|
+
type ToastMessage = React.ReactNode | (() => React.ReactNode)
|
|
24
|
+
|
|
25
|
+
export interface ToastAction {
|
|
26
|
+
key?: React.Key
|
|
27
|
+
label: React.ReactNode
|
|
28
|
+
onClick: (event: React.MouseEvent<HTMLButtonElement>) => void
|
|
29
|
+
context?: ButtonContext
|
|
30
|
+
variant?: ButtonVariant
|
|
31
|
+
disabled?: boolean
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ToastOptions extends Omit<ExternalToast, 'action' | 'cancel'> {
|
|
35
|
+
actions?: readonly ToastAction[]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface ToastApi {
|
|
39
|
+
(message: ToastMessage, options?: ToastOptions): ToastId
|
|
40
|
+
success: (message: ToastMessage, options?: ToastOptions) => ToastId
|
|
41
|
+
info: (message: ToastMessage, options?: ToastOptions) => ToastId
|
|
42
|
+
warning: (message: ToastMessage, options?: ToastOptions) => ToastId
|
|
43
|
+
error: (message: ToastMessage, options?: ToastOptions) => ToastId
|
|
44
|
+
message: (message: ToastMessage, options?: ToastOptions) => ToastId
|
|
45
|
+
loading: (message: ToastMessage, options?: ToastOptions) => ToastId
|
|
46
|
+
custom: typeof sonnerToast.custom
|
|
47
|
+
promise: typeof sonnerToast.promise
|
|
48
|
+
dismiss: typeof sonnerToast.dismiss
|
|
49
|
+
getHistory: typeof sonnerToast.getHistory
|
|
50
|
+
getToasts: typeof sonnerToast.getToasts
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface ToastMediaProps {
|
|
54
|
+
children: React.ReactNode
|
|
55
|
+
className: string
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function ToastMedia({ children, className }: ToastMediaProps): React.ReactElement {
|
|
59
|
+
return (
|
|
60
|
+
<span
|
|
61
|
+
className={cn(
|
|
62
|
+
'flex size-9 items-center justify-center rounded-md bg-current/10 [&_svg]:size-5',
|
|
63
|
+
className,
|
|
64
|
+
)}
|
|
65
|
+
>
|
|
66
|
+
{children}
|
|
67
|
+
</span>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface ToastActionsProps {
|
|
72
|
+
actions: readonly ToastAction[]
|
|
73
|
+
getToastId: () => ToastId | undefined
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function ToastActions({ actions, getToastId }: ToastActionsProps): React.ReactElement {
|
|
77
|
+
return (
|
|
78
|
+
<div data-slot="toast-actions" className="ms-auto flex shrink-0 items-center gap-2">
|
|
79
|
+
{actions.map((action, index) => {
|
|
80
|
+
const primary = index === actions.length - 1
|
|
81
|
+
return (
|
|
82
|
+
<Button
|
|
83
|
+
key={action.key ?? index}
|
|
84
|
+
type="button"
|
|
85
|
+
size="sm"
|
|
86
|
+
context={action.context ?? (primary ? 'primary' : 'neutral')}
|
|
87
|
+
variant={action.variant ?? (primary ? 'solid' : 'ghost')}
|
|
88
|
+
disabled={action.disabled}
|
|
89
|
+
onClick={(event) => {
|
|
90
|
+
action.onClick(event)
|
|
91
|
+
if (!event.defaultPrevented) {
|
|
92
|
+
const toastId = getToastId()
|
|
93
|
+
if (toastId !== undefined) sonnerToast.dismiss(toastId)
|
|
94
|
+
}
|
|
95
|
+
}}
|
|
96
|
+
>
|
|
97
|
+
{action.label}
|
|
98
|
+
</Button>
|
|
99
|
+
)
|
|
100
|
+
})}
|
|
101
|
+
</div>
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function toSonnerOptions(
|
|
106
|
+
options: ToastOptions | undefined,
|
|
107
|
+
getToastId: () => ToastId | undefined,
|
|
108
|
+
): ExternalToast | undefined {
|
|
109
|
+
if (options === undefined) return undefined
|
|
110
|
+
const { actions, ...rest } = options
|
|
111
|
+
if (actions === undefined) return rest
|
|
112
|
+
return {
|
|
113
|
+
...rest,
|
|
114
|
+
action: actions.length > 0 ? <ToastActions actions={actions} getToastId={getToastId} /> : undefined,
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
type SonnerToastMethod = (message: ToastMessage, options?: ExternalToast) => ToastId
|
|
119
|
+
|
|
120
|
+
function dispatchToast(
|
|
121
|
+
method: SonnerToastMethod,
|
|
122
|
+
message: ToastMessage,
|
|
123
|
+
options?: ToastOptions,
|
|
124
|
+
): ToastId {
|
|
125
|
+
let toastId: ToastId | undefined
|
|
126
|
+
toastId = method(message, toSonnerOptions(options, () => toastId))
|
|
127
|
+
return toastId
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const toast: ToastApi = Object.assign(
|
|
131
|
+
(message: ToastMessage, options?: ToastOptions) => dispatchToast(sonnerToast, message, options),
|
|
132
|
+
{
|
|
133
|
+
success: (message: ToastMessage, options?: ToastOptions) =>
|
|
134
|
+
dispatchToast(sonnerToast.success, message, options),
|
|
135
|
+
info: (message: ToastMessage, options?: ToastOptions) =>
|
|
136
|
+
dispatchToast(sonnerToast.info, message, options),
|
|
137
|
+
warning: (message: ToastMessage, options?: ToastOptions) =>
|
|
138
|
+
dispatchToast(sonnerToast.warning, message, options),
|
|
139
|
+
error: (message: ToastMessage, options?: ToastOptions) =>
|
|
140
|
+
dispatchToast(sonnerToast.error, message, options),
|
|
141
|
+
message: (message: ToastMessage, options?: ToastOptions) =>
|
|
142
|
+
dispatchToast(sonnerToast.message, message, options),
|
|
143
|
+
loading: (message: ToastMessage, options?: ToastOptions) =>
|
|
144
|
+
dispatchToast(sonnerToast.loading, message, options),
|
|
145
|
+
custom: sonnerToast.custom,
|
|
146
|
+
promise: sonnerToast.promise,
|
|
147
|
+
dismiss: sonnerToast.dismiss,
|
|
148
|
+
getHistory: sonnerToast.getHistory,
|
|
149
|
+
getToasts: sonnerToast.getToasts,
|
|
150
|
+
},
|
|
151
|
+
)
|
|
10
152
|
|
|
11
153
|
/**
|
|
12
154
|
* Toaster temático (sonner) — monte 1x no root do app. Os toasts saem estilizados
|
|
@@ -17,18 +159,54 @@ import { Toaster as Sonner, toast, type ToasterProps } from 'sonner'
|
|
|
17
159
|
* usamos next-themes — passe `theme` por prop se precisar forçar (default 'system').
|
|
18
160
|
* Elevação dark preservada: `--normal-bg` aponta pro popover (não pro background).
|
|
19
161
|
*/
|
|
20
|
-
export function Toaster({
|
|
162
|
+
export function Toaster({
|
|
163
|
+
className,
|
|
164
|
+
icons,
|
|
165
|
+
style,
|
|
166
|
+
toastOptions,
|
|
167
|
+
...props
|
|
168
|
+
}: ToasterProps): React.ReactElement {
|
|
21
169
|
return (
|
|
22
170
|
<Sonner
|
|
23
171
|
data-slot="toaster"
|
|
24
172
|
theme="system"
|
|
25
|
-
className=
|
|
173
|
+
className={cn('toaster group', className)}
|
|
26
174
|
icons={{
|
|
27
|
-
success:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
175
|
+
success: (
|
|
176
|
+
<ToastMedia className="text-context-success-emphasis">
|
|
177
|
+
<CircleCheckIcon />
|
|
178
|
+
</ToastMedia>
|
|
179
|
+
),
|
|
180
|
+
info: (
|
|
181
|
+
<ToastMedia className="text-context-info-emphasis">
|
|
182
|
+
<InfoIcon />
|
|
183
|
+
</ToastMedia>
|
|
184
|
+
),
|
|
185
|
+
warning: (
|
|
186
|
+
<ToastMedia className="text-context-warning-emphasis">
|
|
187
|
+
<TriangleAlertIcon />
|
|
188
|
+
</ToastMedia>
|
|
189
|
+
),
|
|
190
|
+
error: (
|
|
191
|
+
<ToastMedia className="text-context-danger-emphasis">
|
|
192
|
+
<OctagonXIcon />
|
|
193
|
+
</ToastMedia>
|
|
194
|
+
),
|
|
195
|
+
loading: (
|
|
196
|
+
<ToastMedia className="text-context-neutral-emphasis">
|
|
197
|
+
<Loader2Icon className="animate-spin" />
|
|
198
|
+
</ToastMedia>
|
|
199
|
+
),
|
|
200
|
+
...icons,
|
|
201
|
+
}}
|
|
202
|
+
toastOptions={{
|
|
203
|
+
...toastOptions,
|
|
204
|
+
classNames: {
|
|
205
|
+
...toastOptions?.classNames,
|
|
206
|
+
toast: cn('opus-toast', toastOptions?.classNames?.toast),
|
|
207
|
+
icon: cn('opus-toast-icon', toastOptions?.classNames?.icon),
|
|
208
|
+
closeButton: cn('opus-toast-close', toastOptions?.classNames?.closeButton),
|
|
209
|
+
},
|
|
32
210
|
}}
|
|
33
211
|
style={
|
|
34
212
|
{
|
|
@@ -36,6 +214,7 @@ export function Toaster({ ...props }: ToasterProps): React.ReactElement {
|
|
|
36
214
|
'--normal-text': 'var(--popover-foreground)',
|
|
37
215
|
'--normal-border': 'var(--border)',
|
|
38
216
|
'--border-radius': 'var(--radius)',
|
|
217
|
+
...style,
|
|
39
218
|
} as React.CSSProperties
|
|
40
219
|
}
|
|
41
220
|
{...props}
|