@exxatdesignux/ui 0.0.5
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/package.json +72 -0
- package/src/components/ui/avatar.tsx +384 -0
- package/src/components/ui/badge.tsx +49 -0
- package/src/components/ui/banner.tsx +364 -0
- package/src/components/ui/breadcrumb.tsx +120 -0
- package/src/components/ui/button.tsx +66 -0
- package/src/components/ui/calendar.tsx +220 -0
- package/src/components/ui/card.tsx +136 -0
- package/src/components/ui/chart.tsx +378 -0
- package/src/components/ui/checkbox.tsx +160 -0
- package/src/components/ui/coach-mark.tsx +361 -0
- package/src/components/ui/collapsible.tsx +33 -0
- package/src/components/ui/command.tsx +232 -0
- package/src/components/ui/date-picker-field.tsx +186 -0
- package/src/components/ui/dialog.tsx +171 -0
- package/src/components/ui/drag-handle-grip.tsx +10 -0
- package/src/components/ui/drawer.tsx +134 -0
- package/src/components/ui/dropdown-menu.tsx +422 -0
- package/src/components/ui/field.tsx +238 -0
- package/src/components/ui/form.tsx +137 -0
- package/src/components/ui/input-group.tsx +156 -0
- package/src/components/ui/input-mask.tsx +135 -0
- package/src/components/ui/input.tsx +22 -0
- package/src/components/ui/kbd.tsx +55 -0
- package/src/components/ui/label.tsx +25 -0
- package/src/components/ui/payment-card-fields.tsx +65 -0
- package/src/components/ui/popover.tsx +46 -0
- package/src/components/ui/radio-group.tsx +217 -0
- package/src/components/ui/select.tsx +191 -0
- package/src/components/ui/selection-tile-grid.tsx +246 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/sheet.tsx +147 -0
- package/src/components/ui/sidebar.tsx +716 -0
- package/src/components/ui/skeleton.tsx +13 -0
- package/src/components/ui/sonner.tsx +39 -0
- package/src/components/ui/status-badge.tsx +109 -0
- package/src/components/ui/table.tsx +117 -0
- package/src/components/ui/tabs.tsx +90 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/tip.tsx +21 -0
- package/src/components/ui/toggle-group.tsx +89 -0
- package/src/components/ui/toggle-switch.tsx +31 -0
- package/src/components/ui/toggle.tsx +48 -0
- package/src/components/ui/tooltip.tsx +59 -0
- package/src/components/ui/view-segmented-control.tsx +160 -0
- package/src/globals.css +1795 -0
- package/src/hooks/.gitkeep +0 -0
- package/src/hooks/use-app-theme.ts +172 -0
- package/src/hooks/use-coach-mark.ts +342 -0
- package/src/hooks/use-mobile.ts +31 -0
- package/src/hooks/use-mod-key-label.ts +29 -0
- package/src/index.ts +55 -0
- package/src/lib/compose-refs.ts +15 -0
- package/src/lib/date-filter.ts +67 -0
- package/src/lib/utils.ts +6 -0
- package/src/theme/apply-windows-contrast-theme.ts +29 -0
- package/src/theme/windows-contrast-theme.json +147 -0
- package/src/theme.css +1130 -0
- package/src/types/react-payment-inputs.d.ts +20 -0
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui"
|
|
5
|
+
|
|
6
|
+
import { cn } from "../../lib/utils"
|
|
7
|
+
|
|
8
|
+
function DropdownMenu({
|
|
9
|
+
...props
|
|
10
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
|
|
11
|
+
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function DropdownMenuPortal({
|
|
15
|
+
...props
|
|
16
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
|
|
17
|
+
return (
|
|
18
|
+
<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function DropdownMenuTrigger({
|
|
23
|
+
...props
|
|
24
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
|
|
25
|
+
return (
|
|
26
|
+
<DropdownMenuPrimitive.Trigger
|
|
27
|
+
data-slot="dropdown-menu-trigger"
|
|
28
|
+
suppressHydrationWarning
|
|
29
|
+
{...props}
|
|
30
|
+
/>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function DropdownMenuContent({
|
|
35
|
+
className,
|
|
36
|
+
align = "start",
|
|
37
|
+
sideOffset = 4,
|
|
38
|
+
...props
|
|
39
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
|
|
40
|
+
return (
|
|
41
|
+
<DropdownMenuPrimitive.Portal>
|
|
42
|
+
<DropdownMenuPrimitive.Content
|
|
43
|
+
data-slot="dropdown-menu-content"
|
|
44
|
+
sideOffset={sideOffset}
|
|
45
|
+
align={align}
|
|
46
|
+
className={cn("z-50 max-h-(--radix-dropdown-menu-content-available-height) w-(--radix-dropdown-menu-trigger-width) min-w-32 origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:overflow-hidden data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
|
|
47
|
+
{...props}
|
|
48
|
+
/>
|
|
49
|
+
</DropdownMenuPrimitive.Portal>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function DropdownMenuGroup({
|
|
54
|
+
...props
|
|
55
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
|
|
56
|
+
return (
|
|
57
|
+
<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function DropdownMenuItem({
|
|
62
|
+
className,
|
|
63
|
+
inset,
|
|
64
|
+
variant = "default",
|
|
65
|
+
shortcut,
|
|
66
|
+
children,
|
|
67
|
+
asChild,
|
|
68
|
+
...props
|
|
69
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
|
|
70
|
+
inset?: boolean
|
|
71
|
+
variant?: "default" | "destructive"
|
|
72
|
+
/** Visual keyboard-shortcut hint shown on the right of the item (e.g. "⌘E", "F2").
|
|
73
|
+
* Purely cosmetic — to actually bind the key, render a sibling `<Shortcut keys={…} onInvoke={…} />`
|
|
74
|
+
* in a parent that stays mounted (menu items unmount when the menu closes). */
|
|
75
|
+
shortcut?: string
|
|
76
|
+
}) {
|
|
77
|
+
return (
|
|
78
|
+
<DropdownMenuPrimitive.Item
|
|
79
|
+
data-slot="dropdown-menu-item"
|
|
80
|
+
data-inset={inset}
|
|
81
|
+
data-variant={variant}
|
|
82
|
+
asChild={asChild}
|
|
83
|
+
className={cn(
|
|
84
|
+
"group/dropdown-menu-item relative flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:ps-7 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([data-product-logo]):not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive",
|
|
85
|
+
className
|
|
86
|
+
)}
|
|
87
|
+
{...props}
|
|
88
|
+
>
|
|
89
|
+
{asChild ? (
|
|
90
|
+
children
|
|
91
|
+
) : (
|
|
92
|
+
<>
|
|
93
|
+
{children}
|
|
94
|
+
{shortcut ? <DropdownMenuShortcut>{shortcut}</DropdownMenuShortcut> : null}
|
|
95
|
+
</>
|
|
96
|
+
)}
|
|
97
|
+
</DropdownMenuPrimitive.Item>
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Invisible component that binds a keyboard shortcut globally while mounted.
|
|
102
|
+
* Pair with `DropdownMenuItem shortcut="⌘E"` for the visual hint. */
|
|
103
|
+
function Shortcut({
|
|
104
|
+
keys,
|
|
105
|
+
onInvoke,
|
|
106
|
+
disabled,
|
|
107
|
+
}: {
|
|
108
|
+
keys: string
|
|
109
|
+
onInvoke: (e: KeyboardEvent) => void
|
|
110
|
+
disabled?: boolean
|
|
111
|
+
}) {
|
|
112
|
+
useShortcut(keys, onInvoke, disabled)
|
|
113
|
+
return null
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function useShortcut(
|
|
117
|
+
keys: string,
|
|
118
|
+
onInvoke: (e: KeyboardEvent) => void,
|
|
119
|
+
disabled?: boolean,
|
|
120
|
+
) {
|
|
121
|
+
const ref = React.useRef(onInvoke)
|
|
122
|
+
React.useEffect(() => { ref.current = onInvoke }, [onInvoke])
|
|
123
|
+
React.useEffect(() => {
|
|
124
|
+
if (disabled) return
|
|
125
|
+
const parsed = parseShortcut(keys)
|
|
126
|
+
if (!parsed) return
|
|
127
|
+
function handler(e: KeyboardEvent) {
|
|
128
|
+
if (!matchesShortcut(e, parsed!)) return
|
|
129
|
+
const t = e.target as HTMLElement | null
|
|
130
|
+
if (t && (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.isContentEditable)) return
|
|
131
|
+
// Skip when the user is activating an interactive control — Enter/Space on a
|
|
132
|
+
// focused button, link, or role=button is already that control's native action.
|
|
133
|
+
// Without this guard, a page-level `Shortcut keys="Enter"` (e.g. the wizard's
|
|
134
|
+
// "submit on review") races the button's own click: the Edit / Back / Cancel
|
|
135
|
+
// button runs its handler AND the window listener submits the form on the same
|
|
136
|
+
// keystroke — the classic "review auto-closes when I click Edit" bug.
|
|
137
|
+
if (t && (parsed!.key === "enter" || parsed!.key === " ")) {
|
|
138
|
+
const role = t.getAttribute("role")
|
|
139
|
+
if (
|
|
140
|
+
t.tagName === "BUTTON" ||
|
|
141
|
+
t.tagName === "A" ||
|
|
142
|
+
t.tagName === "SELECT" ||
|
|
143
|
+
role === "button" ||
|
|
144
|
+
role === "link" ||
|
|
145
|
+
role === "menuitem" ||
|
|
146
|
+
role === "tab" ||
|
|
147
|
+
role === "option" ||
|
|
148
|
+
role === "checkbox" ||
|
|
149
|
+
role === "radio" ||
|
|
150
|
+
role === "switch" ||
|
|
151
|
+
t.closest('[role="button"], [role="link"], [role="menuitem"], [role="tab"], [role="option"]')
|
|
152
|
+
)
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
// If a Radix dialog/sheet/alert-dialog is open, only fire when the event
|
|
156
|
+
// originates inside it — page-level shortcuts must NOT bleed through
|
|
157
|
+
// an open dialog, but in-dialog `<Shortcut>` bindings (Export, Save)
|
|
158
|
+
// still need to fire.
|
|
159
|
+
const openDialog = document.querySelector('[role="dialog"][data-state="open"], [role="alertdialog"][data-state="open"]') as HTMLElement | null
|
|
160
|
+
if (openDialog && (!t || !openDialog.contains(t))) return
|
|
161
|
+
e.preventDefault()
|
|
162
|
+
e.stopPropagation()
|
|
163
|
+
ref.current(e)
|
|
164
|
+
}
|
|
165
|
+
window.addEventListener("keydown", handler)
|
|
166
|
+
return () => window.removeEventListener("keydown", handler)
|
|
167
|
+
}, [keys, disabled])
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/* --------------------------------------------------------------------------
|
|
171
|
+
* Shortcut parsing + global binding
|
|
172
|
+
* ------------------------------------------------------------------------ */
|
|
173
|
+
|
|
174
|
+
type ParsedShortcut = {
|
|
175
|
+
meta: boolean
|
|
176
|
+
ctrl: boolean
|
|
177
|
+
shift: boolean
|
|
178
|
+
alt: boolean
|
|
179
|
+
key: string // normalized lowercase, or special like "f2", "backspace", "delete", "enter", "escape", "arrowup"…
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function mapKey(raw: string): string {
|
|
183
|
+
const p = raw.toLowerCase()
|
|
184
|
+
if (raw === "⌫") return "backspace"
|
|
185
|
+
if (raw === "⌦") return "delete"
|
|
186
|
+
if (raw === "⏎" || p === "enter" || p === "return") return "enter"
|
|
187
|
+
if (raw === "␣" || p === "space") return " "
|
|
188
|
+
if (p === "esc" || p === "escape") return "escape"
|
|
189
|
+
if (p === "tab") return "tab"
|
|
190
|
+
if (p === "up" || raw === "↑") return "arrowup"
|
|
191
|
+
if (p === "down" || raw === "↓") return "arrowdown"
|
|
192
|
+
if (p === "left" || raw === "←") return "arrowleft"
|
|
193
|
+
if (p === "right" || raw === "→") return "arrowright"
|
|
194
|
+
return p
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function parseShortcut(input: string): ParsedShortcut | null {
|
|
198
|
+
let s = input.trim()
|
|
199
|
+
if (!s) return null
|
|
200
|
+
const out: ParsedShortcut = { meta: false, ctrl: false, shift: false, alt: false, key: "" }
|
|
201
|
+
// Strip leading symbolic modifiers (⌘ ⌃ ⇧ ⌥) which may be glued to the key char.
|
|
202
|
+
while (s.length) {
|
|
203
|
+
const c = s[0]
|
|
204
|
+
if (c === "⌘") { out.meta = true; s = s.slice(1) }
|
|
205
|
+
else if (c === "⌃") { out.ctrl = true; s = s.slice(1) }
|
|
206
|
+
else if (c === "⇧") { out.shift = true; s = s.slice(1) }
|
|
207
|
+
else if (c === "⌥") { out.alt = true; s = s.slice(1) }
|
|
208
|
+
else break
|
|
209
|
+
}
|
|
210
|
+
if (!s) return null
|
|
211
|
+
// Word-style modifiers (Cmd+Shift+D, Alt P) joined by + or whitespace.
|
|
212
|
+
if (/[+\s]/.test(s)) {
|
|
213
|
+
for (const raw of s.split(/[+\s]+/).filter(Boolean)) {
|
|
214
|
+
const p = raw.toLowerCase()
|
|
215
|
+
if (raw === "⌘" || p === "cmd" || p === "meta" || p === "command") out.meta = true
|
|
216
|
+
else if (raw === "⌃" || p === "ctrl" || p === "control") out.ctrl = true
|
|
217
|
+
else if (raw === "⇧" || p === "shift") out.shift = true
|
|
218
|
+
else if (raw === "⌥" || p === "alt" || p === "opt" || p === "option") out.alt = true
|
|
219
|
+
else out.key = mapKey(raw)
|
|
220
|
+
}
|
|
221
|
+
} else {
|
|
222
|
+
out.key = mapKey(s)
|
|
223
|
+
}
|
|
224
|
+
return out.key ? out : null
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function matchesShortcut(e: KeyboardEvent, s: ParsedShortcut): boolean {
|
|
228
|
+
if (e.metaKey !== s.meta) return false
|
|
229
|
+
if (e.ctrlKey !== s.ctrl) return false
|
|
230
|
+
if (e.altKey !== s.alt) return false
|
|
231
|
+
if (e.shiftKey !== s.shift) return false
|
|
232
|
+
return e.key.toLowerCase() === s.key
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
function DropdownMenuCheckboxItem({
|
|
237
|
+
className,
|
|
238
|
+
children,
|
|
239
|
+
checked,
|
|
240
|
+
inset,
|
|
241
|
+
...props
|
|
242
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem> & {
|
|
243
|
+
inset?: boolean
|
|
244
|
+
}) {
|
|
245
|
+
return (
|
|
246
|
+
<DropdownMenuPrimitive.CheckboxItem
|
|
247
|
+
data-slot="dropdown-menu-checkbox-item"
|
|
248
|
+
data-inset={inset}
|
|
249
|
+
className={cn(
|
|
250
|
+
"relative flex cursor-default items-center gap-1.5 rounded-md py-1 pe-8 ps-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:ps-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([data-product-logo]):not([class*='size-'])]:size-4",
|
|
251
|
+
className
|
|
252
|
+
)}
|
|
253
|
+
checked={checked}
|
|
254
|
+
{...props}
|
|
255
|
+
>
|
|
256
|
+
<span
|
|
257
|
+
className="pointer-events-none absolute end-2 flex items-center justify-center"
|
|
258
|
+
data-slot="dropdown-menu-checkbox-item-indicator"
|
|
259
|
+
>
|
|
260
|
+
<DropdownMenuPrimitive.ItemIndicator>
|
|
261
|
+
<i className="fa-light fa-check" aria-hidden="true" />
|
|
262
|
+
</DropdownMenuPrimitive.ItemIndicator>
|
|
263
|
+
</span>
|
|
264
|
+
{children}
|
|
265
|
+
</DropdownMenuPrimitive.CheckboxItem>
|
|
266
|
+
)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function DropdownMenuRadioGroup({
|
|
270
|
+
...props
|
|
271
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
|
|
272
|
+
return (
|
|
273
|
+
<DropdownMenuPrimitive.RadioGroup
|
|
274
|
+
data-slot="dropdown-menu-radio-group"
|
|
275
|
+
{...props}
|
|
276
|
+
/>
|
|
277
|
+
)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function DropdownMenuRadioItem({
|
|
281
|
+
className,
|
|
282
|
+
children,
|
|
283
|
+
inset,
|
|
284
|
+
...props
|
|
285
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem> & {
|
|
286
|
+
inset?: boolean
|
|
287
|
+
}) {
|
|
288
|
+
return (
|
|
289
|
+
<DropdownMenuPrimitive.RadioItem
|
|
290
|
+
data-slot="dropdown-menu-radio-item"
|
|
291
|
+
data-inset={inset}
|
|
292
|
+
className={cn(
|
|
293
|
+
"relative flex cursor-default items-center gap-1.5 rounded-md py-1 pe-8 ps-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:ps-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([data-product-logo]):not([class*='size-'])]:size-4",
|
|
294
|
+
className
|
|
295
|
+
)}
|
|
296
|
+
{...props}
|
|
297
|
+
>
|
|
298
|
+
<span
|
|
299
|
+
className="pointer-events-none absolute end-2 flex items-center justify-center"
|
|
300
|
+
data-slot="dropdown-menu-radio-item-indicator"
|
|
301
|
+
>
|
|
302
|
+
<DropdownMenuPrimitive.ItemIndicator>
|
|
303
|
+
<i className="fa-light fa-check" aria-hidden="true" />
|
|
304
|
+
</DropdownMenuPrimitive.ItemIndicator>
|
|
305
|
+
</span>
|
|
306
|
+
{children}
|
|
307
|
+
</DropdownMenuPrimitive.RadioItem>
|
|
308
|
+
)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function DropdownMenuLabel({
|
|
312
|
+
className,
|
|
313
|
+
inset,
|
|
314
|
+
...props
|
|
315
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
|
|
316
|
+
inset?: boolean
|
|
317
|
+
}) {
|
|
318
|
+
return (
|
|
319
|
+
<DropdownMenuPrimitive.Label
|
|
320
|
+
data-slot="dropdown-menu-label"
|
|
321
|
+
data-inset={inset}
|
|
322
|
+
className={cn(
|
|
323
|
+
"px-1.5 py-1 text-xs font-medium text-muted-foreground data-inset:ps-7",
|
|
324
|
+
className
|
|
325
|
+
)}
|
|
326
|
+
{...props}
|
|
327
|
+
/>
|
|
328
|
+
)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function DropdownMenuSeparator({
|
|
332
|
+
className,
|
|
333
|
+
...props
|
|
334
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
|
|
335
|
+
return (
|
|
336
|
+
<DropdownMenuPrimitive.Separator
|
|
337
|
+
data-slot="dropdown-menu-separator"
|
|
338
|
+
className={cn("-mx-1 my-1 h-px bg-border", className)}
|
|
339
|
+
{...props}
|
|
340
|
+
/>
|
|
341
|
+
)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function DropdownMenuShortcut({
|
|
345
|
+
className,
|
|
346
|
+
...props
|
|
347
|
+
}: React.ComponentProps<"span">) {
|
|
348
|
+
return (
|
|
349
|
+
<span
|
|
350
|
+
data-slot="dropdown-menu-shortcut"
|
|
351
|
+
className={cn(
|
|
352
|
+
"ms-auto text-xs tracking-widest text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground",
|
|
353
|
+
className
|
|
354
|
+
)}
|
|
355
|
+
{...props}
|
|
356
|
+
/>
|
|
357
|
+
)
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function DropdownMenuSub({
|
|
361
|
+
...props
|
|
362
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
|
|
363
|
+
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function DropdownMenuSubTrigger({
|
|
367
|
+
className,
|
|
368
|
+
inset,
|
|
369
|
+
children,
|
|
370
|
+
...props
|
|
371
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
|
|
372
|
+
inset?: boolean
|
|
373
|
+
}) {
|
|
374
|
+
return (
|
|
375
|
+
<DropdownMenuPrimitive.SubTrigger
|
|
376
|
+
data-slot="dropdown-menu-sub-trigger"
|
|
377
|
+
suppressHydrationWarning
|
|
378
|
+
data-inset={inset}
|
|
379
|
+
className={cn(
|
|
380
|
+
"flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:ps-7 data-open:bg-accent data-open:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([data-product-logo]):not([class*='size-'])]:size-4",
|
|
381
|
+
className
|
|
382
|
+
)}
|
|
383
|
+
{...props}
|
|
384
|
+
>
|
|
385
|
+
{children}
|
|
386
|
+
<i className="fa-light fa-chevron-right rtl:rotate-180 ms-auto" aria-hidden="true" />
|
|
387
|
+
</DropdownMenuPrimitive.SubTrigger>
|
|
388
|
+
)
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function DropdownMenuSubContent({
|
|
392
|
+
className,
|
|
393
|
+
...props
|
|
394
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
|
|
395
|
+
return (
|
|
396
|
+
<DropdownMenuPrimitive.SubContent
|
|
397
|
+
data-slot="dropdown-menu-sub-content"
|
|
398
|
+
className={cn("z-50 min-w-[96px] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-lg bg-popover p-1 text-popover-foreground shadow-lg ring-1 ring-foreground/10 duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
|
|
399
|
+
{...props}
|
|
400
|
+
/>
|
|
401
|
+
)
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
export {
|
|
405
|
+
DropdownMenu,
|
|
406
|
+
DropdownMenuPortal,
|
|
407
|
+
DropdownMenuTrigger,
|
|
408
|
+
DropdownMenuContent,
|
|
409
|
+
DropdownMenuGroup,
|
|
410
|
+
DropdownMenuLabel,
|
|
411
|
+
DropdownMenuItem,
|
|
412
|
+
DropdownMenuCheckboxItem,
|
|
413
|
+
DropdownMenuRadioGroup,
|
|
414
|
+
DropdownMenuRadioItem,
|
|
415
|
+
DropdownMenuSeparator,
|
|
416
|
+
DropdownMenuShortcut,
|
|
417
|
+
DropdownMenuSub,
|
|
418
|
+
DropdownMenuSubTrigger,
|
|
419
|
+
DropdownMenuSubContent,
|
|
420
|
+
Shortcut,
|
|
421
|
+
useShortcut,
|
|
422
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useMemo } from "react"
|
|
4
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
5
|
+
|
|
6
|
+
import { cn } from "../../lib/utils"
|
|
7
|
+
import { Label } from "./label"
|
|
8
|
+
import { Separator } from "./separator"
|
|
9
|
+
|
|
10
|
+
function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
|
|
11
|
+
return (
|
|
12
|
+
<fieldset
|
|
13
|
+
data-slot="field-set"
|
|
14
|
+
className={cn(
|
|
15
|
+
"flex flex-col gap-4 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
|
|
16
|
+
className
|
|
17
|
+
)}
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function FieldLegend({
|
|
24
|
+
className,
|
|
25
|
+
variant = "legend",
|
|
26
|
+
...props
|
|
27
|
+
}: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) {
|
|
28
|
+
return (
|
|
29
|
+
<legend
|
|
30
|
+
data-slot="field-legend"
|
|
31
|
+
data-variant={variant}
|
|
32
|
+
className={cn(
|
|
33
|
+
"mb-1.5 font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base",
|
|
34
|
+
className
|
|
35
|
+
)}
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
42
|
+
return (
|
|
43
|
+
<div
|
|
44
|
+
data-slot="field-group"
|
|
45
|
+
className={cn(
|
|
46
|
+
"group/field-group @container/field-group flex w-full flex-col gap-5 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4",
|
|
47
|
+
className
|
|
48
|
+
)}
|
|
49
|
+
{...props}
|
|
50
|
+
/>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const fieldVariants = cva(
|
|
55
|
+
"group/field flex w-full gap-2 data-[invalid=true]:text-destructive",
|
|
56
|
+
{
|
|
57
|
+
variants: {
|
|
58
|
+
orientation: {
|
|
59
|
+
vertical: "flex-col *:w-full [&>.sr-only]:w-auto",
|
|
60
|
+
horizontal:
|
|
61
|
+
"flex-row items-center has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
|
|
62
|
+
responsive:
|
|
63
|
+
"flex-col *:w-full @md/field-group:flex-row @md/field-group:items-center @md/field-group:*:w-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:*:data-[slot=field-label]:flex-auto [&>.sr-only]:w-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
defaultVariants: {
|
|
67
|
+
orientation: "vertical",
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
function Field({
|
|
73
|
+
className,
|
|
74
|
+
orientation = "vertical",
|
|
75
|
+
...props
|
|
76
|
+
}: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>) {
|
|
77
|
+
return (
|
|
78
|
+
<div
|
|
79
|
+
role="group"
|
|
80
|
+
data-slot="field"
|
|
81
|
+
data-orientation={orientation}
|
|
82
|
+
className={cn(fieldVariants({ orientation }), className)}
|
|
83
|
+
{...props}
|
|
84
|
+
/>
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
89
|
+
return (
|
|
90
|
+
<div
|
|
91
|
+
data-slot="field-content"
|
|
92
|
+
className={cn(
|
|
93
|
+
"group/field-content flex flex-1 flex-col gap-0.5 leading-snug",
|
|
94
|
+
className
|
|
95
|
+
)}
|
|
96
|
+
{...props}
|
|
97
|
+
/>
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function FieldLabel({
|
|
102
|
+
className,
|
|
103
|
+
...props
|
|
104
|
+
}: React.ComponentProps<typeof Label>) {
|
|
105
|
+
return (
|
|
106
|
+
<Label
|
|
107
|
+
data-slot="field-label"
|
|
108
|
+
className={cn(
|
|
109
|
+
"group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50 has-data-checked:border-primary/30 has-data-checked:bg-primary/5 has-[>[data-slot=field]]:rounded-lg has-[>[data-slot=field]]:border *:data-[slot=field]:p-2.5 dark:has-data-checked:border-primary/20 dark:has-data-checked:bg-primary/10",
|
|
110
|
+
"has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col",
|
|
111
|
+
className
|
|
112
|
+
)}
|
|
113
|
+
{...props}
|
|
114
|
+
/>
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
119
|
+
return (
|
|
120
|
+
<div
|
|
121
|
+
data-slot="field-label"
|
|
122
|
+
className={cn(
|
|
123
|
+
"flex w-fit items-center gap-2 text-sm leading-snug font-medium group-data-[disabled=true]/field:opacity-50",
|
|
124
|
+
className
|
|
125
|
+
)}
|
|
126
|
+
{...props}
|
|
127
|
+
/>
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
|
|
132
|
+
return (
|
|
133
|
+
<p
|
|
134
|
+
data-slot="field-description"
|
|
135
|
+
className={cn(
|
|
136
|
+
"text-start text-sm leading-normal font-normal text-muted-foreground group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5",
|
|
137
|
+
"last:mt-0 nth-last-2:-mt-1",
|
|
138
|
+
"[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
|
|
139
|
+
className
|
|
140
|
+
)}
|
|
141
|
+
{...props}
|
|
142
|
+
/>
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function FieldSeparator({
|
|
147
|
+
children,
|
|
148
|
+
className,
|
|
149
|
+
...props
|
|
150
|
+
}: React.ComponentProps<"div"> & {
|
|
151
|
+
children?: React.ReactNode
|
|
152
|
+
}) {
|
|
153
|
+
return (
|
|
154
|
+
<div
|
|
155
|
+
data-slot="field-separator"
|
|
156
|
+
data-content={!!children}
|
|
157
|
+
className={cn(
|
|
158
|
+
"relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2",
|
|
159
|
+
className
|
|
160
|
+
)}
|
|
161
|
+
{...props}
|
|
162
|
+
>
|
|
163
|
+
<Separator className="absolute inset-0 top-1/2" />
|
|
164
|
+
{children && (
|
|
165
|
+
<span
|
|
166
|
+
className="relative mx-auto block w-fit bg-background px-2 text-muted-foreground"
|
|
167
|
+
data-slot="field-separator-content"
|
|
168
|
+
>
|
|
169
|
+
{children}
|
|
170
|
+
</span>
|
|
171
|
+
)}
|
|
172
|
+
</div>
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function FieldError({
|
|
177
|
+
className,
|
|
178
|
+
children,
|
|
179
|
+
errors,
|
|
180
|
+
...props
|
|
181
|
+
}: React.ComponentProps<"div"> & {
|
|
182
|
+
errors?: Array<{ message?: string } | undefined>
|
|
183
|
+
}) {
|
|
184
|
+
const content = useMemo(() => {
|
|
185
|
+
if (children) {
|
|
186
|
+
return children
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (!errors?.length) {
|
|
190
|
+
return null
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const uniqueErrors = [
|
|
194
|
+
...new Map(errors.map((error) => [error?.message, error])).values(),
|
|
195
|
+
]
|
|
196
|
+
|
|
197
|
+
if (uniqueErrors?.length == 1) {
|
|
198
|
+
return uniqueErrors[0]?.message
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return (
|
|
202
|
+
<ul className="ms-4 flex list-disc flex-col gap-1">
|
|
203
|
+
{uniqueErrors.map(
|
|
204
|
+
(error, index) =>
|
|
205
|
+
error?.message && <li key={index}>{error.message}</li>
|
|
206
|
+
)}
|
|
207
|
+
</ul>
|
|
208
|
+
)
|
|
209
|
+
}, [children, errors])
|
|
210
|
+
|
|
211
|
+
if (!content) {
|
|
212
|
+
return null
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return (
|
|
216
|
+
<div
|
|
217
|
+
role="alert"
|
|
218
|
+
data-slot="field-error"
|
|
219
|
+
className={cn("text-sm font-normal text-destructive", className)}
|
|
220
|
+
{...props}
|
|
221
|
+
>
|
|
222
|
+
{content}
|
|
223
|
+
</div>
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export {
|
|
228
|
+
Field,
|
|
229
|
+
FieldLabel,
|
|
230
|
+
FieldDescription,
|
|
231
|
+
FieldError,
|
|
232
|
+
FieldGroup,
|
|
233
|
+
FieldLegend,
|
|
234
|
+
FieldSeparator,
|
|
235
|
+
FieldSet,
|
|
236
|
+
FieldContent,
|
|
237
|
+
FieldTitle,
|
|
238
|
+
}
|