@carefully-built/ui 0.1.15
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/README.md +148 -0
- package/dist/index.d.mts +1351 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +3489 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +70 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,3489 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { createElement, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
5
|
+
import { Avatar as Avatar$1, Dialog as Dialog$1, DropdownMenu as DropdownMenu$1, Label as Label$1, Popover as Popover$1, Select as Select$1, Slot, Switch as Switch$1, Tabs as Tabs$1, Tooltip as Tooltip$1 } from "radix-ui";
|
|
6
|
+
import { clsx } from "clsx";
|
|
7
|
+
import { twMerge } from "tailwind-merge";
|
|
8
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
import { cva } from "class-variance-authority";
|
|
10
|
+
import { ArrowDown, ArrowUp, Check, ChevronDown, ChevronLeft, ChevronRight, ChevronUp, ChevronsLeft, ChevronsRight, ChevronsUpDown, CircleHelp, Command, CornerDownLeft, Eye, FileText, Filter, Inbox, Pencil, Search, SearchX, Trash2, Upload, X } from "lucide-react";
|
|
11
|
+
import { DayPicker, getDefaultClassNames } from "react-day-picker";
|
|
12
|
+
import { Drawer as Drawer$1 } from "vaul";
|
|
13
|
+
import { createPortal } from "react-dom";
|
|
14
|
+
|
|
15
|
+
//#region src/utils/cn.ts
|
|
16
|
+
function cn(...inputs) {
|
|
17
|
+
return twMerge(clsx(inputs));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/primitives/avatar.tsx
|
|
22
|
+
function Avatar({ className, size = "default", ...props }) {
|
|
23
|
+
return /* @__PURE__ */ jsx(Avatar$1.Root, {
|
|
24
|
+
"data-slot": "avatar",
|
|
25
|
+
"data-size": size,
|
|
26
|
+
className: cn("size-8 rounded-full after:rounded-full data-[size=lg]:size-10 data-[size=sm]:size-6 after:border-border group/avatar relative flex shrink-0 select-none after:absolute after:inset-0 after:border after:mix-blend-darken dark:after:mix-blend-lighten", className),
|
|
27
|
+
...props
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function AvatarImage({ className, ...props }) {
|
|
31
|
+
return /* @__PURE__ */ jsx(Avatar$1.Image, {
|
|
32
|
+
"data-slot": "avatar-image",
|
|
33
|
+
className: cn("rounded-full aspect-square size-full object-cover", className),
|
|
34
|
+
...props
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function AvatarFallback({ className, ...props }) {
|
|
38
|
+
return /* @__PURE__ */ jsx(Avatar$1.Fallback, {
|
|
39
|
+
"data-slot": "avatar-fallback",
|
|
40
|
+
className: cn("bg-muted text-muted-foreground rounded-full flex size-full items-center justify-center text-sm group-data-[size=sm]/avatar:text-xs", className),
|
|
41
|
+
...props
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region src/primitives/button.tsx
|
|
47
|
+
const buttonVariants = cva("group/button inline-flex shrink-0 cursor-pointer items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", {
|
|
48
|
+
variants: {
|
|
49
|
+
variant: {
|
|
50
|
+
default: "bg-primary text-primary-foreground hover:brightness-90 [a]:hover:bg-primary/80",
|
|
51
|
+
outline: "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
|
|
52
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
|
|
53
|
+
ghost: "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
|
|
54
|
+
destructive: "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
|
|
55
|
+
link: "text-primary underline-offset-4 hover:underline"
|
|
56
|
+
},
|
|
57
|
+
size: {
|
|
58
|
+
default: "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
|
59
|
+
xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
|
60
|
+
sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
|
|
61
|
+
lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
|
62
|
+
icon: "size-8",
|
|
63
|
+
"icon-xs": "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
|
|
64
|
+
"icon-sm": "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
|
|
65
|
+
"icon-lg": "size-9"
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
defaultVariants: {
|
|
69
|
+
variant: "default",
|
|
70
|
+
size: "default"
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
function Button({ className, variant = "default", size = "default", asChild = false, ...props }) {
|
|
74
|
+
return /* @__PURE__ */ jsx(asChild ? Slot.Root : "button", {
|
|
75
|
+
"data-slot": "button",
|
|
76
|
+
"data-variant": variant,
|
|
77
|
+
"data-size": size,
|
|
78
|
+
className: cn(buttonVariants({
|
|
79
|
+
variant,
|
|
80
|
+
size,
|
|
81
|
+
className
|
|
82
|
+
})),
|
|
83
|
+
...props
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
//#endregion
|
|
88
|
+
//#region src/primitives/calendar.tsx
|
|
89
|
+
function Calendar({ className, classNames, showOutsideDays = true, ...props }) {
|
|
90
|
+
const defaultClassNames = getDefaultClassNames();
|
|
91
|
+
return /* @__PURE__ */ jsx(DayPicker, {
|
|
92
|
+
showOutsideDays,
|
|
93
|
+
className: cn("p-3", className),
|
|
94
|
+
classNames: {
|
|
95
|
+
root: cn("w-fit", defaultClassNames.root),
|
|
96
|
+
months: "flex flex-col gap-4",
|
|
97
|
+
month: "flex flex-col gap-4",
|
|
98
|
+
month_caption: "relative flex items-center justify-center pt-1",
|
|
99
|
+
caption_label: "text-sm font-medium",
|
|
100
|
+
nav: "flex items-center gap-1",
|
|
101
|
+
button_previous: cn(buttonVariants({
|
|
102
|
+
variant: "ghost",
|
|
103
|
+
size: "icon-sm"
|
|
104
|
+
}), "absolute left-1 size-7 bg-transparent p-0 opacity-70 hover:opacity-100"),
|
|
105
|
+
button_next: cn(buttonVariants({
|
|
106
|
+
variant: "ghost",
|
|
107
|
+
size: "icon-sm"
|
|
108
|
+
}), "absolute right-1 size-7 bg-transparent p-0 opacity-70 hover:opacity-100"),
|
|
109
|
+
month_grid: "w-full border-collapse space-y-1",
|
|
110
|
+
weekdays: "flex",
|
|
111
|
+
weekday: "w-9 text-[0.78rem] font-medium text-muted-foreground",
|
|
112
|
+
week: "mt-2 flex w-full",
|
|
113
|
+
day: "size-9 p-0 text-sm",
|
|
114
|
+
day_button: cn(buttonVariants({
|
|
115
|
+
variant: "ghost",
|
|
116
|
+
size: "icon-sm"
|
|
117
|
+
}), "size-9 p-0 font-normal aria-selected:opacity-100"),
|
|
118
|
+
selected: "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
|
|
119
|
+
today: "bg-muted text-foreground",
|
|
120
|
+
outside: "text-muted-foreground opacity-50 aria-selected:bg-primary/10 aria-selected:text-muted-foreground",
|
|
121
|
+
disabled: "text-muted-foreground opacity-50",
|
|
122
|
+
hidden: "invisible",
|
|
123
|
+
...classNames
|
|
124
|
+
},
|
|
125
|
+
components: { Chevron: ({ orientation, className: iconClassName, ...iconProps }) => orientation === "left" ? /* @__PURE__ */ jsx(ChevronLeft, {
|
|
126
|
+
className: cn("size-4", iconClassName),
|
|
127
|
+
...iconProps
|
|
128
|
+
}) : /* @__PURE__ */ jsx(ChevronRight, {
|
|
129
|
+
className: cn("size-4", iconClassName),
|
|
130
|
+
...iconProps
|
|
131
|
+
}) },
|
|
132
|
+
...props
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
//#endregion
|
|
137
|
+
//#region src/primitives/card.tsx
|
|
138
|
+
function Card({ className, size = "default", ...props }) {
|
|
139
|
+
return /* @__PURE__ */ jsx("div", {
|
|
140
|
+
"data-slot": "card",
|
|
141
|
+
"data-size": size,
|
|
142
|
+
className: cn("ring-foreground/10 bg-card text-card-foreground gap-4 overflow-hidden rounded-xl py-4 text-sm ring-1 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl group/card flex flex-col", className),
|
|
143
|
+
...props
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
function CardHeader({ className, ...props }) {
|
|
147
|
+
return /* @__PURE__ */ jsx("div", {
|
|
148
|
+
"data-slot": "card-header",
|
|
149
|
+
className: cn("gap-1 rounded-t-xl px-4 group-data-[size=sm]/card:px-3 [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3 group/card-header @container/card-header grid auto-rows-min items-start has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto]", className),
|
|
150
|
+
...props
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
function CardTitle({ className, ...props }) {
|
|
154
|
+
return /* @__PURE__ */ jsx("div", {
|
|
155
|
+
"data-slot": "card-title",
|
|
156
|
+
className: cn("text-base leading-snug font-medium group-data-[size=sm]/card:text-sm", className),
|
|
157
|
+
...props
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
function CardDescription({ className, ...props }) {
|
|
161
|
+
return /* @__PURE__ */ jsx("div", {
|
|
162
|
+
"data-slot": "card-description",
|
|
163
|
+
className: cn("text-muted-foreground text-sm", className),
|
|
164
|
+
...props
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
function CardContent({ className, ...props }) {
|
|
168
|
+
return /* @__PURE__ */ jsx("div", {
|
|
169
|
+
"data-slot": "card-content",
|
|
170
|
+
className: cn("px-4 group-data-[size=sm]/card:px-3", className),
|
|
171
|
+
...props
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
function CardFooter({ className, ...props }) {
|
|
175
|
+
return /* @__PURE__ */ jsx("div", {
|
|
176
|
+
"data-slot": "card-footer",
|
|
177
|
+
className: cn("bg-muted/50 rounded-b-xl border-t p-4 group-data-[size=sm]/card:p-3 flex items-center", className),
|
|
178
|
+
...props
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
//#endregion
|
|
183
|
+
//#region src/primitives/chip-utils.ts
|
|
184
|
+
const CHIP_CLASS_NAMES = {
|
|
185
|
+
default: "inline-flex w-fit max-w-full flex-none items-center gap-1 rounded-md px-1.5 py-[2px] text-xs font-medium leading-4",
|
|
186
|
+
compact: "inline-flex w-fit max-w-full flex-none items-center gap-1 rounded-[4px] px-1.5 py-px text-[10px] font-medium leading-3"
|
|
187
|
+
};
|
|
188
|
+
function getChipClassName(size = "default") {
|
|
189
|
+
return CHIP_CLASS_NAMES[size];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
//#endregion
|
|
193
|
+
//#region src/primitives/chip.tsx
|
|
194
|
+
function Chip({ children, className, leading, size = "default", trailing, ...props }) {
|
|
195
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
196
|
+
...props,
|
|
197
|
+
className: cn(getChipClassName(size), className),
|
|
198
|
+
children: [
|
|
199
|
+
leading ? /* @__PURE__ */ jsx("span", {
|
|
200
|
+
className: "shrink-0",
|
|
201
|
+
children: leading
|
|
202
|
+
}) : null,
|
|
203
|
+
/* @__PURE__ */ jsx("span", {
|
|
204
|
+
className: "truncate",
|
|
205
|
+
children
|
|
206
|
+
}),
|
|
207
|
+
trailing ? /* @__PURE__ */ jsx("span", {
|
|
208
|
+
className: "shrink-0",
|
|
209
|
+
children: trailing
|
|
210
|
+
}) : null
|
|
211
|
+
]
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
function ChipButton({ children, className, leading, selected = false, size = "default", trailing, ...props }) {
|
|
215
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
216
|
+
type: "button",
|
|
217
|
+
"aria-pressed": selected,
|
|
218
|
+
...props,
|
|
219
|
+
className: cn(getChipClassName(size), "border text-left transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", selected ? "border-[#713dff] bg-[#f6f1ff] text-[#1f1f23] shadow-[0_0_0_1px_rgba(113,61,255,0.08)] hover:bg-[#f6f1ff]" : "border-[#e7e8eb] bg-white text-[#5f6368] hover:bg-[#f7f7f9]", className),
|
|
220
|
+
children: [
|
|
221
|
+
leading ? /* @__PURE__ */ jsx("span", {
|
|
222
|
+
className: "shrink-0",
|
|
223
|
+
children: leading
|
|
224
|
+
}) : null,
|
|
225
|
+
/* @__PURE__ */ jsx("span", {
|
|
226
|
+
className: "truncate",
|
|
227
|
+
children
|
|
228
|
+
}),
|
|
229
|
+
trailing ? /* @__PURE__ */ jsx("span", {
|
|
230
|
+
className: "shrink-0",
|
|
231
|
+
children: trailing
|
|
232
|
+
}) : null
|
|
233
|
+
]
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
//#endregion
|
|
238
|
+
//#region src/primitives/dialog.tsx
|
|
239
|
+
function Dialog({ ...props }) {
|
|
240
|
+
return /* @__PURE__ */ jsx(Dialog$1.Root, {
|
|
241
|
+
"data-slot": "dialog",
|
|
242
|
+
...props
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
function DialogTrigger({ ...props }) {
|
|
246
|
+
return /* @__PURE__ */ jsx(Dialog$1.Trigger, {
|
|
247
|
+
"data-slot": "dialog-trigger",
|
|
248
|
+
...props
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
function DialogPortal({ ...props }) {
|
|
252
|
+
return /* @__PURE__ */ jsx(Dialog$1.Portal, {
|
|
253
|
+
"data-slot": "dialog-portal",
|
|
254
|
+
...props
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
function DialogOverlay({ className, ...props }) {
|
|
258
|
+
return /* @__PURE__ */ jsx(Dialog$1.Overlay, {
|
|
259
|
+
"data-slot": "dialog-overlay",
|
|
260
|
+
className: cn("data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 isolate z-50", className),
|
|
261
|
+
...props
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
function DialogContent({ className, children, showCloseButton = true, ...props }) {
|
|
265
|
+
return /* @__PURE__ */ jsxs(DialogPortal, { children: [/* @__PURE__ */ jsx(DialogOverlay, {}), /* @__PURE__ */ jsxs(Dialog$1.Content, {
|
|
266
|
+
"data-slot": "dialog-content",
|
|
267
|
+
className: cn("bg-background data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 ring-foreground/10 grid max-w-[calc(100%-2rem)] gap-4 rounded-xl p-4 text-sm ring-1 duration-100 sm:max-w-sm fixed top-1/2 left-1/2 z-50 w-full -translate-x-1/2 -translate-y-1/2 outline-none", className),
|
|
268
|
+
...props,
|
|
269
|
+
children: [children, showCloseButton && /* @__PURE__ */ jsx(Dialog$1.Close, {
|
|
270
|
+
"data-slot": "dialog-close",
|
|
271
|
+
asChild: true,
|
|
272
|
+
children: /* @__PURE__ */ jsxs(Button, {
|
|
273
|
+
variant: "ghost",
|
|
274
|
+
className: "absolute top-2 right-2",
|
|
275
|
+
size: "icon-sm",
|
|
276
|
+
children: [/* @__PURE__ */ jsx(X, {}), /* @__PURE__ */ jsx("span", {
|
|
277
|
+
className: "sr-only",
|
|
278
|
+
children: "Close"
|
|
279
|
+
})]
|
|
280
|
+
})
|
|
281
|
+
})]
|
|
282
|
+
})] });
|
|
283
|
+
}
|
|
284
|
+
function DialogHeader({ className, ...props }) {
|
|
285
|
+
return /* @__PURE__ */ jsx("div", {
|
|
286
|
+
"data-slot": "dialog-header",
|
|
287
|
+
className: cn("gap-2 flex flex-col", className),
|
|
288
|
+
...props
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
function DialogFooter({ className, showCloseButton = false, children, ...props }) {
|
|
292
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
293
|
+
"data-slot": "dialog-footer",
|
|
294
|
+
className: cn("bg-muted/50 -mx-4 -mb-4 rounded-b-xl border-t p-4 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className),
|
|
295
|
+
...props,
|
|
296
|
+
children: [children, showCloseButton && /* @__PURE__ */ jsx(Dialog$1.Close, {
|
|
297
|
+
asChild: true,
|
|
298
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
299
|
+
variant: "outline",
|
|
300
|
+
children: "Close"
|
|
301
|
+
})
|
|
302
|
+
})]
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
function DialogTitle({ className, ...props }) {
|
|
306
|
+
return /* @__PURE__ */ jsx(Dialog$1.Title, {
|
|
307
|
+
"data-slot": "dialog-title",
|
|
308
|
+
className: cn("text-base leading-none font-medium", className),
|
|
309
|
+
...props
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
function DialogDescription({ className, ...props }) {
|
|
313
|
+
return /* @__PURE__ */ jsx(Dialog$1.Description, {
|
|
314
|
+
"data-slot": "dialog-description",
|
|
315
|
+
className: cn("text-muted-foreground *:[a]:hover:text-foreground text-sm *:[a]:underline *:[a]:underline-offset-3", className),
|
|
316
|
+
...props
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
//#endregion
|
|
321
|
+
//#region src/primitives/dropdown-menu.tsx
|
|
322
|
+
function DropdownMenu({ modal = false, ...props }) {
|
|
323
|
+
return /* @__PURE__ */ jsx(DropdownMenu$1.Root, {
|
|
324
|
+
"data-slot": "dropdown-menu",
|
|
325
|
+
modal,
|
|
326
|
+
...props
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
function DropdownMenuTrigger({ ...props }) {
|
|
330
|
+
return /* @__PURE__ */ jsx(DropdownMenu$1.Trigger, {
|
|
331
|
+
"data-slot": "dropdown-menu-trigger",
|
|
332
|
+
...props
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
function DropdownMenuContent({ className, align = "start", sideOffset = 4, ...props }) {
|
|
336
|
+
return /* @__PURE__ */ jsx(DropdownMenu$1.Portal, { children: /* @__PURE__ */ jsx(DropdownMenu$1.Content, {
|
|
337
|
+
"data-slot": "dropdown-menu-content",
|
|
338
|
+
sideOffset,
|
|
339
|
+
align,
|
|
340
|
+
className: cn("data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 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 ring-foreground/10 bg-popover text-popover-foreground min-w-32 rounded-lg p-1 shadow-md ring-1 duration-100 z-50 max-h-(--radix-dropdown-menu-content-available-height) w-(--radix-dropdown-menu-trigger-width) origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto data-[state=closed]:overflow-hidden", className),
|
|
341
|
+
...props
|
|
342
|
+
}) });
|
|
343
|
+
}
|
|
344
|
+
function DropdownMenuItem({ className, inset, variant = "default", ...props }) {
|
|
345
|
+
return /* @__PURE__ */ jsx(DropdownMenu$1.Item, {
|
|
346
|
+
"data-slot": "dropdown-menu-item",
|
|
347
|
+
"data-inset": inset,
|
|
348
|
+
"data-variant": variant,
|
|
349
|
+
className: cn("focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive not-data-[variant=destructive]:focus:**:text-accent-foreground gap-1.5 rounded-md px-1.5 py-1 text-sm data-inset:pl-7 [&_svg:not([class*='size-'])]:size-4 group/dropdown-menu-item relative flex cursor-pointer items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0", className),
|
|
350
|
+
...props
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
function DropdownMenuCheckboxItem({ className, children, checked, inset, ...props }) {
|
|
354
|
+
return /* @__PURE__ */ jsxs(DropdownMenu$1.CheckboxItem, {
|
|
355
|
+
"data-slot": "dropdown-menu-checkbox-item",
|
|
356
|
+
"data-inset": inset,
|
|
357
|
+
className: cn("focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm data-inset:pl-7 [&_svg:not([class*='size-'])]:size-4 relative flex cursor-pointer items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0", className),
|
|
358
|
+
checked,
|
|
359
|
+
...props,
|
|
360
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
361
|
+
className: "absolute right-2 flex items-center justify-center pointer-events-none",
|
|
362
|
+
"data-slot": "dropdown-menu-checkbox-item-indicator",
|
|
363
|
+
children: /* @__PURE__ */ jsx(DropdownMenu$1.ItemIndicator, { children: /* @__PURE__ */ jsx(Check, { className: "size-4" }) })
|
|
364
|
+
}), children]
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
function DropdownMenuLabel({ className, inset, ...props }) {
|
|
368
|
+
return /* @__PURE__ */ jsx(DropdownMenu$1.Label, {
|
|
369
|
+
"data-slot": "dropdown-menu-label",
|
|
370
|
+
"data-inset": inset,
|
|
371
|
+
className: cn("text-muted-foreground px-1.5 py-1 text-xs font-medium data-inset:pl-7", className),
|
|
372
|
+
...props
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
function DropdownMenuSeparator({ className, ...props }) {
|
|
376
|
+
return /* @__PURE__ */ jsx(DropdownMenu$1.Separator, {
|
|
377
|
+
"data-slot": "dropdown-menu-separator",
|
|
378
|
+
className: cn("bg-border -mx-1 my-1 h-px", className),
|
|
379
|
+
...props
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
//#endregion
|
|
384
|
+
//#region src/primitives/drawer.tsx
|
|
385
|
+
function Drawer({ ...props }) {
|
|
386
|
+
return /* @__PURE__ */ jsx(Drawer$1.Root, {
|
|
387
|
+
"data-slot": "drawer",
|
|
388
|
+
...props
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
function DrawerPortal({ ...props }) {
|
|
392
|
+
return /* @__PURE__ */ jsx(Drawer$1.Portal, {
|
|
393
|
+
"data-slot": "drawer-portal",
|
|
394
|
+
...props
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
function DrawerOverlay({ className, ...props }) {
|
|
398
|
+
return /* @__PURE__ */ jsx(Drawer$1.Overlay, {
|
|
399
|
+
"data-slot": "drawer-overlay",
|
|
400
|
+
className: cn("data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 fixed inset-0 z-50 bg-black/10 backdrop-blur-xs", className),
|
|
401
|
+
...props
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
function DrawerContent({ className, children, ...props }) {
|
|
405
|
+
return /* @__PURE__ */ jsxs(DrawerPortal, {
|
|
406
|
+
"data-slot": "drawer-portal",
|
|
407
|
+
children: [/* @__PURE__ */ jsx(DrawerOverlay, {}), /* @__PURE__ */ jsxs(Drawer$1.Content, {
|
|
408
|
+
"data-slot": "drawer-content",
|
|
409
|
+
className: cn("bg-background group/drawer-content fixed z-50 flex h-auto flex-col overflow-visible text-sm data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-xl data-[vaul-drawer-direction=bottom]:border-t data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:rounded-r-xl data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:rounded-l-xl data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-xl data-[vaul-drawer-direction=top]:border-b data-[vaul-drawer-direction=left]:sm:max-w-sm data-[vaul-drawer-direction=right]:sm:max-w-sm", className),
|
|
410
|
+
...props,
|
|
411
|
+
children: [/* @__PURE__ */ jsx("div", { className: "bg-muted mx-auto mt-4 hidden h-1 w-[100px] shrink-0 rounded-full group-data-[vaul-drawer-direction=bottom]/drawer-content:block" }), children]
|
|
412
|
+
})]
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
function DrawerHeader({ className, ...props }) {
|
|
416
|
+
return /* @__PURE__ */ jsx("div", {
|
|
417
|
+
"data-slot": "drawer-header",
|
|
418
|
+
className: cn("flex flex-col gap-0.5 p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:gap-0.5 md:text-left", className),
|
|
419
|
+
...props
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
function DrawerTitle({ className, ...props }) {
|
|
423
|
+
return /* @__PURE__ */ jsx(Drawer$1.Title, {
|
|
424
|
+
"data-slot": "drawer-title",
|
|
425
|
+
className: cn("text-foreground text-base font-medium", className),
|
|
426
|
+
...props
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
function DrawerDescription({ className, ...props }) {
|
|
430
|
+
return /* @__PURE__ */ jsx(Drawer$1.Description, {
|
|
431
|
+
"data-slot": "drawer-description",
|
|
432
|
+
className: cn("text-muted-foreground text-sm", className),
|
|
433
|
+
...props
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
//#endregion
|
|
438
|
+
//#region src/utils/date-display.ts
|
|
439
|
+
const DEFAULT_DATE_DISPLAY_LABELS = {
|
|
440
|
+
today: "Today",
|
|
441
|
+
yesterday: "Yesterday",
|
|
442
|
+
daysAgo: (dayCount) => `${String(dayCount)} days ago`
|
|
443
|
+
};
|
|
444
|
+
function getDate(value) {
|
|
445
|
+
return value instanceof Date ? value : new Date(value);
|
|
446
|
+
}
|
|
447
|
+
function startOfDay(date) {
|
|
448
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
|
449
|
+
}
|
|
450
|
+
function getDayDifference(from, to) {
|
|
451
|
+
return Math.round((startOfDay(to).getTime() - startOfDay(from).getTime()) / (1440 * 60 * 1e3));
|
|
452
|
+
}
|
|
453
|
+
function capitalizeMonthLabel(value) {
|
|
454
|
+
return value.split(" ").map((part, index) => {
|
|
455
|
+
if (index !== 1 || part.length === 0) return part;
|
|
456
|
+
const [firstCharacter = "", ...restCharacters] = part;
|
|
457
|
+
return `${firstCharacter.toUpperCase()}${restCharacters.join("")}`;
|
|
458
|
+
}).join(" ");
|
|
459
|
+
}
|
|
460
|
+
function formatAbsoluteDate(value, options = {}) {
|
|
461
|
+
const date = getDate(value);
|
|
462
|
+
const currentYear = (/* @__PURE__ */ new Date()).getFullYear();
|
|
463
|
+
const includesYear = date.getFullYear() !== currentYear;
|
|
464
|
+
const locale = options.locale ?? "en-US";
|
|
465
|
+
return capitalizeMonthLabel(new Intl.DateTimeFormat(locale, {
|
|
466
|
+
day: "numeric",
|
|
467
|
+
month: "short",
|
|
468
|
+
...includesYear ? { year: "numeric" } : {}
|
|
469
|
+
}).format(date));
|
|
470
|
+
}
|
|
471
|
+
function formatDisplayDate(value, options = {}) {
|
|
472
|
+
const date = getDate(value);
|
|
473
|
+
const dayDifference = getDayDifference(date, /* @__PURE__ */ new Date());
|
|
474
|
+
const labels = {
|
|
475
|
+
...DEFAULT_DATE_DISPLAY_LABELS,
|
|
476
|
+
...options.labels
|
|
477
|
+
};
|
|
478
|
+
if (dayDifference === 0) return labels.today;
|
|
479
|
+
if (dayDifference === 1) return labels.yesterday;
|
|
480
|
+
if (dayDifference >= 2 && dayDifference <= 10) return labels.daysAgo(dayDifference);
|
|
481
|
+
return formatAbsoluteDate(date, options);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
//#endregion
|
|
485
|
+
//#region src/primitives/display-date.tsx
|
|
486
|
+
function DisplayDate({ value, formatOptions, className }) {
|
|
487
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
488
|
+
return /* @__PURE__ */ jsx("time", {
|
|
489
|
+
dateTime: date.toISOString(),
|
|
490
|
+
title: formatAbsoluteDate(date, formatOptions),
|
|
491
|
+
className,
|
|
492
|
+
children: formatDisplayDate(date, formatOptions)
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
//#endregion
|
|
497
|
+
//#region src/primitives/field-detail-row.tsx
|
|
498
|
+
function FieldDetailRow({ icon: Icon, label, value, labelColumnClassName = "grid-cols-[110px_minmax(0,1fr)]" }) {
|
|
499
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
500
|
+
className: `grid ${labelColumnClassName} items-start gap-2 py-1.5`,
|
|
501
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
502
|
+
className: "flex items-center gap-2 text-sm text-muted-foreground",
|
|
503
|
+
children: [/* @__PURE__ */ jsx(Icon, { className: "size-4 shrink-0" }), /* @__PURE__ */ jsx("span", {
|
|
504
|
+
className: "text-[12px]",
|
|
505
|
+
children: label
|
|
506
|
+
})]
|
|
507
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
508
|
+
className: "min-w-0 text-[14px] leading-5 tracking-[-0.28px] text-foreground",
|
|
509
|
+
children: value
|
|
510
|
+
})]
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
//#endregion
|
|
515
|
+
//#region src/primitives/file-dropzone.tsx
|
|
516
|
+
function formatFileSize(size) {
|
|
517
|
+
if (size < 1024) return `${size} B`;
|
|
518
|
+
if (size < 1024 * 1024) return `${Math.round(size / 1024)} KB`;
|
|
519
|
+
return `${(size / (1024 * 1024)).toFixed(1)} MB`;
|
|
520
|
+
}
|
|
521
|
+
function getFileTypeLabel(file) {
|
|
522
|
+
return file.type || file.name.split(".").pop()?.toUpperCase() || "File";
|
|
523
|
+
}
|
|
524
|
+
function FileDropzone({ accept, browseLabel = "Browse", browseButtonClassName, className, currentPreviewUrl = null, disabled = false, emptyIcon, helperText, footerText = "Drag a file here or click the box to select one.", inputClassName, multiple = false, onFileSelect, onFilesSelect, onRemove, previewAlt, title = "Drop a file here or browse" }) {
|
|
525
|
+
const inputRef = useRef(null);
|
|
526
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
527
|
+
const [selectedFiles, setSelectedFiles] = useState([]);
|
|
528
|
+
const hasSelectedFiles = selectedFiles.length > 0;
|
|
529
|
+
const openFilePicker = () => {
|
|
530
|
+
if (disabled) return;
|
|
531
|
+
inputRef.current?.click();
|
|
532
|
+
};
|
|
533
|
+
const handleFileSelection = (files) => {
|
|
534
|
+
const selectedFiles$1 = Array.from(files ?? []);
|
|
535
|
+
if (selectedFiles$1.length === 0 || disabled) return;
|
|
536
|
+
setSelectedFiles(selectedFiles$1);
|
|
537
|
+
if (multiple && onFilesSelect) {
|
|
538
|
+
onFilesSelect(selectedFiles$1);
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
const firstFile = selectedFiles$1[0];
|
|
542
|
+
if (firstFile) onFileSelect(firstFile);
|
|
543
|
+
};
|
|
544
|
+
const handleInputChange = (event) => {
|
|
545
|
+
handleFileSelection(event.target.files);
|
|
546
|
+
event.target.value = "";
|
|
547
|
+
};
|
|
548
|
+
const handleDrop = (event) => {
|
|
549
|
+
event.preventDefault();
|
|
550
|
+
event.stopPropagation();
|
|
551
|
+
setIsDragging(false);
|
|
552
|
+
handleFileSelection(event.dataTransfer.files);
|
|
553
|
+
};
|
|
554
|
+
const handleRemove = (event) => {
|
|
555
|
+
event.stopPropagation();
|
|
556
|
+
setSelectedFiles([]);
|
|
557
|
+
onRemove?.();
|
|
558
|
+
};
|
|
559
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
560
|
+
/* @__PURE__ */ jsx("input", {
|
|
561
|
+
ref: inputRef,
|
|
562
|
+
type: "file",
|
|
563
|
+
accept,
|
|
564
|
+
multiple,
|
|
565
|
+
className: "hidden",
|
|
566
|
+
onChange: handleInputChange,
|
|
567
|
+
disabled
|
|
568
|
+
}),
|
|
569
|
+
/* @__PURE__ */ jsx("div", {
|
|
570
|
+
role: "button",
|
|
571
|
+
tabIndex: disabled ? -1 : 0,
|
|
572
|
+
"aria-disabled": disabled,
|
|
573
|
+
onClick: openFilePicker,
|
|
574
|
+
onKeyDown: (event) => {
|
|
575
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
576
|
+
event.preventDefault();
|
|
577
|
+
openFilePicker();
|
|
578
|
+
}
|
|
579
|
+
},
|
|
580
|
+
onDragEnter: (event) => {
|
|
581
|
+
event.preventDefault();
|
|
582
|
+
event.stopPropagation();
|
|
583
|
+
if (!disabled) setIsDragging(true);
|
|
584
|
+
},
|
|
585
|
+
onDragOver: (event) => {
|
|
586
|
+
event.preventDefault();
|
|
587
|
+
event.stopPropagation();
|
|
588
|
+
if (!disabled) setIsDragging(true);
|
|
589
|
+
},
|
|
590
|
+
onDragLeave: (event) => {
|
|
591
|
+
event.preventDefault();
|
|
592
|
+
event.stopPropagation();
|
|
593
|
+
setIsDragging(false);
|
|
594
|
+
},
|
|
595
|
+
onDrop: handleDrop,
|
|
596
|
+
className: cn("bg-muted/30 relative flex w-full cursor-pointer flex-col items-center justify-center rounded-lg border border-dashed px-6 py-5 text-center transition-colors outline-none", "border-primary/60 hover:bg-muted/50 focus-visible:ring-primary/20 focus-visible:ring-2", isDragging && "bg-muted/60", disabled && "cursor-not-allowed opacity-60", className),
|
|
597
|
+
children: currentPreviewUrl ? /* @__PURE__ */ jsxs("div", {
|
|
598
|
+
className: "bg-background relative flex min-h-40 w-full items-center justify-center overflow-hidden rounded-md",
|
|
599
|
+
children: [
|
|
600
|
+
/* @__PURE__ */ jsx("img", {
|
|
601
|
+
src: currentPreviewUrl,
|
|
602
|
+
alt: previewAlt,
|
|
603
|
+
className: "size-full object-contain"
|
|
604
|
+
}),
|
|
605
|
+
onRemove ? /* @__PURE__ */ jsx(Button, {
|
|
606
|
+
type: "button",
|
|
607
|
+
variant: "destructive",
|
|
608
|
+
size: "icon-xs",
|
|
609
|
+
className: "absolute top-2 right-2 z-10",
|
|
610
|
+
onClick: handleRemove,
|
|
611
|
+
disabled,
|
|
612
|
+
children: /* @__PURE__ */ jsx(X, { className: "size-3.5" })
|
|
613
|
+
}) : null,
|
|
614
|
+
hasSelectedFiles ? /* @__PURE__ */ jsxs("div", {
|
|
615
|
+
className: "bg-background/95 absolute right-2 bottom-2 left-2 rounded-md border px-3 py-2 text-left shadow-sm backdrop-blur",
|
|
616
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
617
|
+
className: "truncate text-sm font-medium",
|
|
618
|
+
children: selectedFiles[0]?.name
|
|
619
|
+
}), selectedFiles[0] ? /* @__PURE__ */ jsxs("p", {
|
|
620
|
+
className: "text-muted-foreground text-xs",
|
|
621
|
+
children: [
|
|
622
|
+
getFileTypeLabel(selectedFiles[0]),
|
|
623
|
+
" | ",
|
|
624
|
+
formatFileSize(selectedFiles[0].size)
|
|
625
|
+
]
|
|
626
|
+
}) : null]
|
|
627
|
+
}) : null
|
|
628
|
+
]
|
|
629
|
+
}) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
630
|
+
/* @__PURE__ */ jsx("div", {
|
|
631
|
+
className: "text-primary mb-2 flex items-center justify-center",
|
|
632
|
+
children: emptyIcon ?? /* @__PURE__ */ jsx(Upload, { className: "size-5" })
|
|
633
|
+
}),
|
|
634
|
+
/* @__PURE__ */ jsxs("div", {
|
|
635
|
+
className: "space-y-0.5",
|
|
636
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
637
|
+
className: "text-foreground text-base font-medium",
|
|
638
|
+
children: title
|
|
639
|
+
}), helperText ? /* @__PURE__ */ jsx("p", {
|
|
640
|
+
className: "text-muted-foreground text-sm",
|
|
641
|
+
children: helperText
|
|
642
|
+
}) : null]
|
|
643
|
+
}),
|
|
644
|
+
/* @__PURE__ */ jsx(Button, {
|
|
645
|
+
type: "button",
|
|
646
|
+
size: "sm",
|
|
647
|
+
className: cn("mt-4 shadow-[0px_1px_1px_rgba(5,32,81,0.05)]", browseButtonClassName),
|
|
648
|
+
onClick: (event) => {
|
|
649
|
+
event.stopPropagation();
|
|
650
|
+
openFilePicker();
|
|
651
|
+
},
|
|
652
|
+
disabled,
|
|
653
|
+
children: browseLabel
|
|
654
|
+
}),
|
|
655
|
+
hasSelectedFiles ? /* @__PURE__ */ jsx("div", {
|
|
656
|
+
className: "mt-4 w-full space-y-2",
|
|
657
|
+
children: selectedFiles.map((file) => /* @__PURE__ */ jsxs("div", {
|
|
658
|
+
className: "bg-background flex min-w-0 items-center gap-3 rounded-md border px-3 py-2 text-left",
|
|
659
|
+
children: [
|
|
660
|
+
/* @__PURE__ */ jsx(FileText, { className: "text-muted-foreground size-4 shrink-0" }),
|
|
661
|
+
/* @__PURE__ */ jsxs("div", {
|
|
662
|
+
className: "min-w-0 flex-1",
|
|
663
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
664
|
+
className: "truncate text-sm font-medium",
|
|
665
|
+
children: file.name
|
|
666
|
+
}), /* @__PURE__ */ jsxs("p", {
|
|
667
|
+
className: "text-muted-foreground text-xs",
|
|
668
|
+
children: [
|
|
669
|
+
getFileTypeLabel(file),
|
|
670
|
+
" | ",
|
|
671
|
+
formatFileSize(file.size)
|
|
672
|
+
]
|
|
673
|
+
})]
|
|
674
|
+
}),
|
|
675
|
+
/* @__PURE__ */ jsx(Check, { className: "text-primary size-4 shrink-0" })
|
|
676
|
+
]
|
|
677
|
+
}, `${file.name}-${file.size}-${file.lastModified}`))
|
|
678
|
+
}) : null
|
|
679
|
+
] })
|
|
680
|
+
}),
|
|
681
|
+
footerText ? /* @__PURE__ */ jsx("p", {
|
|
682
|
+
className: cn("text-muted-foreground mt-2 text-xs", inputClassName),
|
|
683
|
+
children: footerText
|
|
684
|
+
}) : null
|
|
685
|
+
] });
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
//#endregion
|
|
689
|
+
//#region src/primitives/keyboard-shortcut-hint.tsx
|
|
690
|
+
function KeyboardKeycap({ children, className }) {
|
|
691
|
+
return /* @__PURE__ */ jsx("span", {
|
|
692
|
+
className: cn("inline-flex h-4 min-w-4 items-center justify-center rounded-[4px] border px-1 text-[9px] font-semibold leading-none", className),
|
|
693
|
+
children
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
function ShortcutModifierKeycap({ modifierLabel, className }) {
|
|
697
|
+
return /* @__PURE__ */ jsx(KeyboardKeycap, {
|
|
698
|
+
className,
|
|
699
|
+
children: modifierLabel === "Cmd" ? /* @__PURE__ */ jsx(Command, { className: "size-[10px]" }) : "Ctrl"
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
//#endregion
|
|
704
|
+
//#region src/overlays/responsive-sheet.footer.tsx
|
|
705
|
+
function DesktopConfirmShortcutHint({ desktopModifierLabel }) {
|
|
706
|
+
const keycapClassName = "border-primary-foreground/20 bg-primary-foreground/10";
|
|
707
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
708
|
+
className: "text-primary-foreground/70 inline-flex items-center gap-1",
|
|
709
|
+
children: [/* @__PURE__ */ jsx(ShortcutModifierKeycap, {
|
|
710
|
+
modifierLabel: desktopModifierLabel,
|
|
711
|
+
className: keycapClassName
|
|
712
|
+
}), /* @__PURE__ */ jsx(KeyboardKeycap, {
|
|
713
|
+
className: keycapClassName,
|
|
714
|
+
children: /* @__PURE__ */ jsx(CornerDownLeft, { className: "size-[10px]" })
|
|
715
|
+
})]
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
function SheetActionFooter({ footer, onCancel, cancelLabel, onConfirm, confirmLabel, confirmDisabled, confirmLoading, desktopConfirmShortcutEnabled = false, desktopModifierLabel = null }) {
|
|
719
|
+
if (footer) return footer;
|
|
720
|
+
if (!onCancel && !onConfirm) return null;
|
|
721
|
+
const footerButtonClassName = Number(Boolean(onCancel)) + Number(Boolean(onConfirm)) === 1 ? "w-full" : "min-w-0 flex-1";
|
|
722
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
723
|
+
className: "flex w-full flex-nowrap items-center gap-2",
|
|
724
|
+
children: [onCancel ? /* @__PURE__ */ jsx(Button, {
|
|
725
|
+
type: "button",
|
|
726
|
+
variant: "outline",
|
|
727
|
+
onClick: onCancel,
|
|
728
|
+
className: footerButtonClassName,
|
|
729
|
+
children: cancelLabel
|
|
730
|
+
}) : null, onConfirm ? /* @__PURE__ */ jsx(Button, {
|
|
731
|
+
type: "button",
|
|
732
|
+
onClick: onConfirm,
|
|
733
|
+
disabled: confirmDisabled,
|
|
734
|
+
className: `${footerButtonClassName} relative`,
|
|
735
|
+
children: /* @__PURE__ */ jsxs("span", {
|
|
736
|
+
className: "inline-flex w-full items-center justify-center",
|
|
737
|
+
children: [/* @__PURE__ */ jsx("span", { children: confirmLoading ? "Saving..." : confirmLabel }), desktopConfirmShortcutEnabled && desktopModifierLabel ? /* @__PURE__ */ jsx("span", {
|
|
738
|
+
className: "absolute top-1/2 right-2 -translate-y-1/2",
|
|
739
|
+
children: /* @__PURE__ */ jsx(DesktopConfirmShortcutHint, { desktopModifierLabel })
|
|
740
|
+
}) : null]
|
|
741
|
+
})
|
|
742
|
+
}) : null]
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
//#endregion
|
|
747
|
+
//#region src/primitives/sheet.tsx
|
|
748
|
+
function Sheet({ ...props }) {
|
|
749
|
+
return /* @__PURE__ */ jsx(Dialog$1.Root, {
|
|
750
|
+
"data-slot": "sheet",
|
|
751
|
+
...props
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
function SheetPortal({ ...props }) {
|
|
755
|
+
return /* @__PURE__ */ jsx(Dialog$1.Portal, {
|
|
756
|
+
"data-slot": "sheet-portal",
|
|
757
|
+
...props
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
function SheetOverlay({ className, ...props }) {
|
|
761
|
+
return /* @__PURE__ */ jsx(Dialog$1.Overlay, {
|
|
762
|
+
"data-slot": "sheet-overlay",
|
|
763
|
+
className: cn("data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 fixed inset-0 z-50 bg-black/10 backdrop-blur-xs duration-100 data-ending-style:opacity-0 data-starting-style:opacity-0", className),
|
|
764
|
+
...props
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
function SheetContent({ className, children, side = "right", showCloseButton = true, ...props }) {
|
|
768
|
+
return /* @__PURE__ */ jsxs(SheetPortal, { children: [/* @__PURE__ */ jsx(SheetOverlay, {}), /* @__PURE__ */ jsxs(Dialog$1.Content, {
|
|
769
|
+
"data-slot": "sheet-content",
|
|
770
|
+
"data-side": side,
|
|
771
|
+
className: cn("bg-background data-open:animate-in data-closed:animate-out data-[side=right]:data-closed:slide-out-to-right-10 data-[side=right]:data-open:slide-in-from-right-10 data-[side=left]:data-closed:slide-out-to-left-10 data-[side=left]:data-open:slide-in-from-left-10 data-[side=top]:data-closed:slide-out-to-top-10 data-[side=top]:data-open:slide-in-from-top-10 data-closed:fade-out-0 data-open:fade-in-0 data-[side=bottom]:data-closed:slide-out-to-bottom-10 data-[side=bottom]:data-open:slide-in-from-bottom-10 fixed z-50 flex flex-col gap-4 overflow-visible bg-clip-padding text-sm shadow-lg transition duration-200 ease-in-out data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:rounded-r-xl data-[side=left]:border-r data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:rounded-l-xl data-[side=right]:border-l data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm", className),
|
|
772
|
+
...props,
|
|
773
|
+
children: [children, showCloseButton ? /* @__PURE__ */ jsx(Dialog$1.Close, {
|
|
774
|
+
"data-slot": "sheet-close",
|
|
775
|
+
asChild: true,
|
|
776
|
+
children: /* @__PURE__ */ jsxs(Button, {
|
|
777
|
+
variant: "ghost",
|
|
778
|
+
className: "absolute top-3 right-3",
|
|
779
|
+
size: "icon-sm",
|
|
780
|
+
children: [/* @__PURE__ */ jsx(X, { className: "size-4" }), /* @__PURE__ */ jsx("span", {
|
|
781
|
+
className: "sr-only",
|
|
782
|
+
children: "Close"
|
|
783
|
+
})]
|
|
784
|
+
})
|
|
785
|
+
}) : null]
|
|
786
|
+
})] });
|
|
787
|
+
}
|
|
788
|
+
function SheetHeader({ className, ...props }) {
|
|
789
|
+
return /* @__PURE__ */ jsx("div", {
|
|
790
|
+
"data-slot": "sheet-header",
|
|
791
|
+
className: cn("flex flex-col gap-0.5 p-4", className),
|
|
792
|
+
...props
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
function SheetTitle({ className, ...props }) {
|
|
796
|
+
return /* @__PURE__ */ jsx(Dialog$1.Title, {
|
|
797
|
+
"data-slot": "sheet-title",
|
|
798
|
+
className: cn("text-foreground text-base font-medium", className),
|
|
799
|
+
...props
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
function SheetDescription({ className, ...props }) {
|
|
803
|
+
return /* @__PURE__ */ jsx(Dialog$1.Description, {
|
|
804
|
+
"data-slot": "sheet-description",
|
|
805
|
+
className: cn("text-muted-foreground text-sm", className),
|
|
806
|
+
...props
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
//#endregion
|
|
811
|
+
//#region src/overlays/responsive-sheet.layouts.tsx
|
|
812
|
+
function SheetDescriptionBlock({ title, description, classes }) {
|
|
813
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(SheetTitle, {
|
|
814
|
+
className: classes?.title,
|
|
815
|
+
children: title
|
|
816
|
+
}), description ? /* @__PURE__ */ jsx(SheetDescription, {
|
|
817
|
+
className: classes?.description,
|
|
818
|
+
children: description
|
|
819
|
+
}) : null] });
|
|
820
|
+
}
|
|
821
|
+
function shouldPreventOutsideInteraction(target, guard) {
|
|
822
|
+
const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
|
|
823
|
+
if (!element) return false;
|
|
824
|
+
if (element.closest("[data-searchable-select-content]")) return true;
|
|
825
|
+
return guard?.selectors.some((selector) => element.closest(selector)) ?? false;
|
|
826
|
+
}
|
|
827
|
+
function MobileSheetLayout({ open, onOpenChange, modal, outsideInteractionGuard, title, description, children, footer, mobileDrawerContentClassName, contentClassName, footerClassName, classes }) {
|
|
828
|
+
return /* @__PURE__ */ jsx(Drawer, {
|
|
829
|
+
open,
|
|
830
|
+
onOpenChange,
|
|
831
|
+
modal,
|
|
832
|
+
children: /* @__PURE__ */ jsxs(DrawerContent, {
|
|
833
|
+
"aria-describedby": description ? void 0 : "responsive-sheet-description-empty",
|
|
834
|
+
className: cn("px-4 pb-[calc(env(safe-area-inset-bottom)+20px)]", mobileDrawerContentClassName, classes?.mobileContent),
|
|
835
|
+
onInteractOutside: (event) => {
|
|
836
|
+
if (shouldPreventOutsideInteraction(event.target, outsideInteractionGuard)) event.preventDefault();
|
|
837
|
+
},
|
|
838
|
+
onPointerDownOutside: (event) => {
|
|
839
|
+
if (shouldPreventOutsideInteraction(event.target, outsideInteractionGuard)) event.preventDefault();
|
|
840
|
+
},
|
|
841
|
+
children: [/* @__PURE__ */ jsxs(DrawerHeader, {
|
|
842
|
+
className: cn("px-0 pb-4", classes?.header),
|
|
843
|
+
children: [/* @__PURE__ */ jsx(DrawerTitle, {
|
|
844
|
+
className: classes?.title,
|
|
845
|
+
children: title
|
|
846
|
+
}), description ? /* @__PURE__ */ jsx(DrawerDescription, {
|
|
847
|
+
className: classes?.description,
|
|
848
|
+
children: description
|
|
849
|
+
}) : /* @__PURE__ */ jsx(DrawerDescription, {
|
|
850
|
+
id: "responsive-sheet-description-empty",
|
|
851
|
+
className: "sr-only",
|
|
852
|
+
children: "Dialog"
|
|
853
|
+
})]
|
|
854
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
855
|
+
className: "flex min-h-0 flex-1 flex-col gap-4",
|
|
856
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
857
|
+
className: cn("-mx-1 flex-1 overflow-y-auto px-1 pb-3 [scrollbar-gutter:stable]", contentClassName, classes?.body),
|
|
858
|
+
children
|
|
859
|
+
}), footer ? /* @__PURE__ */ jsx("div", {
|
|
860
|
+
className: cn("shrink-0 border-t pt-4 pb-3", footerClassName, classes?.footer),
|
|
861
|
+
children: footer
|
|
862
|
+
}) : null]
|
|
863
|
+
})]
|
|
864
|
+
})
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
function DesktopSheetLayout({ open, onOpenChange, modal, outsideInteractionGuard, title, description, children, footer, width, contentClassName, footerClassName, classes }) {
|
|
868
|
+
return /* @__PURE__ */ jsx(Sheet, {
|
|
869
|
+
open,
|
|
870
|
+
onOpenChange,
|
|
871
|
+
modal,
|
|
872
|
+
children: /* @__PURE__ */ jsxs(SheetContent, {
|
|
873
|
+
"aria-describedby": description ? void 0 : "responsive-sheet-description-empty",
|
|
874
|
+
style: {
|
|
875
|
+
width: `${String(width)}px`,
|
|
876
|
+
maxWidth: "85vw"
|
|
877
|
+
},
|
|
878
|
+
className: cn("flex flex-col gap-0 p-0", classes?.desktopContent),
|
|
879
|
+
onInteractOutside: (event) => {
|
|
880
|
+
if (shouldPreventOutsideInteraction(event.target, outsideInteractionGuard)) event.preventDefault();
|
|
881
|
+
},
|
|
882
|
+
onPointerDownOutside: (event) => {
|
|
883
|
+
if (shouldPreventOutsideInteraction(event.target, outsideInteractionGuard)) event.preventDefault();
|
|
884
|
+
},
|
|
885
|
+
children: [
|
|
886
|
+
/* @__PURE__ */ jsx(SheetHeader, {
|
|
887
|
+
className: cn("border-b px-4 py-4", classes?.header),
|
|
888
|
+
children: /* @__PURE__ */ jsx(SheetDescriptionBlock, {
|
|
889
|
+
title,
|
|
890
|
+
description,
|
|
891
|
+
classes
|
|
892
|
+
})
|
|
893
|
+
}),
|
|
894
|
+
description ? null : /* @__PURE__ */ jsx(SheetDescription, {
|
|
895
|
+
id: "responsive-sheet-description-empty",
|
|
896
|
+
className: "sr-only",
|
|
897
|
+
children: "Dialog"
|
|
898
|
+
}),
|
|
899
|
+
/* @__PURE__ */ jsxs("div", {
|
|
900
|
+
className: "flex min-h-0 flex-1 flex-col",
|
|
901
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
902
|
+
className: cn("flex-1 overflow-x-visible overflow-y-auto px-4 pt-4 pb-6 [scrollbar-gutter:stable]", contentClassName, classes?.body),
|
|
903
|
+
children
|
|
904
|
+
}), footer ? /* @__PURE__ */ jsx("div", {
|
|
905
|
+
className: cn("border-t px-4 py-4", footerClassName, classes?.footer),
|
|
906
|
+
children: footer
|
|
907
|
+
}) : null]
|
|
908
|
+
})
|
|
909
|
+
]
|
|
910
|
+
})
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
//#endregion
|
|
915
|
+
//#region src/overlays/responsive-sheet.shortcuts.ts
|
|
916
|
+
function getDesktopShortcutModifierLabel() {
|
|
917
|
+
if (typeof navigator === "undefined") return "Ctrl";
|
|
918
|
+
const platform = navigator.userAgentData?.platform ?? navigator.userAgent;
|
|
919
|
+
return /Mac|iPhone|iPad|iPod/i.test(platform) ? "Cmd" : "Ctrl";
|
|
920
|
+
}
|
|
921
|
+
function isAllowedConfirmShortcutEvent(event, desktopModifierLabel) {
|
|
922
|
+
if (event.key !== "Enter" || event.repeat || event.isComposing) return false;
|
|
923
|
+
const expectsMetaKey = desktopModifierLabel === "Cmd";
|
|
924
|
+
const usedExpectedModifier = expectsMetaKey ? event.metaKey : event.ctrlKey;
|
|
925
|
+
const usedOtherModifier = expectsMetaKey ? event.ctrlKey : event.metaKey;
|
|
926
|
+
const usedShiftModifier = event.shiftKey;
|
|
927
|
+
const usedAltModifier = event.altKey;
|
|
928
|
+
if (!usedExpectedModifier || usedOtherModifier || usedShiftModifier || usedAltModifier) return false;
|
|
929
|
+
return true;
|
|
930
|
+
}
|
|
931
|
+
function useDesktopShortcutModifierLabel(enabled) {
|
|
932
|
+
const [desktopModifierLabel, setDesktopModifierLabel] = useState(null);
|
|
933
|
+
useEffect(() => {
|
|
934
|
+
if (!enabled) {
|
|
935
|
+
setDesktopModifierLabel(null);
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
setDesktopModifierLabel(getDesktopShortcutModifierLabel());
|
|
939
|
+
}, [enabled]);
|
|
940
|
+
return desktopModifierLabel;
|
|
941
|
+
}
|
|
942
|
+
function useDesktopConfirmShortcut({ open, enabled, confirmDisabled, confirmLoading, onConfirm }) {
|
|
943
|
+
useEffect(() => {
|
|
944
|
+
if (!open || !enabled || !onConfirm || confirmDisabled || confirmLoading) return;
|
|
945
|
+
const desktopModifierLabel = getDesktopShortcutModifierLabel();
|
|
946
|
+
const handleKeyDown = (event) => {
|
|
947
|
+
if (!isAllowedConfirmShortcutEvent(event, desktopModifierLabel)) return;
|
|
948
|
+
event.preventDefault();
|
|
949
|
+
onConfirm();
|
|
950
|
+
};
|
|
951
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
952
|
+
return () => {
|
|
953
|
+
window.removeEventListener("keydown", handleKeyDown);
|
|
954
|
+
};
|
|
955
|
+
}, [
|
|
956
|
+
confirmDisabled,
|
|
957
|
+
confirmLoading,
|
|
958
|
+
enabled,
|
|
959
|
+
onConfirm,
|
|
960
|
+
open
|
|
961
|
+
]);
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
//#endregion
|
|
965
|
+
//#region src/utils/use-media-query.ts
|
|
966
|
+
function useMediaQuery(query, defaultValue = false) {
|
|
967
|
+
const [matches, setMatches] = useState(defaultValue);
|
|
968
|
+
useEffect(() => {
|
|
969
|
+
const mediaQuery = window.matchMedia(query);
|
|
970
|
+
const handleChange = () => {
|
|
971
|
+
setMatches(mediaQuery.matches);
|
|
972
|
+
};
|
|
973
|
+
handleChange();
|
|
974
|
+
mediaQuery.addEventListener("change", handleChange);
|
|
975
|
+
return () => {
|
|
976
|
+
mediaQuery.removeEventListener("change", handleChange);
|
|
977
|
+
};
|
|
978
|
+
}, [query]);
|
|
979
|
+
return matches;
|
|
980
|
+
}
|
|
981
|
+
function useIsMobile(maxWidth = 767) {
|
|
982
|
+
return useMediaQuery(`(max-width: ${String(maxWidth)}px)`);
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
//#endregion
|
|
986
|
+
//#region src/overlays/responsive-sheet.tsx
|
|
987
|
+
function ResponsiveSheet({ open, onOpenChange, title, description, children, footer, onCancel, cancelLabel = "Cancel", onConfirm, confirmLabel = "Save", confirmDisabled = false, confirmLoading = false, width = 550, modal = true, outsideInteractionGuard, enableDesktopConfirmShortcut = true, mobileDrawerContentClassName, className, contentClassName, footerClassName, classes }) {
|
|
988
|
+
const isMobile = useIsMobile();
|
|
989
|
+
const desktopConfirmShortcutEnabled = !isMobile && enableDesktopConfirmShortcut && Boolean(onConfirm);
|
|
990
|
+
const desktopModifierLabel = useDesktopShortcutModifierLabel(desktopConfirmShortcutEnabled);
|
|
991
|
+
useDesktopConfirmShortcut({
|
|
992
|
+
open,
|
|
993
|
+
enabled: desktopConfirmShortcutEnabled,
|
|
994
|
+
confirmDisabled,
|
|
995
|
+
confirmLoading,
|
|
996
|
+
onConfirm: onConfirm ? () => {
|
|
997
|
+
onConfirm();
|
|
998
|
+
} : void 0
|
|
999
|
+
});
|
|
1000
|
+
const resolvedFooter = /* @__PURE__ */ jsx(SheetActionFooter, {
|
|
1001
|
+
footer,
|
|
1002
|
+
onCancel,
|
|
1003
|
+
cancelLabel,
|
|
1004
|
+
onConfirm,
|
|
1005
|
+
confirmLabel,
|
|
1006
|
+
confirmDisabled,
|
|
1007
|
+
confirmLoading,
|
|
1008
|
+
desktopConfirmShortcutEnabled,
|
|
1009
|
+
desktopModifierLabel
|
|
1010
|
+
});
|
|
1011
|
+
const sharedLayoutProps = {
|
|
1012
|
+
open,
|
|
1013
|
+
onOpenChange,
|
|
1014
|
+
modal,
|
|
1015
|
+
outsideInteractionGuard,
|
|
1016
|
+
title,
|
|
1017
|
+
description,
|
|
1018
|
+
footer: [
|
|
1019
|
+
footer,
|
|
1020
|
+
onCancel,
|
|
1021
|
+
onConfirm
|
|
1022
|
+
].some((value) => value !== null && value !== void 0) ? resolvedFooter : null,
|
|
1023
|
+
children,
|
|
1024
|
+
contentClassName,
|
|
1025
|
+
footerClassName,
|
|
1026
|
+
mobileDrawerContentClassName,
|
|
1027
|
+
classes: {
|
|
1028
|
+
...classes,
|
|
1029
|
+
desktopContent: cn(className, classes?.desktopContent),
|
|
1030
|
+
mobileContent: cn(className, mobileDrawerContentClassName, classes?.mobileContent)
|
|
1031
|
+
}
|
|
1032
|
+
};
|
|
1033
|
+
return isMobile ? /* @__PURE__ */ jsx(MobileSheetLayout, { ...sharedLayoutProps }) : /* @__PURE__ */ jsx(DesktopSheetLayout, {
|
|
1034
|
+
...sharedLayoutProps,
|
|
1035
|
+
width
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
//#endregion
|
|
1040
|
+
//#region src/primitives/tooltip.tsx
|
|
1041
|
+
function TooltipProvider({ delayDuration = 0, ...props }) {
|
|
1042
|
+
return /* @__PURE__ */ jsx(Tooltip$1.Provider, {
|
|
1043
|
+
"data-slot": "tooltip-provider",
|
|
1044
|
+
delayDuration,
|
|
1045
|
+
...props
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
function Tooltip({ ...props }) {
|
|
1049
|
+
return /* @__PURE__ */ jsx(Tooltip$1.Root, {
|
|
1050
|
+
"data-slot": "tooltip",
|
|
1051
|
+
...props
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
1054
|
+
function TooltipTrigger({ ...props }) {
|
|
1055
|
+
return /* @__PURE__ */ jsx(Tooltip$1.Trigger, {
|
|
1056
|
+
"data-slot": "tooltip-trigger",
|
|
1057
|
+
...props
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
function TooltipContent({ className, sideOffset = 0, children, ...props }) {
|
|
1061
|
+
return /* @__PURE__ */ jsx(Tooltip$1.Portal, { children: /* @__PURE__ */ jsxs(Tooltip$1.Content, {
|
|
1062
|
+
"data-slot": "tooltip-content",
|
|
1063
|
+
sideOffset,
|
|
1064
|
+
className: cn("data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 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 rounded-md bg-foreground px-3 py-1.5 text-xs text-background z-50 w-fit max-w-[min(20rem,calc(100vw-1.5rem))] whitespace-normal break-words origin-(--radix-tooltip-content-transform-origin)", className),
|
|
1065
|
+
...props,
|
|
1066
|
+
children: [children, /* @__PURE__ */ jsx(Tooltip$1.Arrow, { className: "size-2.5 rotate-45 rounded-[2px] bg-foreground fill-foreground z-50 translate-y-[calc(-50%_-_2px)]" })]
|
|
1067
|
+
}) });
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
//#endregion
|
|
1071
|
+
//#region src/primitives/help-info-button.tsx
|
|
1072
|
+
function HelpInfoButton({ ariaLabel, tooltip, title, description, children, width = 620 }) {
|
|
1073
|
+
const [open, setOpen] = useState(false);
|
|
1074
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
|
|
1075
|
+
asChild: true,
|
|
1076
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
1077
|
+
type: "button",
|
|
1078
|
+
variant: "ghost",
|
|
1079
|
+
size: "icon",
|
|
1080
|
+
className: "size-6 text-muted-foreground hover:text-foreground",
|
|
1081
|
+
"aria-label": ariaLabel,
|
|
1082
|
+
onClick: () => {
|
|
1083
|
+
setOpen(true);
|
|
1084
|
+
},
|
|
1085
|
+
children: /* @__PURE__ */ jsx(CircleHelp, { className: "size-3.5" })
|
|
1086
|
+
})
|
|
1087
|
+
}), /* @__PURE__ */ jsx(TooltipContent, { children: tooltip })] }), /* @__PURE__ */ jsx(ResponsiveSheet, {
|
|
1088
|
+
open,
|
|
1089
|
+
onOpenChange: setOpen,
|
|
1090
|
+
title,
|
|
1091
|
+
description,
|
|
1092
|
+
cancelLabel: "Close",
|
|
1093
|
+
onCancel: () => {
|
|
1094
|
+
setOpen(false);
|
|
1095
|
+
},
|
|
1096
|
+
width,
|
|
1097
|
+
children
|
|
1098
|
+
})] });
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
//#endregion
|
|
1102
|
+
//#region src/primitives/input.tsx
|
|
1103
|
+
function Input({ className, type, ...props }) {
|
|
1104
|
+
return /* @__PURE__ */ jsx("input", {
|
|
1105
|
+
type,
|
|
1106
|
+
"data-slot": "input",
|
|
1107
|
+
className: cn("dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 disabled:bg-input/50 dark:disabled:bg-input/80 h-8 rounded-lg border bg-transparent px-2.5 py-1 text-base transition-colors file:h-6 file:text-sm file:font-medium focus-visible:ring-3 aria-invalid:ring-3 md:text-sm file:text-foreground placeholder:text-muted-foreground w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50", className),
|
|
1108
|
+
...props
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
//#endregion
|
|
1113
|
+
//#region src/primitives/label.tsx
|
|
1114
|
+
function Label({ className, ...props }) {
|
|
1115
|
+
return /* @__PURE__ */ jsx(Label$1.Root, {
|
|
1116
|
+
"data-slot": "label",
|
|
1117
|
+
className: cn("flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50", className),
|
|
1118
|
+
...props
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
//#endregion
|
|
1123
|
+
//#region src/primitives/pagination.tsx
|
|
1124
|
+
function Pagination({ currentPage, totalPages, totalItems, pageSize, startIndex, endIndex, onPageChange, pageSizeOptions, onPageSizeChange, className }) {
|
|
1125
|
+
if (totalPages <= 1) return null;
|
|
1126
|
+
const hasPrevPage = currentPage > 1;
|
|
1127
|
+
const hasNextPage = currentPage < totalPages;
|
|
1128
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1129
|
+
className: cn("flex items-center justify-between gap-3 py-3", className),
|
|
1130
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
1131
|
+
className: "text-xs text-muted-foreground",
|
|
1132
|
+
children: totalItems === 0 ? "No items" : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1133
|
+
"Showing ",
|
|
1134
|
+
/* @__PURE__ */ jsx("span", {
|
|
1135
|
+
className: "font-medium",
|
|
1136
|
+
children: startIndex + 1
|
|
1137
|
+
}),
|
|
1138
|
+
" -",
|
|
1139
|
+
" ",
|
|
1140
|
+
/* @__PURE__ */ jsx("span", {
|
|
1141
|
+
className: "font-medium",
|
|
1142
|
+
children: endIndex
|
|
1143
|
+
}),
|
|
1144
|
+
" of",
|
|
1145
|
+
" ",
|
|
1146
|
+
/* @__PURE__ */ jsx("span", {
|
|
1147
|
+
className: "font-medium",
|
|
1148
|
+
children: totalItems
|
|
1149
|
+
})
|
|
1150
|
+
] })
|
|
1151
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
1152
|
+
className: "flex items-center gap-1.5",
|
|
1153
|
+
children: [
|
|
1154
|
+
pageSizeOptions && onPageSizeChange && /* @__PURE__ */ jsxs("div", {
|
|
1155
|
+
className: "hidden items-center gap-1.5 sm:flex",
|
|
1156
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1157
|
+
className: "text-xs text-muted-foreground",
|
|
1158
|
+
children: "Rows:"
|
|
1159
|
+
}), /* @__PURE__ */ jsx("select", {
|
|
1160
|
+
value: String(pageSize),
|
|
1161
|
+
onChange: (event) => {
|
|
1162
|
+
onPageSizeChange(Number(event.target.value));
|
|
1163
|
+
},
|
|
1164
|
+
className: "border-input bg-background h-7 w-[64px] rounded-md border px-2 text-xs",
|
|
1165
|
+
children: pageSizeOptions.map((size) => /* @__PURE__ */ jsx("option", {
|
|
1166
|
+
value: String(size),
|
|
1167
|
+
children: size
|
|
1168
|
+
}, size))
|
|
1169
|
+
})]
|
|
1170
|
+
}),
|
|
1171
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1172
|
+
className: "hidden text-xs text-muted-foreground sm:block",
|
|
1173
|
+
children: [
|
|
1174
|
+
"Page ",
|
|
1175
|
+
currentPage,
|
|
1176
|
+
" of ",
|
|
1177
|
+
totalPages
|
|
1178
|
+
]
|
|
1179
|
+
}),
|
|
1180
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1181
|
+
className: "flex items-center gap-0.5",
|
|
1182
|
+
children: [
|
|
1183
|
+
/* @__PURE__ */ jsxs(Button, {
|
|
1184
|
+
variant: "outline",
|
|
1185
|
+
size: "icon",
|
|
1186
|
+
className: "size-7",
|
|
1187
|
+
onClick: () => {
|
|
1188
|
+
onPageChange(1);
|
|
1189
|
+
},
|
|
1190
|
+
disabled: !hasPrevPage,
|
|
1191
|
+
children: [/* @__PURE__ */ jsx(ChevronsLeft, { className: "size-3.5" }), /* @__PURE__ */ jsx("span", {
|
|
1192
|
+
className: "sr-only",
|
|
1193
|
+
children: "First page"
|
|
1194
|
+
})]
|
|
1195
|
+
}),
|
|
1196
|
+
/* @__PURE__ */ jsxs(Button, {
|
|
1197
|
+
variant: "outline",
|
|
1198
|
+
size: "icon",
|
|
1199
|
+
className: "size-7",
|
|
1200
|
+
onClick: () => {
|
|
1201
|
+
onPageChange(currentPage - 1);
|
|
1202
|
+
},
|
|
1203
|
+
disabled: !hasPrevPage,
|
|
1204
|
+
children: [/* @__PURE__ */ jsx(ChevronLeft, { className: "size-3.5" }), /* @__PURE__ */ jsx("span", {
|
|
1205
|
+
className: "sr-only",
|
|
1206
|
+
children: "Previous page"
|
|
1207
|
+
})]
|
|
1208
|
+
}),
|
|
1209
|
+
/* @__PURE__ */ jsxs("span", {
|
|
1210
|
+
className: "px-1.5 text-xs sm:hidden",
|
|
1211
|
+
children: [
|
|
1212
|
+
currentPage,
|
|
1213
|
+
"/",
|
|
1214
|
+
totalPages
|
|
1215
|
+
]
|
|
1216
|
+
}),
|
|
1217
|
+
/* @__PURE__ */ jsxs(Button, {
|
|
1218
|
+
variant: "outline",
|
|
1219
|
+
size: "icon",
|
|
1220
|
+
className: "size-7",
|
|
1221
|
+
onClick: () => {
|
|
1222
|
+
onPageChange(currentPage + 1);
|
|
1223
|
+
},
|
|
1224
|
+
disabled: !hasNextPage,
|
|
1225
|
+
children: [/* @__PURE__ */ jsx(ChevronRight, { className: "size-3.5" }), /* @__PURE__ */ jsx("span", {
|
|
1226
|
+
className: "sr-only",
|
|
1227
|
+
children: "Next page"
|
|
1228
|
+
})]
|
|
1229
|
+
}),
|
|
1230
|
+
/* @__PURE__ */ jsxs(Button, {
|
|
1231
|
+
variant: "outline",
|
|
1232
|
+
size: "icon",
|
|
1233
|
+
className: "size-7",
|
|
1234
|
+
onClick: () => {
|
|
1235
|
+
onPageChange(totalPages);
|
|
1236
|
+
},
|
|
1237
|
+
disabled: !hasNextPage,
|
|
1238
|
+
children: [/* @__PURE__ */ jsx(ChevronsRight, { className: "size-3.5" }), /* @__PURE__ */ jsx("span", {
|
|
1239
|
+
className: "sr-only",
|
|
1240
|
+
children: "Last page"
|
|
1241
|
+
})]
|
|
1242
|
+
})
|
|
1243
|
+
]
|
|
1244
|
+
})
|
|
1245
|
+
]
|
|
1246
|
+
})]
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
//#endregion
|
|
1251
|
+
//#region src/primitives/popover.tsx
|
|
1252
|
+
function Popover({ ...props }) {
|
|
1253
|
+
return /* @__PURE__ */ jsx(Popover$1.Root, {
|
|
1254
|
+
"data-slot": "popover",
|
|
1255
|
+
...props
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
function PopoverTrigger({ ...props }) {
|
|
1259
|
+
return /* @__PURE__ */ jsx(Popover$1.Trigger, {
|
|
1260
|
+
"data-slot": "popover-trigger",
|
|
1261
|
+
...props
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
function PopoverContent({ className, align = "center", sideOffset = 4, ...props }) {
|
|
1265
|
+
return /* @__PURE__ */ jsx(Popover$1.Portal, { children: /* @__PURE__ */ jsx(Popover$1.Content, {
|
|
1266
|
+
"data-slot": "popover-content",
|
|
1267
|
+
align,
|
|
1268
|
+
sideOffset,
|
|
1269
|
+
className: cn("z-50 flex w-72 origin-(--radix-popover-content-transform-origin) flex-col gap-2.5 rounded-lg bg-popover p-2.5 text-sm text-popover-foreground shadow-md ring-1 ring-foreground/10 outline-hidden 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),
|
|
1270
|
+
...props
|
|
1271
|
+
}) });
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
//#endregion
|
|
1275
|
+
//#region src/primitives/scroll-fade-area.tsx
|
|
1276
|
+
const MIN_THUMB_SIZE = 40;
|
|
1277
|
+
const SCROLLBAR_INSET = 6;
|
|
1278
|
+
function getScrollFadeMask({ canScrollEnd, canScrollStart, orientation }) {
|
|
1279
|
+
const direction = orientation === "horizontal" ? "right" : "bottom";
|
|
1280
|
+
if (canScrollStart && canScrollEnd) return `linear-gradient(to ${direction}, transparent, black var(--scroll-fade-size), black calc(100% - var(--scroll-fade-size)), transparent)`;
|
|
1281
|
+
if (canScrollStart) return `linear-gradient(to ${direction}, transparent, black var(--scroll-fade-size))`;
|
|
1282
|
+
if (canScrollEnd) return `linear-gradient(to ${direction}, black calc(100% - var(--scroll-fade-size)), transparent)`;
|
|
1283
|
+
return "none";
|
|
1284
|
+
}
|
|
1285
|
+
function getScrollState(scrollArea, orientation) {
|
|
1286
|
+
if (orientation === "horizontal") return {
|
|
1287
|
+
canScrollStart: scrollArea.scrollLeft > 1,
|
|
1288
|
+
canScrollEnd: scrollArea.scrollLeft + scrollArea.clientWidth < scrollArea.scrollWidth - 1
|
|
1289
|
+
};
|
|
1290
|
+
return {
|
|
1291
|
+
canScrollStart: scrollArea.scrollTop > 1,
|
|
1292
|
+
canScrollEnd: scrollArea.scrollTop + scrollArea.clientHeight < scrollArea.scrollHeight - 1
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
1295
|
+
function getVerticalScrollbarState(scrollArea) {
|
|
1296
|
+
const trackHeight = scrollArea.clientHeight - SCROLLBAR_INSET * 2;
|
|
1297
|
+
const maxScrollTop = scrollArea.scrollHeight - scrollArea.clientHeight;
|
|
1298
|
+
if (maxScrollTop <= 0 || trackHeight <= 0) return {
|
|
1299
|
+
isScrollable: false,
|
|
1300
|
+
thumbHeight: 0,
|
|
1301
|
+
thumbTop: 0
|
|
1302
|
+
};
|
|
1303
|
+
const thumbHeight = Math.max(scrollArea.clientHeight / scrollArea.scrollHeight * trackHeight, MIN_THUMB_SIZE);
|
|
1304
|
+
return {
|
|
1305
|
+
isScrollable: true,
|
|
1306
|
+
thumbHeight,
|
|
1307
|
+
thumbTop: (trackHeight - thumbHeight) * (scrollArea.scrollTop / maxScrollTop)
|
|
1308
|
+
};
|
|
1309
|
+
}
|
|
1310
|
+
function ScrollFadeArea({ children, className, fadeSize = 24, onPointerEnter, onPointerLeave, onScroll, orientation = "vertical", scrollbarVisibility = "hidden", style, viewportClassName, ...props }) {
|
|
1311
|
+
const scrollAreaRef = React.useRef(null);
|
|
1312
|
+
const dragOffsetRef = React.useRef(0);
|
|
1313
|
+
const [scrollState, setScrollState] = React.useState({
|
|
1314
|
+
canScrollStart: false,
|
|
1315
|
+
canScrollEnd: false
|
|
1316
|
+
});
|
|
1317
|
+
const [verticalScrollbarState, setVerticalScrollbarState] = React.useState({
|
|
1318
|
+
isScrollable: false,
|
|
1319
|
+
thumbHeight: 0,
|
|
1320
|
+
thumbTop: 0
|
|
1321
|
+
});
|
|
1322
|
+
const [isSectionActive, setIsSectionActive] = React.useState(false);
|
|
1323
|
+
const [isDraggingScrollbar, setIsDraggingScrollbar] = React.useState(false);
|
|
1324
|
+
const shouldRenderSectionScrollbar = orientation === "vertical" && scrollbarVisibility === "section-hover";
|
|
1325
|
+
const isSectionScrollbarVisible = isSectionActive || isDraggingScrollbar;
|
|
1326
|
+
const updateScrollState = React.useCallback(() => {
|
|
1327
|
+
const scrollArea = scrollAreaRef.current;
|
|
1328
|
+
if (!scrollArea) return;
|
|
1329
|
+
const nextScrollState = getScrollState(scrollArea, orientation);
|
|
1330
|
+
setScrollState((currentScrollState) => {
|
|
1331
|
+
if (currentScrollState.canScrollStart === nextScrollState.canScrollStart && currentScrollState.canScrollEnd === nextScrollState.canScrollEnd) return currentScrollState;
|
|
1332
|
+
return nextScrollState;
|
|
1333
|
+
});
|
|
1334
|
+
if (shouldRenderSectionScrollbar) setVerticalScrollbarState(getVerticalScrollbarState(scrollArea));
|
|
1335
|
+
}, [orientation, shouldRenderSectionScrollbar]);
|
|
1336
|
+
const scrollToThumbPosition = React.useCallback((thumbTop) => {
|
|
1337
|
+
const scrollArea = scrollAreaRef.current;
|
|
1338
|
+
if (!scrollArea) return;
|
|
1339
|
+
const maxThumbTop = scrollArea.clientHeight - SCROLLBAR_INSET * 2 - verticalScrollbarState.thumbHeight;
|
|
1340
|
+
scrollArea.scrollTop = (scrollArea.scrollHeight - scrollArea.clientHeight) * (maxThumbTop > 0 ? Math.min(Math.max(thumbTop, 0), maxThumbTop) / maxThumbTop : 0);
|
|
1341
|
+
updateScrollState();
|
|
1342
|
+
}, [updateScrollState, verticalScrollbarState.thumbHeight]);
|
|
1343
|
+
function handleTrackPointerDown(event) {
|
|
1344
|
+
if (event.target !== event.currentTarget) return;
|
|
1345
|
+
scrollToThumbPosition(event.nativeEvent.offsetY - verticalScrollbarState.thumbHeight / 2);
|
|
1346
|
+
}
|
|
1347
|
+
function handleThumbPointerDown(event) {
|
|
1348
|
+
event.preventDefault();
|
|
1349
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
1350
|
+
setIsDraggingScrollbar(true);
|
|
1351
|
+
dragOffsetRef.current = event.clientY - SCROLLBAR_INSET - verticalScrollbarState.thumbTop;
|
|
1352
|
+
}
|
|
1353
|
+
function handleThumbPointerMove(event) {
|
|
1354
|
+
if (!event.currentTarget.hasPointerCapture(event.pointerId)) return;
|
|
1355
|
+
scrollToThumbPosition(event.clientY - SCROLLBAR_INSET - dragOffsetRef.current);
|
|
1356
|
+
}
|
|
1357
|
+
function handleThumbPointerUp(event) {
|
|
1358
|
+
if (event.currentTarget.hasPointerCapture(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId);
|
|
1359
|
+
setIsDraggingScrollbar(false);
|
|
1360
|
+
}
|
|
1361
|
+
React.useEffect(() => {
|
|
1362
|
+
const scrollArea = scrollAreaRef.current;
|
|
1363
|
+
if (!scrollArea) return;
|
|
1364
|
+
updateScrollState();
|
|
1365
|
+
const resizeObserver = new ResizeObserver(updateScrollState);
|
|
1366
|
+
resizeObserver.observe(scrollArea);
|
|
1367
|
+
if (scrollArea.firstElementChild) resizeObserver.observe(scrollArea.firstElementChild);
|
|
1368
|
+
return () => {
|
|
1369
|
+
resizeObserver.disconnect();
|
|
1370
|
+
};
|
|
1371
|
+
}, [children, updateScrollState]);
|
|
1372
|
+
const maskImage = getScrollFadeMask({
|
|
1373
|
+
canScrollEnd: scrollState.canScrollEnd,
|
|
1374
|
+
canScrollStart: scrollState.canScrollStart,
|
|
1375
|
+
orientation
|
|
1376
|
+
});
|
|
1377
|
+
const maskStyle = {
|
|
1378
|
+
"--scroll-fade-size": `${String(fadeSize)}px`,
|
|
1379
|
+
WebkitMaskImage: maskImage,
|
|
1380
|
+
maskImage
|
|
1381
|
+
};
|
|
1382
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1383
|
+
className: cn("group/scroll-fade relative min-h-0 min-w-0", className),
|
|
1384
|
+
style,
|
|
1385
|
+
onPointerEnter: (event) => {
|
|
1386
|
+
setIsSectionActive(true);
|
|
1387
|
+
onPointerEnter?.(event);
|
|
1388
|
+
},
|
|
1389
|
+
onPointerLeave: (event) => {
|
|
1390
|
+
setIsSectionActive(false);
|
|
1391
|
+
onPointerLeave?.(event);
|
|
1392
|
+
},
|
|
1393
|
+
...props,
|
|
1394
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
1395
|
+
ref: scrollAreaRef,
|
|
1396
|
+
className: cn("min-h-0 min-w-0", orientation === "horizontal" ? "overflow-x-auto" : "h-full overflow-y-auto", "[-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden", viewportClassName),
|
|
1397
|
+
style: maskStyle,
|
|
1398
|
+
onScroll: (event) => {
|
|
1399
|
+
updateScrollState();
|
|
1400
|
+
onScroll?.(event);
|
|
1401
|
+
},
|
|
1402
|
+
children
|
|
1403
|
+
}), shouldRenderSectionScrollbar && verticalScrollbarState.isScrollable ? /* @__PURE__ */ jsx("div", {
|
|
1404
|
+
"aria-hidden": "true",
|
|
1405
|
+
className: cn("absolute top-1.5 right-0 bottom-1.5 z-10 w-3 transition-opacity duration-150", isSectionScrollbarVisible ? "pointer-events-auto opacity-100" : "pointer-events-none opacity-0"),
|
|
1406
|
+
"data-section-scrollbar": "true",
|
|
1407
|
+
onPointerDown: handleTrackPointerDown,
|
|
1408
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
1409
|
+
className: "absolute right-1 w-1.5 rounded-full bg-black/15 transition-colors hover:bg-black/25",
|
|
1410
|
+
onPointerDown: handleThumbPointerDown,
|
|
1411
|
+
onPointerMove: handleThumbPointerMove,
|
|
1412
|
+
onPointerUp: handleThumbPointerUp,
|
|
1413
|
+
onPointerCancel: handleThumbPointerUp,
|
|
1414
|
+
style: {
|
|
1415
|
+
height: `${String(verticalScrollbarState.thumbHeight)}px`,
|
|
1416
|
+
transform: `translateY(${String(verticalScrollbarState.thumbTop)}px)`
|
|
1417
|
+
}
|
|
1418
|
+
})
|
|
1419
|
+
}) : null]
|
|
1420
|
+
});
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
//#endregion
|
|
1424
|
+
//#region src/primitives/tabs.tsx
|
|
1425
|
+
function Tabs({ className, orientation = "horizontal", ...props }) {
|
|
1426
|
+
return /* @__PURE__ */ jsx(Tabs$1.Root, {
|
|
1427
|
+
"data-slot": "tabs",
|
|
1428
|
+
"data-orientation": orientation,
|
|
1429
|
+
className: cn("group/tabs flex gap-2 data-horizontal:flex-col", className),
|
|
1430
|
+
...props
|
|
1431
|
+
});
|
|
1432
|
+
}
|
|
1433
|
+
const tabsListVariants = cva("gap-2 rounded-lg p-[3px] group-data-horizontal/tabs:h-8 data-[variant=line]:rounded-none group/tabs-list text-muted-foreground inline-flex w-fit items-center justify-center group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col", {
|
|
1434
|
+
variants: { variant: {
|
|
1435
|
+
default: "bg-muted",
|
|
1436
|
+
line: "gap-2 bg-transparent"
|
|
1437
|
+
} },
|
|
1438
|
+
defaultVariants: { variant: "default" }
|
|
1439
|
+
});
|
|
1440
|
+
function TabsList({ className, variant = "default", ...props }) {
|
|
1441
|
+
return /* @__PURE__ */ jsx(Tabs$1.List, {
|
|
1442
|
+
"data-slot": "tabs-list",
|
|
1443
|
+
"data-variant": variant,
|
|
1444
|
+
className: cn(tabsListVariants({ variant }), className),
|
|
1445
|
+
...props
|
|
1446
|
+
});
|
|
1447
|
+
}
|
|
1448
|
+
function TabsScrollArea({ className, viewportClassName, ...props }) {
|
|
1449
|
+
return /* @__PURE__ */ jsx(ScrollFadeArea, {
|
|
1450
|
+
...props,
|
|
1451
|
+
"data-slot": "tabs-scroll-area",
|
|
1452
|
+
orientation: "horizontal",
|
|
1453
|
+
className: cn("w-full min-w-0", className),
|
|
1454
|
+
viewportClassName: cn("min-w-0", viewportClassName)
|
|
1455
|
+
});
|
|
1456
|
+
}
|
|
1457
|
+
function TabsTrigger({ className, ...props }) {
|
|
1458
|
+
return /* @__PURE__ */ jsx(Tabs$1.Trigger, {
|
|
1459
|
+
"data-slot": "tabs-trigger",
|
|
1460
|
+
className: cn("focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground/60 hover:text-foreground dark:text-muted-foreground dark:hover:text-foreground relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-1.5 py-0.5 text-sm font-medium whitespace-nowrap transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent", "data-active:bg-background dark:data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 data-active:text-foreground", "after:bg-foreground after:absolute after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100", className),
|
|
1461
|
+
...props
|
|
1462
|
+
});
|
|
1463
|
+
}
|
|
1464
|
+
function TabsContent({ className, ...props }) {
|
|
1465
|
+
return /* @__PURE__ */ jsx(Tabs$1.Content, {
|
|
1466
|
+
"data-slot": "tabs-content",
|
|
1467
|
+
className: cn("flex-1 text-sm outline-none", className),
|
|
1468
|
+
...props
|
|
1469
|
+
});
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
//#endregion
|
|
1473
|
+
//#region src/primitives/segmented-toggle.tsx
|
|
1474
|
+
function SegmentedToggle({ value, onChange, options, disabled = false, className, variant = "default", scrollable = false }) {
|
|
1475
|
+
const isPrimaryVariant = variant === "primary";
|
|
1476
|
+
return /* @__PURE__ */ jsx(Tabs, {
|
|
1477
|
+
value,
|
|
1478
|
+
onValueChange: (nextValue) => {
|
|
1479
|
+
if (nextValue) onChange(nextValue);
|
|
1480
|
+
},
|
|
1481
|
+
className: cn("w-full", disabled && "opacity-50", className),
|
|
1482
|
+
children: scrollable ? /* @__PURE__ */ jsx(TabsScrollArea, { children: /* @__PURE__ */ jsx(TabsList, {
|
|
1483
|
+
className: cn("w-full min-w-max", isPrimaryVariant && "border-border bg-background h-8 gap-0.5 rounded-lg border p-0.5 shadow-none"),
|
|
1484
|
+
children: options.map((option) => /* @__PURE__ */ jsxs(TabsTrigger, {
|
|
1485
|
+
value: option.value,
|
|
1486
|
+
disabled,
|
|
1487
|
+
className: cn("shrink-0", isPrimaryVariant && [
|
|
1488
|
+
"text-foreground h-full gap-1.5 rounded-md px-2 py-0 text-[13px] leading-5 font-medium tracking-[-0.28px] shadow-none",
|
|
1489
|
+
"hover:text-foreground",
|
|
1490
|
+
"data-active:border-border data-active:bg-accent data-active:text-accent-foreground data-active:shadow-none",
|
|
1491
|
+
"[&_svg:not([class*=size-])]:size-4"
|
|
1492
|
+
]),
|
|
1493
|
+
children: [option.icon, option.label]
|
|
1494
|
+
}, option.value))
|
|
1495
|
+
}) }) : /* @__PURE__ */ jsx(TabsList, {
|
|
1496
|
+
className: cn("w-full", isPrimaryVariant && "border-border bg-background h-8 gap-0.5 rounded-lg border p-0.5 shadow-none"),
|
|
1497
|
+
children: options.map((option) => /* @__PURE__ */ jsxs(TabsTrigger, {
|
|
1498
|
+
value: option.value,
|
|
1499
|
+
disabled,
|
|
1500
|
+
className: cn("flex-1", isPrimaryVariant && [
|
|
1501
|
+
"text-foreground h-full gap-1.5 rounded-md px-2 py-0 text-[13px] leading-5 font-medium tracking-[-0.28px] shadow-none",
|
|
1502
|
+
"hover:text-foreground",
|
|
1503
|
+
"data-active:border-border data-active:bg-accent data-active:text-accent-foreground data-active:shadow-none",
|
|
1504
|
+
"[&_svg:not([class*=size-])]:size-4"
|
|
1505
|
+
]),
|
|
1506
|
+
children: [option.icon, option.label]
|
|
1507
|
+
}, option.value))
|
|
1508
|
+
})
|
|
1509
|
+
});
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
//#endregion
|
|
1513
|
+
//#region src/primitives/select.tsx
|
|
1514
|
+
function Select({ ...props }) {
|
|
1515
|
+
return /* @__PURE__ */ jsx(Select$1.Root, {
|
|
1516
|
+
"data-slot": "select",
|
|
1517
|
+
...props
|
|
1518
|
+
});
|
|
1519
|
+
}
|
|
1520
|
+
function SelectValue({ ...props }) {
|
|
1521
|
+
return /* @__PURE__ */ jsx(Select$1.Value, {
|
|
1522
|
+
"data-slot": "select-value",
|
|
1523
|
+
...props
|
|
1524
|
+
});
|
|
1525
|
+
}
|
|
1526
|
+
function SelectTrigger({ className, size = "default", children, ...props }) {
|
|
1527
|
+
return /* @__PURE__ */ jsxs(Select$1.Trigger, {
|
|
1528
|
+
"data-slot": "select-trigger",
|
|
1529
|
+
"data-size": size,
|
|
1530
|
+
className: cn("border-input data-placeholder:text-muted-foreground dark:bg-input/30 dark:hover:bg-input/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 gap-1.5 rounded-lg border bg-transparent py-1 pr-2 pl-2 text-sm transition-colors select-none focus-visible:ring-3 aria-invalid:ring-3 data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:gap-1.5 [&_svg:not([class*='size-'])]:size-4 flex w-fit items-center justify-between whitespace-nowrap outline-none disabled:cursor-not-allowed disabled:opacity-50 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center [&_svg]:pointer-events-none [&_svg]:shrink-0", className),
|
|
1531
|
+
...props,
|
|
1532
|
+
children: [children, /* @__PURE__ */ jsx(Select$1.Icon, {
|
|
1533
|
+
asChild: true,
|
|
1534
|
+
children: /* @__PURE__ */ jsx(ChevronsUpDown, { className: "text-muted-foreground size-4 pointer-events-none" })
|
|
1535
|
+
})]
|
|
1536
|
+
});
|
|
1537
|
+
}
|
|
1538
|
+
function SelectContent({ className, children, position = "item-aligned", align = "center", ...props }) {
|
|
1539
|
+
return /* @__PURE__ */ jsx(Select$1.Portal, { children: /* @__PURE__ */ jsxs(Select$1.Content, {
|
|
1540
|
+
"data-slot": "select-content",
|
|
1541
|
+
"data-align-trigger": position === "item-aligned",
|
|
1542
|
+
className: cn("bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 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 ring-foreground/10 min-w-[250px] rounded-lg shadow-md ring-1 duration-100 relative z-50 max-h-(--radix-select-content-available-height) origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto data-[align-trigger=true]:animate-none", position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", className),
|
|
1543
|
+
position,
|
|
1544
|
+
align,
|
|
1545
|
+
...props,
|
|
1546
|
+
children: [
|
|
1547
|
+
/* @__PURE__ */ jsx(SelectScrollUpButton, {}),
|
|
1548
|
+
/* @__PURE__ */ jsx(Select$1.Viewport, {
|
|
1549
|
+
"data-position": position,
|
|
1550
|
+
className: cn("data-[position=popper]:h-(--radix-select-trigger-height) data-[position=popper]:w-full data-[position=popper]:min-w-(--radix-select-trigger-width)", position === "popper" && ""),
|
|
1551
|
+
children
|
|
1552
|
+
}),
|
|
1553
|
+
/* @__PURE__ */ jsx(SelectScrollDownButton, {})
|
|
1554
|
+
]
|
|
1555
|
+
}) });
|
|
1556
|
+
}
|
|
1557
|
+
function SelectItem({ className, children, ...props }) {
|
|
1558
|
+
return /* @__PURE__ */ jsxs(Select$1.Item, {
|
|
1559
|
+
"data-slot": "select-item",
|
|
1560
|
+
className: cn("focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2 relative flex w-full cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0", className),
|
|
1561
|
+
...props,
|
|
1562
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1563
|
+
className: "pointer-events-none absolute right-2 flex size-4 items-center justify-center",
|
|
1564
|
+
children: /* @__PURE__ */ jsx(Select$1.ItemIndicator, { children: /* @__PURE__ */ jsx(Check, { className: "pointer-events-none size-4" }) })
|
|
1565
|
+
}), /* @__PURE__ */ jsx(Select$1.ItemText, { children })]
|
|
1566
|
+
});
|
|
1567
|
+
}
|
|
1568
|
+
function SelectScrollUpButton({ className, ...props }) {
|
|
1569
|
+
return /* @__PURE__ */ jsx(Select$1.ScrollUpButton, {
|
|
1570
|
+
"data-slot": "select-scroll-up-button",
|
|
1571
|
+
className: cn("bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-4", className),
|
|
1572
|
+
...props,
|
|
1573
|
+
children: /* @__PURE__ */ jsx(ChevronUp, {})
|
|
1574
|
+
});
|
|
1575
|
+
}
|
|
1576
|
+
function SelectScrollDownButton({ className, ...props }) {
|
|
1577
|
+
return /* @__PURE__ */ jsx(Select$1.ScrollDownButton, {
|
|
1578
|
+
"data-slot": "select-scroll-down-button",
|
|
1579
|
+
className: cn("bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-4", className),
|
|
1580
|
+
...props,
|
|
1581
|
+
children: /* @__PURE__ */ jsx(ChevronDown, {})
|
|
1582
|
+
});
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
//#endregion
|
|
1586
|
+
//#region src/primitives/skeleton.tsx
|
|
1587
|
+
function Skeleton({ className, ...props }) {
|
|
1588
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1589
|
+
"data-slot": "skeleton",
|
|
1590
|
+
className: cn("bg-muted rounded-md animate-pulse", className),
|
|
1591
|
+
...props
|
|
1592
|
+
});
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
//#endregion
|
|
1596
|
+
//#region src/primitives/switch.tsx
|
|
1597
|
+
function Switch({ className, ...props }) {
|
|
1598
|
+
return /* @__PURE__ */ jsx(Switch$1.Root, {
|
|
1599
|
+
"data-slot": "switch",
|
|
1600
|
+
className: cn("peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input inline-flex h-6 w-11 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50", className),
|
|
1601
|
+
...props,
|
|
1602
|
+
children: /* @__PURE__ */ jsx(Switch$1.Thumb, {
|
|
1603
|
+
"data-slot": "switch-thumb",
|
|
1604
|
+
className: cn("bg-background pointer-events-none block size-5 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[22px] data-[state=unchecked]:translate-x-0.5")
|
|
1605
|
+
})
|
|
1606
|
+
});
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
//#endregion
|
|
1610
|
+
//#region src/primitives/table.tsx
|
|
1611
|
+
function Table({ className, ...props }) {
|
|
1612
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1613
|
+
"data-slot": "table-container",
|
|
1614
|
+
className: "relative w-full min-w-0 overflow-x-auto",
|
|
1615
|
+
children: /* @__PURE__ */ jsx("table", {
|
|
1616
|
+
"data-slot": "table",
|
|
1617
|
+
className: cn("w-full min-w-0 caption-bottom text-sm", className),
|
|
1618
|
+
...props
|
|
1619
|
+
})
|
|
1620
|
+
});
|
|
1621
|
+
}
|
|
1622
|
+
function TableHeader({ className, ...props }) {
|
|
1623
|
+
return /* @__PURE__ */ jsx("thead", {
|
|
1624
|
+
"data-slot": "table-header",
|
|
1625
|
+
className: cn("[&_tr]:border-b", className),
|
|
1626
|
+
...props
|
|
1627
|
+
});
|
|
1628
|
+
}
|
|
1629
|
+
function TableBody({ className, ...props }) {
|
|
1630
|
+
return /* @__PURE__ */ jsx("tbody", {
|
|
1631
|
+
"data-slot": "table-body",
|
|
1632
|
+
className: cn("[&_tr:last-child]:border-0", className),
|
|
1633
|
+
...props
|
|
1634
|
+
});
|
|
1635
|
+
}
|
|
1636
|
+
function TableFooter({ className, ...props }) {
|
|
1637
|
+
return /* @__PURE__ */ jsx("tfoot", {
|
|
1638
|
+
"data-slot": "table-footer",
|
|
1639
|
+
className: cn("bg-muted/50 border-t font-medium [&>tr]:last:border-b-0", className),
|
|
1640
|
+
...props
|
|
1641
|
+
});
|
|
1642
|
+
}
|
|
1643
|
+
function TableRow({ className, ...props }) {
|
|
1644
|
+
return /* @__PURE__ */ jsx("tr", {
|
|
1645
|
+
"data-slot": "table-row",
|
|
1646
|
+
className: cn("hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors", className),
|
|
1647
|
+
...props
|
|
1648
|
+
});
|
|
1649
|
+
}
|
|
1650
|
+
function TableHead({ className, ...props }) {
|
|
1651
|
+
return /* @__PURE__ */ jsx("th", {
|
|
1652
|
+
"data-slot": "table-head",
|
|
1653
|
+
className: cn("text-foreground h-10 overflow-hidden px-2 text-left align-middle font-medium text-ellipsis whitespace-nowrap [&:has([role=checkbox])]:pr-0", className),
|
|
1654
|
+
...props
|
|
1655
|
+
});
|
|
1656
|
+
}
|
|
1657
|
+
function TableCell({ className, ...props }) {
|
|
1658
|
+
return /* @__PURE__ */ jsx("td", {
|
|
1659
|
+
"data-slot": "table-cell",
|
|
1660
|
+
className: cn("overflow-hidden p-2 align-middle text-ellipsis whitespace-nowrap [&:has([role=checkbox])]:pr-0", className),
|
|
1661
|
+
...props
|
|
1662
|
+
});
|
|
1663
|
+
}
|
|
1664
|
+
function TableCaption({ className, ...props }) {
|
|
1665
|
+
return /* @__PURE__ */ jsx("caption", {
|
|
1666
|
+
"data-slot": "table-caption",
|
|
1667
|
+
className: cn("text-muted-foreground mt-4 text-sm", className),
|
|
1668
|
+
...props
|
|
1669
|
+
});
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
//#endregion
|
|
1673
|
+
//#region src/primitives/textarea.tsx
|
|
1674
|
+
function Textarea({ className, ...props }) {
|
|
1675
|
+
return /* @__PURE__ */ jsx("textarea", {
|
|
1676
|
+
"data-slot": "textarea",
|
|
1677
|
+
className: cn("dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 disabled:bg-input/50 dark:disabled:bg-input/80 min-h-24 rounded-lg border bg-transparent px-3 py-2 text-base transition-colors placeholder:text-muted-foreground w-full min-w-0 resize-y outline-none focus-visible:ring-3 aria-invalid:ring-3 md:text-sm disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50", className),
|
|
1678
|
+
...props
|
|
1679
|
+
});
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
//#endregion
|
|
1683
|
+
//#region src/primitives/user-picker-utils.ts
|
|
1684
|
+
function formatUserDisplayName(user, fallbackName = "User") {
|
|
1685
|
+
const label = user.label?.trim();
|
|
1686
|
+
if (label) return label;
|
|
1687
|
+
const email = user.email?.trim();
|
|
1688
|
+
if (email) return email;
|
|
1689
|
+
return fallbackName;
|
|
1690
|
+
}
|
|
1691
|
+
function formatSelectedUserSummary(selectedUsers, copy = {}) {
|
|
1692
|
+
const selectedNames = selectedUsers.slice(0, 2).map((user) => formatUserDisplayName(user, copy.fallbackName));
|
|
1693
|
+
const remainingCount = selectedUsers.length - selectedNames.length;
|
|
1694
|
+
if (remainingCount <= 0) return selectedNames.join(", ");
|
|
1695
|
+
const remainingLabel = remainingCount === 1 ? copy.selectedSingleLabel ?? "person" : copy.selectedPluralLabel ?? "people";
|
|
1696
|
+
return `${selectedNames.join(", ")} +${String(remainingCount)} ${remainingLabel}`;
|
|
1697
|
+
}
|
|
1698
|
+
function buildUserInitials(user, fallbackName) {
|
|
1699
|
+
const displayName = formatUserDisplayName(user, fallbackName);
|
|
1700
|
+
const nameParts = displayName.split(/\s+/).filter(Boolean).slice(0, 2);
|
|
1701
|
+
if (nameParts.length > 1) return nameParts.map((part) => part[0]?.toUpperCase()).join("");
|
|
1702
|
+
return displayName[0]?.toUpperCase() ?? "U";
|
|
1703
|
+
}
|
|
1704
|
+
function filterSelectableUsers(users, selectedValues) {
|
|
1705
|
+
const selectedValueSet = new Set(selectedValues);
|
|
1706
|
+
return users.filter((user) => !user.archived || selectedValueSet.has(user.value));
|
|
1707
|
+
}
|
|
1708
|
+
function filterUsersBySearch(users, search, fallbackName) {
|
|
1709
|
+
const normalizedSearch = search.trim().toLowerCase();
|
|
1710
|
+
if (!normalizedSearch) return [...users];
|
|
1711
|
+
return users.filter((user) => {
|
|
1712
|
+
return [
|
|
1713
|
+
formatUserDisplayName(user, fallbackName),
|
|
1714
|
+
user.email,
|
|
1715
|
+
user.searchText
|
|
1716
|
+
].filter(Boolean).join(" ").toLowerCase().includes(normalizedSearch);
|
|
1717
|
+
});
|
|
1718
|
+
}
|
|
1719
|
+
function toggleUserSelection(selectedValues, toggledValue) {
|
|
1720
|
+
if (selectedValues.includes(toggledValue)) return selectedValues.filter((value) => value !== toggledValue);
|
|
1721
|
+
return [...selectedValues, toggledValue];
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
//#endregion
|
|
1725
|
+
//#region src/primitives/user-picker.tsx
|
|
1726
|
+
function UserAvatar({ user, className, fallbackName }) {
|
|
1727
|
+
return /* @__PURE__ */ jsxs(Avatar, {
|
|
1728
|
+
size: "sm",
|
|
1729
|
+
className,
|
|
1730
|
+
children: [user.imageUrl ? /* @__PURE__ */ jsx(AvatarImage, {
|
|
1731
|
+
src: user.imageUrl,
|
|
1732
|
+
alt: formatUserDisplayName(user, fallbackName)
|
|
1733
|
+
}) : null, /* @__PURE__ */ jsx(AvatarFallback, { children: buildUserInitials(user, fallbackName) })]
|
|
1734
|
+
});
|
|
1735
|
+
}
|
|
1736
|
+
function UserRow({ user, fallbackName }) {
|
|
1737
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
1738
|
+
className: "flex min-w-0 items-center gap-2",
|
|
1739
|
+
children: [/* @__PURE__ */ jsx(UserAvatar, {
|
|
1740
|
+
user,
|
|
1741
|
+
fallbackName
|
|
1742
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
1743
|
+
className: "min-w-0",
|
|
1744
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1745
|
+
className: "block truncate font-medium",
|
|
1746
|
+
children: formatUserDisplayName(user, fallbackName)
|
|
1747
|
+
}), user.email ? /* @__PURE__ */ jsx("span", {
|
|
1748
|
+
className: "block truncate text-xs text-muted-foreground",
|
|
1749
|
+
children: user.email
|
|
1750
|
+
}) : null]
|
|
1751
|
+
})]
|
|
1752
|
+
});
|
|
1753
|
+
}
|
|
1754
|
+
function MultipleUserValue({ selectedUsers, placeholder, copy }) {
|
|
1755
|
+
if (selectedUsers.length === 0) return /* @__PURE__ */ jsx("span", {
|
|
1756
|
+
className: "text-muted-foreground",
|
|
1757
|
+
children: placeholder
|
|
1758
|
+
});
|
|
1759
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
1760
|
+
className: "flex min-w-0 items-center gap-2",
|
|
1761
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1762
|
+
className: "flex -space-x-2",
|
|
1763
|
+
children: selectedUsers.slice(0, 3).map((user) => /* @__PURE__ */ jsx(UserAvatar, {
|
|
1764
|
+
user,
|
|
1765
|
+
className: "ring-2 ring-background",
|
|
1766
|
+
fallbackName: copy.fallbackName
|
|
1767
|
+
}, user.value))
|
|
1768
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
1769
|
+
className: "truncate",
|
|
1770
|
+
children: copy.formatSelectedCount ? copy.formatSelectedCount(selectedUsers.length) : formatSelectedUserSummary(selectedUsers, copy)
|
|
1771
|
+
})]
|
|
1772
|
+
});
|
|
1773
|
+
}
|
|
1774
|
+
function UserPicker(props) {
|
|
1775
|
+
const { options, copy = {}, placeholder = props.mode === "single" ? copy.singlePlaceholder ?? "Select person" : copy.multiplePlaceholder ?? "Select people", searchPlaceholder = copy.searchPlaceholder ?? "Search people...", emptyMessage = copy.emptyMessage ?? "No people available", disabled = false, className, triggerClassName } = props;
|
|
1776
|
+
const [open, setOpen] = useState(false);
|
|
1777
|
+
const [search, setSearch] = useState("");
|
|
1778
|
+
const singleValue = props.mode === "single" ? props.value : void 0;
|
|
1779
|
+
const multipleValue = props.mode === "multiple" ? props.value : void 0;
|
|
1780
|
+
const selectedValues = useMemo(() => props.mode === "single" ? singleValue ? [singleValue] : [] : [...multipleValue ?? []], [
|
|
1781
|
+
multipleValue,
|
|
1782
|
+
props.mode,
|
|
1783
|
+
singleValue
|
|
1784
|
+
]);
|
|
1785
|
+
const selectedValueSet = useMemo(() => new Set(selectedValues), [selectedValues]);
|
|
1786
|
+
const selectableUsers = useMemo(() => filterSelectableUsers(options, selectedValues), [options, selectedValues]);
|
|
1787
|
+
const filteredUsers = useMemo(() => filterUsersBySearch(selectableUsers, search, copy.fallbackName), [
|
|
1788
|
+
copy.fallbackName,
|
|
1789
|
+
search,
|
|
1790
|
+
selectableUsers
|
|
1791
|
+
]);
|
|
1792
|
+
const selectedUsers = selectedValues.map((selectedValue) => options.find((option) => option.value === selectedValue)).filter((user) => Boolean(user));
|
|
1793
|
+
const selectedUser = selectedUsers[0];
|
|
1794
|
+
function selectUser(userValue) {
|
|
1795
|
+
if (props.mode === "single") {
|
|
1796
|
+
props.onValueChange(userValue);
|
|
1797
|
+
setOpen(false);
|
|
1798
|
+
return;
|
|
1799
|
+
}
|
|
1800
|
+
props.onValueChange(toggleUserSelection(props.value, userValue));
|
|
1801
|
+
}
|
|
1802
|
+
return /* @__PURE__ */ jsx(Popover, {
|
|
1803
|
+
open,
|
|
1804
|
+
onOpenChange: setOpen,
|
|
1805
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1806
|
+
className: cn("relative", className),
|
|
1807
|
+
children: [/* @__PURE__ */ jsx(PopoverTrigger, {
|
|
1808
|
+
asChild: true,
|
|
1809
|
+
children: /* @__PURE__ */ jsxs(Button, {
|
|
1810
|
+
type: "button",
|
|
1811
|
+
variant: "outline",
|
|
1812
|
+
disabled,
|
|
1813
|
+
className: cn("h-9 w-full justify-between gap-2 px-2 text-left font-normal", selectedValues.length === 0 && "text-muted-foreground", triggerClassName),
|
|
1814
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1815
|
+
className: "min-w-0 flex-1",
|
|
1816
|
+
children: props.mode === "single" ? selectedUser ? /* @__PURE__ */ jsx(UserRow, {
|
|
1817
|
+
user: selectedUser,
|
|
1818
|
+
fallbackName: copy.fallbackName
|
|
1819
|
+
}) : /* @__PURE__ */ jsx("span", { children: placeholder }) : /* @__PURE__ */ jsx(MultipleUserValue, {
|
|
1820
|
+
selectedUsers,
|
|
1821
|
+
placeholder,
|
|
1822
|
+
copy
|
|
1823
|
+
})
|
|
1824
|
+
}), /* @__PURE__ */ jsx(ChevronDown, { className: "size-4 shrink-0 text-muted-foreground" })]
|
|
1825
|
+
})
|
|
1826
|
+
}), /* @__PURE__ */ jsxs(PopoverContent, {
|
|
1827
|
+
align: "start",
|
|
1828
|
+
"data-searchable-select-content": "",
|
|
1829
|
+
className: "w-[var(--radix-popover-trigger-width)] min-w-72 p-2",
|
|
1830
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
1831
|
+
className: "relative",
|
|
1832
|
+
children: [/* @__PURE__ */ jsx(Search, { className: "absolute left-2.5 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" }), /* @__PURE__ */ jsx(Input, {
|
|
1833
|
+
value: search,
|
|
1834
|
+
onChange: (event) => {
|
|
1835
|
+
setSearch(event.target.value);
|
|
1836
|
+
},
|
|
1837
|
+
placeholder: searchPlaceholder,
|
|
1838
|
+
className: "h-8 pl-8"
|
|
1839
|
+
})]
|
|
1840
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
1841
|
+
className: "max-h-72 space-y-1 overflow-y-auto",
|
|
1842
|
+
children: [filteredUsers.map((user) => {
|
|
1843
|
+
const selected = selectedValueSet.has(user.value);
|
|
1844
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
1845
|
+
type: "button",
|
|
1846
|
+
className: cn("flex w-full items-center justify-between gap-3 rounded-md px-2 py-2 text-left text-sm transition-colors hover:bg-accent hover:text-accent-foreground", selected && "bg-muted/70"),
|
|
1847
|
+
onClick: () => {
|
|
1848
|
+
selectUser(user.value);
|
|
1849
|
+
},
|
|
1850
|
+
children: [/* @__PURE__ */ jsx(UserRow, {
|
|
1851
|
+
user,
|
|
1852
|
+
fallbackName: copy.fallbackName
|
|
1853
|
+
}), selected ? /* @__PURE__ */ jsx(Check, { className: "size-4 shrink-0" }) : null]
|
|
1854
|
+
}, user.value);
|
|
1855
|
+
}), filteredUsers.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
1856
|
+
className: "px-2 py-4 text-sm text-muted-foreground",
|
|
1857
|
+
children: emptyMessage
|
|
1858
|
+
}) : null]
|
|
1859
|
+
})]
|
|
1860
|
+
})]
|
|
1861
|
+
})
|
|
1862
|
+
});
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
//#endregion
|
|
1866
|
+
//#region src/empty-state/empty-state-card.tsx
|
|
1867
|
+
function EmptyStateCard({ icon, title, subtitle, actionLabel, onAction, actionHref, actionTarget = "_self", actionRel, actionDisabled = false, actionIcon, className }) {
|
|
1868
|
+
const hasLinkAction = actionLabel !== void 0 && actionHref !== void 0;
|
|
1869
|
+
const hasButtonAction = actionLabel !== void 0 && onAction !== void 0;
|
|
1870
|
+
const hasDisabledAction = actionLabel !== void 0 && actionDisabled;
|
|
1871
|
+
const hasAction = hasLinkAction || hasButtonAction || hasDisabledAction;
|
|
1872
|
+
return /* @__PURE__ */ jsx(Card, {
|
|
1873
|
+
className: cn("w-full border border-dashed border-border shadow-none ring-0", className),
|
|
1874
|
+
children: /* @__PURE__ */ jsxs(CardContent, {
|
|
1875
|
+
className: "flex flex-col items-center justify-start px-6 py-10 text-center",
|
|
1876
|
+
children: [
|
|
1877
|
+
/* @__PURE__ */ jsx("div", {
|
|
1878
|
+
className: "mb-4 flex size-14 items-center justify-center rounded-full bg-muted text-muted-foreground",
|
|
1879
|
+
children: icon
|
|
1880
|
+
}),
|
|
1881
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1882
|
+
className: "space-y-1.5",
|
|
1883
|
+
children: [/* @__PURE__ */ jsx("h3", {
|
|
1884
|
+
className: "text-lg font-medium tracking-tight",
|
|
1885
|
+
children: title
|
|
1886
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
1887
|
+
className: "max-w-2xl text-sm text-muted-foreground",
|
|
1888
|
+
children: subtitle
|
|
1889
|
+
})]
|
|
1890
|
+
}),
|
|
1891
|
+
hasAction ? hasLinkAction ? /* @__PURE__ */ jsx(Button, {
|
|
1892
|
+
asChild: true,
|
|
1893
|
+
className: "mt-5",
|
|
1894
|
+
disabled: actionDisabled,
|
|
1895
|
+
children: /* @__PURE__ */ jsxs("a", {
|
|
1896
|
+
href: actionHref,
|
|
1897
|
+
target: actionTarget,
|
|
1898
|
+
rel: actionRel,
|
|
1899
|
+
children: [actionIcon ? /* @__PURE__ */ jsx("span", {
|
|
1900
|
+
className: "mr-2 inline-flex items-center",
|
|
1901
|
+
children: actionIcon
|
|
1902
|
+
}) : null, actionLabel]
|
|
1903
|
+
})
|
|
1904
|
+
}) : hasDisabledAction ? /* @__PURE__ */ jsxs(Button, {
|
|
1905
|
+
className: "mt-5",
|
|
1906
|
+
disabled: true,
|
|
1907
|
+
children: [actionIcon ? /* @__PURE__ */ jsx("span", {
|
|
1908
|
+
className: "mr-2 inline-flex items-center",
|
|
1909
|
+
children: actionIcon
|
|
1910
|
+
}) : null, actionLabel]
|
|
1911
|
+
}) : /* @__PURE__ */ jsxs(Button, {
|
|
1912
|
+
className: "mt-5",
|
|
1913
|
+
onClick: onAction,
|
|
1914
|
+
disabled: actionDisabled,
|
|
1915
|
+
children: [actionIcon ? /* @__PURE__ */ jsx("span", {
|
|
1916
|
+
className: "mr-2 inline-flex items-center",
|
|
1917
|
+
children: actionIcon
|
|
1918
|
+
}) : null, actionLabel]
|
|
1919
|
+
}) : null
|
|
1920
|
+
]
|
|
1921
|
+
})
|
|
1922
|
+
});
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
//#endregion
|
|
1926
|
+
//#region src/empty-state/initial-empty-state.tsx
|
|
1927
|
+
function InitialEmptyState({ icon = /* @__PURE__ */ jsx(Inbox, { className: "size-7" }), title, subtitle, actionLabel, onAction, actionIcon, className }) {
|
|
1928
|
+
return /* @__PURE__ */ jsx(EmptyStateCard, {
|
|
1929
|
+
icon,
|
|
1930
|
+
title,
|
|
1931
|
+
subtitle,
|
|
1932
|
+
actionLabel,
|
|
1933
|
+
onAction,
|
|
1934
|
+
actionIcon,
|
|
1935
|
+
className
|
|
1936
|
+
});
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
//#endregion
|
|
1940
|
+
//#region src/empty-state/no-results-state.tsx
|
|
1941
|
+
function NoResultsState({ icon = /* @__PURE__ */ jsx(SearchX, { className: "size-7" }), title = "No results", subtitle = "Try changing your search or filters.", className }) {
|
|
1942
|
+
return /* @__PURE__ */ jsx(EmptyStateCard, {
|
|
1943
|
+
icon,
|
|
1944
|
+
title,
|
|
1945
|
+
subtitle,
|
|
1946
|
+
className
|
|
1947
|
+
});
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
//#endregion
|
|
1951
|
+
//#region src/empty-state/collection-empty-state.ts
|
|
1952
|
+
function resolveCollectionEmptyState({ totalCount, filteredCount, hasSearch = false, hasFilters = false }) {
|
|
1953
|
+
if (filteredCount > 0) return "none";
|
|
1954
|
+
if (totalCount === 0) return "initial";
|
|
1955
|
+
if (hasSearch || hasFilters) return "no-results";
|
|
1956
|
+
return "initial";
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
//#endregion
|
|
1960
|
+
//#region src/utils/search.ts
|
|
1961
|
+
function normalizeSearchValue(value) {
|
|
1962
|
+
return value.normalize("NFD").replace(/\p{Diacritic}/gu, "").toLocaleLowerCase().replace(/[^\p{L}\p{N}\s]/gu, " ").replace(/\s+/g, " ").trim();
|
|
1963
|
+
}
|
|
1964
|
+
function tokenize(value) {
|
|
1965
|
+
return normalizeSearchValue(value).split(" ").filter((token) => token.length > 0);
|
|
1966
|
+
}
|
|
1967
|
+
function flattenSearchPart(part, fragments) {
|
|
1968
|
+
if (typeof part === "string") {
|
|
1969
|
+
const normalized = part.trim();
|
|
1970
|
+
if (normalized.length > 0) fragments.push(normalized);
|
|
1971
|
+
return;
|
|
1972
|
+
}
|
|
1973
|
+
if (!part) return;
|
|
1974
|
+
for (const nestedPart of part) flattenSearchPart(nestedPart, fragments);
|
|
1975
|
+
}
|
|
1976
|
+
function getMaxDistance(term) {
|
|
1977
|
+
if (term.length <= 4) return 1;
|
|
1978
|
+
if (term.length <= 8) return 2;
|
|
1979
|
+
return 3;
|
|
1980
|
+
}
|
|
1981
|
+
function damerauLevenshtein(left, right) {
|
|
1982
|
+
const rows = left.length + 1;
|
|
1983
|
+
const columns = right.length + 1;
|
|
1984
|
+
const matrix = Array.from({ length: rows }, () => Array(columns).fill(0));
|
|
1985
|
+
for (let row = 0; row < rows; row += 1) {
|
|
1986
|
+
const matrixRow = matrix[row];
|
|
1987
|
+
if (!matrixRow) continue;
|
|
1988
|
+
matrixRow[0] = row;
|
|
1989
|
+
}
|
|
1990
|
+
for (let column = 0; column < columns; column += 1) {
|
|
1991
|
+
const firstRow = matrix[0];
|
|
1992
|
+
if (!firstRow) return 0;
|
|
1993
|
+
firstRow[column] = column;
|
|
1994
|
+
}
|
|
1995
|
+
for (let row = 1; row < rows; row += 1) {
|
|
1996
|
+
const currentRow = matrix[row];
|
|
1997
|
+
const previousRow = matrix[row - 1];
|
|
1998
|
+
if (!currentRow || !previousRow) continue;
|
|
1999
|
+
for (let column = 1; column < columns; column += 1) {
|
|
2000
|
+
const substitutionCost = left[row - 1] === right[column - 1] ? 0 : 1;
|
|
2001
|
+
const leftCost = currentRow[column - 1];
|
|
2002
|
+
const topCost = previousRow[column];
|
|
2003
|
+
const diagonalCost = previousRow[column - 1];
|
|
2004
|
+
if (leftCost === void 0 || topCost === void 0 || diagonalCost === void 0) continue;
|
|
2005
|
+
currentRow[column] = Math.min(topCost + 1, leftCost + 1, diagonalCost + substitutionCost);
|
|
2006
|
+
if (row > 1 && column > 1 && left[row - 1] === right[column - 2] && left[row - 2] === right[column - 1]) {
|
|
2007
|
+
const transpositionCost = matrix[row - 2]?.[column - 2];
|
|
2008
|
+
if (transpositionCost !== void 0) currentRow[column] = Math.min(currentRow[column] ?? Number.POSITIVE_INFINITY, transpositionCost + 1);
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
return matrix[left.length]?.[right.length] ?? 0;
|
|
2013
|
+
}
|
|
2014
|
+
function scoreTokenMatch(searchTerm, candidateToken, isLastTerm) {
|
|
2015
|
+
if (candidateToken === searchTerm) return 120;
|
|
2016
|
+
if (isLastTerm && candidateToken.startsWith(searchTerm)) return 90 - Math.max(candidateToken.length - searchTerm.length, 0);
|
|
2017
|
+
const distance = damerauLevenshtein(searchTerm, candidateToken);
|
|
2018
|
+
if (distance > getMaxDistance(searchTerm)) return null;
|
|
2019
|
+
return 70 - distance * 10 - Math.abs(candidateToken.length - searchTerm.length);
|
|
2020
|
+
}
|
|
2021
|
+
function buildSearchText(...parts) {
|
|
2022
|
+
const fragments = [];
|
|
2023
|
+
for (const part of parts) flattenSearchPart(part, fragments);
|
|
2024
|
+
return fragments.join(" ").replace(/\s+/g, " ").trim();
|
|
2025
|
+
}
|
|
2026
|
+
function scoreFuzzyMatch(query, candidate) {
|
|
2027
|
+
const normalizedQuery = normalizeSearchValue(query);
|
|
2028
|
+
if (normalizedQuery.length === 0) return 0;
|
|
2029
|
+
const queryTerms = tokenize(normalizedQuery);
|
|
2030
|
+
const candidateTerms = tokenize(candidate);
|
|
2031
|
+
const normalizedCandidate = normalizeSearchValue(candidate);
|
|
2032
|
+
if (queryTerms.length === 0 || candidateTerms.length === 0) return null;
|
|
2033
|
+
let score = normalizedCandidate.includes(normalizedQuery) ? 40 : 0;
|
|
2034
|
+
for (const [index, queryTerm] of queryTerms.entries()) {
|
|
2035
|
+
let bestScore = null;
|
|
2036
|
+
for (const candidateTerm of candidateTerms) {
|
|
2037
|
+
const tokenScore = scoreTokenMatch(queryTerm, candidateTerm, index === queryTerms.length - 1);
|
|
2038
|
+
if (tokenScore !== null && (bestScore === null || tokenScore > bestScore)) bestScore = tokenScore;
|
|
2039
|
+
}
|
|
2040
|
+
if (bestScore === null) return null;
|
|
2041
|
+
score += bestScore;
|
|
2042
|
+
}
|
|
2043
|
+
return score;
|
|
2044
|
+
}
|
|
2045
|
+
function filterAndRankBySearch(items, query) {
|
|
2046
|
+
const normalizedQuery = normalizeSearchValue(query);
|
|
2047
|
+
if (normalizedQuery.length === 0) return [...items];
|
|
2048
|
+
return items.map((item) => ({
|
|
2049
|
+
item,
|
|
2050
|
+
score: scoreFuzzyMatch(normalizedQuery, item.searchText)
|
|
2051
|
+
})).filter((result) => result.score !== null).sort((left, right) => right.score - left.score).map((result) => result.item);
|
|
2052
|
+
}
|
|
2053
|
+
function rankBySearch(items, query, getSearchText) {
|
|
2054
|
+
return filterAndRankBySearch(items.map((item) => ({
|
|
2055
|
+
item,
|
|
2056
|
+
searchText: getSearchText(item)
|
|
2057
|
+
})), query).map((result) => result.item);
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
//#endregion
|
|
2061
|
+
//#region src/search/searchable-select-position.ts
|
|
2062
|
+
function clamp(value, min, max) {
|
|
2063
|
+
if (max < min) return min;
|
|
2064
|
+
return Math.min(Math.max(value, min), max);
|
|
2065
|
+
}
|
|
2066
|
+
function resolveSearchableSelectDropdownPosition({ triggerRect, boundaryRect, portalRect, contentWidth, contentHeight, viewportWidth, viewportHeight, offset = 8, padding = 8 }) {
|
|
2067
|
+
const boundaryLeftEdge = boundaryRect?.left ?? 0;
|
|
2068
|
+
const boundaryRightEdge = boundaryRect?.right ?? viewportWidth;
|
|
2069
|
+
const boundaryTopEdge = boundaryRect?.top ?? 0;
|
|
2070
|
+
const boundaryBottomEdge = boundaryRect?.bottom ?? viewportHeight;
|
|
2071
|
+
const boundaryLeft = Math.max(padding, boundaryLeftEdge + padding);
|
|
2072
|
+
const boundaryRight = Math.min(viewportWidth - padding, boundaryRightEdge - padding);
|
|
2073
|
+
const boundaryTop = Math.max(padding, boundaryTopEdge + padding);
|
|
2074
|
+
const boundaryBottom = Math.min(viewportHeight - padding, boundaryBottomEdge - padding);
|
|
2075
|
+
const availableWidth = Math.max(0, boundaryRight - boundaryLeft);
|
|
2076
|
+
const width = Math.min(contentWidth, availableWidth);
|
|
2077
|
+
const alignedRightLeft = triggerRect.right - width;
|
|
2078
|
+
const defaultLeft = triggerRect.left;
|
|
2079
|
+
const left = clamp(defaultLeft + width > boundaryRight && alignedRightLeft >= boundaryLeft ? alignedRightLeft : defaultLeft, boundaryLeft, boundaryRight - width);
|
|
2080
|
+
const spaceAbove = triggerRect.top - boundaryTop;
|
|
2081
|
+
const spaceBelow = boundaryBottom - triggerRect.bottom;
|
|
2082
|
+
const shouldOpenUp = spaceBelow < contentHeight + offset && spaceAbove > spaceBelow;
|
|
2083
|
+
const direction = shouldOpenUp ? "up" : "down";
|
|
2084
|
+
const maxHeight = Math.max(120, Math.floor((shouldOpenUp ? spaceAbove : spaceBelow) - offset));
|
|
2085
|
+
const renderedHeight = Math.min(contentHeight, maxHeight);
|
|
2086
|
+
return {
|
|
2087
|
+
top: (shouldOpenUp ? Math.max(boundaryTop, triggerRect.top - offset - renderedHeight) : Math.min(boundaryBottom - renderedHeight, triggerRect.bottom + offset)) - (portalRect?.top ?? 0),
|
|
2088
|
+
left: left - (portalRect?.left ?? 0),
|
|
2089
|
+
width,
|
|
2090
|
+
maxHeight,
|
|
2091
|
+
direction
|
|
2092
|
+
};
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
//#endregion
|
|
2096
|
+
//#region src/search/searchable-select.tsx
|
|
2097
|
+
const AUTO_SEARCHABLE_SELECT_THRESHOLD = 5;
|
|
2098
|
+
const SEARCHABLE_SELECT_MODAL_CONTENT_SELECTOR = "[data-slot=\"sheet-content\"], [data-slot=\"drawer-content\"]";
|
|
2099
|
+
function getSearchableSelectBoundaryRect(element) {
|
|
2100
|
+
return element.closest(SEARCHABLE_SELECT_MODAL_CONTENT_SELECTOR)?.getBoundingClientRect();
|
|
2101
|
+
}
|
|
2102
|
+
function isSearchableSelectPointerInside(target, triggerContainer, dropdownContent) {
|
|
2103
|
+
return Boolean(triggerContainer?.contains(target) || dropdownContent?.contains(target));
|
|
2104
|
+
}
|
|
2105
|
+
function getSearchableSelectPortalContainer(trigger) {
|
|
2106
|
+
return trigger?.closest(SEARCHABLE_SELECT_MODAL_CONTENT_SELECTOR) ?? null;
|
|
2107
|
+
}
|
|
2108
|
+
function SearchableSelect({ value, onValueChange, options, placeholder = "Select...", disabled = false, className, triggerClassName, contentClassName, ariaLabel, searchPlaceholder = "Search...", emptyMessage = "No results found", leadingIcon: LeadingIcon, searchThreshold = AUTO_SEARCHABLE_SELECT_THRESHOLD, size = "default", renderOption, renderValue }) {
|
|
2109
|
+
const selectId = useId();
|
|
2110
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
2111
|
+
const [isMounted, setIsMounted] = useState(false);
|
|
2112
|
+
const [search, setSearch] = useState("");
|
|
2113
|
+
const [highlightedOptionIndex, setHighlightedOptionIndex] = useState(0);
|
|
2114
|
+
const containerRef = useRef(null);
|
|
2115
|
+
const triggerRef = useRef(null);
|
|
2116
|
+
const contentRef = useRef(null);
|
|
2117
|
+
const searchInputRef = useRef(null);
|
|
2118
|
+
const optionRefs = useRef([]);
|
|
2119
|
+
const [portalContainer, setPortalContainer] = useState(null);
|
|
2120
|
+
const [dropdownPosition, setDropdownPosition] = useState(null);
|
|
2121
|
+
const selectedOption = options.find((option) => option.value === value);
|
|
2122
|
+
const showSearchInput = options.length > searchThreshold;
|
|
2123
|
+
useEffect(() => {
|
|
2124
|
+
setIsMounted(true);
|
|
2125
|
+
}, []);
|
|
2126
|
+
useEffect(() => {
|
|
2127
|
+
function closeOtherSearchableSelects(event) {
|
|
2128
|
+
if (event instanceof CustomEvent && event.detail !== selectId) setIsOpen(false);
|
|
2129
|
+
}
|
|
2130
|
+
window.addEventListener("searchable-select:open", closeOtherSearchableSelects);
|
|
2131
|
+
return () => {
|
|
2132
|
+
window.removeEventListener("searchable-select:open", closeOtherSearchableSelects);
|
|
2133
|
+
};
|
|
2134
|
+
}, [selectId]);
|
|
2135
|
+
useEffect(() => {
|
|
2136
|
+
if (!isOpen) return;
|
|
2137
|
+
window.dispatchEvent(new CustomEvent("searchable-select:open", { detail: selectId }));
|
|
2138
|
+
}, [isOpen, selectId]);
|
|
2139
|
+
useEffect(() => {
|
|
2140
|
+
if (!showSearchInput || !isOpen) {
|
|
2141
|
+
setSearch("");
|
|
2142
|
+
return;
|
|
2143
|
+
}
|
|
2144
|
+
requestAnimationFrame(() => {
|
|
2145
|
+
searchInputRef.current?.focus();
|
|
2146
|
+
});
|
|
2147
|
+
}, [isOpen, showSearchInput]);
|
|
2148
|
+
useEffect(() => {
|
|
2149
|
+
function handlePointerDown(event) {
|
|
2150
|
+
if (isSearchableSelectPointerInside(event.target, containerRef.current, contentRef.current)) return;
|
|
2151
|
+
setIsOpen(false);
|
|
2152
|
+
}
|
|
2153
|
+
if (!isOpen) return;
|
|
2154
|
+
document.addEventListener("pointerdown", handlePointerDown);
|
|
2155
|
+
return () => {
|
|
2156
|
+
document.removeEventListener("pointerdown", handlePointerDown);
|
|
2157
|
+
};
|
|
2158
|
+
}, [isOpen]);
|
|
2159
|
+
const filteredOptions = useMemo(() => rankBySearch(options, search, (option) => buildSearchText(option.label, option.searchText)), [options, search]);
|
|
2160
|
+
useEffect(() => {
|
|
2161
|
+
if (!isOpen) return;
|
|
2162
|
+
const selectedIndex = filteredOptions.findIndex((option) => option.value === value);
|
|
2163
|
+
if (selectedIndex >= 0) {
|
|
2164
|
+
setHighlightedOptionIndex(selectedIndex);
|
|
2165
|
+
return;
|
|
2166
|
+
}
|
|
2167
|
+
setHighlightedOptionIndex(0);
|
|
2168
|
+
}, [
|
|
2169
|
+
filteredOptions,
|
|
2170
|
+
isOpen,
|
|
2171
|
+
value
|
|
2172
|
+
]);
|
|
2173
|
+
useEffect(() => {
|
|
2174
|
+
optionRefs.current = optionRefs.current.slice(0, filteredOptions.length);
|
|
2175
|
+
if (filteredOptions.length === 0) {
|
|
2176
|
+
setHighlightedOptionIndex(0);
|
|
2177
|
+
return;
|
|
2178
|
+
}
|
|
2179
|
+
setHighlightedOptionIndex((currentValue) => Math.min(currentValue, filteredOptions.length - 1));
|
|
2180
|
+
}, [filteredOptions]);
|
|
2181
|
+
useEffect(() => {
|
|
2182
|
+
if (!isOpen || filteredOptions.length === 0) return;
|
|
2183
|
+
optionRefs.current[highlightedOptionIndex]?.scrollIntoView({ block: "nearest" });
|
|
2184
|
+
}, [
|
|
2185
|
+
filteredOptions.length,
|
|
2186
|
+
highlightedOptionIndex,
|
|
2187
|
+
isOpen
|
|
2188
|
+
]);
|
|
2189
|
+
useLayoutEffect(() => {
|
|
2190
|
+
if (!isOpen) {
|
|
2191
|
+
setDropdownPosition(null);
|
|
2192
|
+
setPortalContainer(null);
|
|
2193
|
+
return;
|
|
2194
|
+
}
|
|
2195
|
+
function updateDropdownPosition() {
|
|
2196
|
+
const trigger = triggerRef.current;
|
|
2197
|
+
const content = contentRef.current;
|
|
2198
|
+
if (!trigger || !content) return;
|
|
2199
|
+
const nextPortalContainer = getSearchableSelectPortalContainer(trigger);
|
|
2200
|
+
setPortalContainer(nextPortalContainer);
|
|
2201
|
+
const triggerRect = trigger.getBoundingClientRect();
|
|
2202
|
+
const contentRect = content.getBoundingClientRect();
|
|
2203
|
+
const nextPosition = resolveSearchableSelectDropdownPosition({
|
|
2204
|
+
triggerRect,
|
|
2205
|
+
boundaryRect: getSearchableSelectBoundaryRect(trigger),
|
|
2206
|
+
portalRect: nextPortalContainer?.getBoundingClientRect(),
|
|
2207
|
+
contentWidth: Math.max(triggerRect.width, contentRect.width, 250),
|
|
2208
|
+
contentHeight: contentRect.height,
|
|
2209
|
+
viewportWidth: window.innerWidth,
|
|
2210
|
+
viewportHeight: window.innerHeight
|
|
2211
|
+
});
|
|
2212
|
+
setDropdownPosition((currentValue) => {
|
|
2213
|
+
if (currentValue?.top === nextPosition.top && currentValue?.left === nextPosition.left && currentValue?.width === nextPosition.width && currentValue?.maxHeight === nextPosition.maxHeight) return currentValue;
|
|
2214
|
+
return nextPosition;
|
|
2215
|
+
});
|
|
2216
|
+
}
|
|
2217
|
+
updateDropdownPosition();
|
|
2218
|
+
const animationFrameId = window.requestAnimationFrame(updateDropdownPosition);
|
|
2219
|
+
window.addEventListener("resize", updateDropdownPosition);
|
|
2220
|
+
window.addEventListener("scroll", updateDropdownPosition, true);
|
|
2221
|
+
return () => {
|
|
2222
|
+
window.cancelAnimationFrame(animationFrameId);
|
|
2223
|
+
window.removeEventListener("resize", updateDropdownPosition);
|
|
2224
|
+
window.removeEventListener("scroll", updateDropdownPosition, true);
|
|
2225
|
+
};
|
|
2226
|
+
}, [
|
|
2227
|
+
filteredOptions.length,
|
|
2228
|
+
isOpen,
|
|
2229
|
+
search,
|
|
2230
|
+
showSearchInput
|
|
2231
|
+
]);
|
|
2232
|
+
function selectHighlightedOption() {
|
|
2233
|
+
const highlightedOption = filteredOptions[highlightedOptionIndex];
|
|
2234
|
+
if (!highlightedOption) return;
|
|
2235
|
+
onValueChange(highlightedOption.value);
|
|
2236
|
+
setIsOpen(false);
|
|
2237
|
+
}
|
|
2238
|
+
function selectOption(value$1) {
|
|
2239
|
+
onValueChange(value$1);
|
|
2240
|
+
setIsOpen(false);
|
|
2241
|
+
}
|
|
2242
|
+
function moveHighlightedOption(direction) {
|
|
2243
|
+
if (filteredOptions.length === 0) return;
|
|
2244
|
+
setHighlightedOptionIndex((currentValue) => {
|
|
2245
|
+
if (direction === "down") return (currentValue + 1) % filteredOptions.length;
|
|
2246
|
+
return (currentValue - 1 + filteredOptions.length) % filteredOptions.length;
|
|
2247
|
+
});
|
|
2248
|
+
}
|
|
2249
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2250
|
+
ref: containerRef,
|
|
2251
|
+
className: cn("relative", className),
|
|
2252
|
+
children: [/* @__PURE__ */ jsxs("button", {
|
|
2253
|
+
ref: triggerRef,
|
|
2254
|
+
type: "button",
|
|
2255
|
+
"aria-label": ariaLabel,
|
|
2256
|
+
"aria-expanded": isOpen,
|
|
2257
|
+
disabled,
|
|
2258
|
+
onClick: () => setIsOpen((currentValue) => !currentValue),
|
|
2259
|
+
onKeyDown: (event) => {
|
|
2260
|
+
if (event.key === "Escape") setIsOpen(false);
|
|
2261
|
+
},
|
|
2262
|
+
className: cn("border-input data-placeholder:text-muted-foreground dark:bg-input/30 dark:hover:bg-input/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 flex w-full items-center justify-between gap-1.5 rounded-lg border bg-transparent py-1 pr-2 pl-2 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:ring-3 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:ring-3", size === "sm" ? "h-7 rounded-[min(var(--radius-md),10px)]" : "h-8", triggerClassName),
|
|
2263
|
+
children: [
|
|
2264
|
+
LeadingIcon ? /* @__PURE__ */ jsx(LeadingIcon, {
|
|
2265
|
+
className: "text-muted-foreground size-4 shrink-0",
|
|
2266
|
+
strokeWidth: 1.8
|
|
2267
|
+
}) : null,
|
|
2268
|
+
/* @__PURE__ */ jsx("span", {
|
|
2269
|
+
className: "min-w-0 flex-1 truncate text-left",
|
|
2270
|
+
children: selectedOption ? renderValue?.(selectedOption) ?? selectedOption.label : /* @__PURE__ */ jsx("span", {
|
|
2271
|
+
className: "text-muted-foreground",
|
|
2272
|
+
children: placeholder
|
|
2273
|
+
})
|
|
2274
|
+
}),
|
|
2275
|
+
/* @__PURE__ */ jsx(ChevronDown, { className: cn("text-muted-foreground size-4 shrink-0 transition-transform", isOpen && "rotate-180") })
|
|
2276
|
+
]
|
|
2277
|
+
}), isOpen && isMounted ? createPortal(/* @__PURE__ */ jsxs("div", {
|
|
2278
|
+
ref: contentRef,
|
|
2279
|
+
"data-searchable-select-content": "",
|
|
2280
|
+
className: cn("bg-popover text-popover-foreground pointer-events-auto z-[60] min-w-0 rounded-xl border p-2 shadow-lg", portalContainer ? "absolute" : "fixed", contentClassName),
|
|
2281
|
+
style: {
|
|
2282
|
+
top: dropdownPosition?.top ?? 0,
|
|
2283
|
+
left: dropdownPosition?.left ?? 0,
|
|
2284
|
+
width: dropdownPosition?.width ?? triggerRef.current?.getBoundingClientRect().width,
|
|
2285
|
+
visibility: dropdownPosition ? "visible" : "hidden"
|
|
2286
|
+
},
|
|
2287
|
+
children: [showSearchInput ? /* @__PURE__ */ jsxs("div", {
|
|
2288
|
+
className: "relative mb-2",
|
|
2289
|
+
children: [/* @__PURE__ */ jsx(Search, { className: "text-muted-foreground absolute top-1/2 left-2.5 size-4 -translate-y-1/2" }), /* @__PURE__ */ jsx(Input, {
|
|
2290
|
+
ref: searchInputRef,
|
|
2291
|
+
value: search,
|
|
2292
|
+
onChange: (event) => setSearch(event.target.value),
|
|
2293
|
+
onKeyDown: (event) => {
|
|
2294
|
+
if (event.key === "ArrowDown") {
|
|
2295
|
+
event.preventDefault();
|
|
2296
|
+
moveHighlightedOption("down");
|
|
2297
|
+
}
|
|
2298
|
+
if (event.key === "ArrowUp") {
|
|
2299
|
+
event.preventDefault();
|
|
2300
|
+
moveHighlightedOption("up");
|
|
2301
|
+
}
|
|
2302
|
+
if (event.key === "Enter") {
|
|
2303
|
+
event.preventDefault();
|
|
2304
|
+
selectHighlightedOption();
|
|
2305
|
+
}
|
|
2306
|
+
if (event.key === "Escape") {
|
|
2307
|
+
event.preventDefault();
|
|
2308
|
+
setIsOpen(false);
|
|
2309
|
+
}
|
|
2310
|
+
},
|
|
2311
|
+
placeholder: searchPlaceholder,
|
|
2312
|
+
className: "pl-9"
|
|
2313
|
+
})]
|
|
2314
|
+
}) : null, /* @__PURE__ */ jsxs("div", {
|
|
2315
|
+
className: "space-y-1 overflow-y-auto",
|
|
2316
|
+
style: { maxHeight: Math.min(256, dropdownPosition?.maxHeight ?? 256) },
|
|
2317
|
+
children: [filteredOptions.map((option, index) => {
|
|
2318
|
+
const isSelected = option.value === value;
|
|
2319
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
2320
|
+
ref: (element) => {
|
|
2321
|
+
optionRefs.current[index] = element;
|
|
2322
|
+
},
|
|
2323
|
+
type: "button",
|
|
2324
|
+
className: cn("hover:bg-accent hover:text-accent-foreground flex h-auto w-full items-center justify-between gap-3 rounded-md px-2 py-1.5 text-left text-sm transition-colors", isSelected && "bg-muted/60", highlightedOptionIndex === index && "bg-accent text-accent-foreground"),
|
|
2325
|
+
onMouseEnter: () => setHighlightedOptionIndex(index),
|
|
2326
|
+
onPointerDown: (event) => {
|
|
2327
|
+
event.preventDefault();
|
|
2328
|
+
event.stopPropagation();
|
|
2329
|
+
selectOption(option.value);
|
|
2330
|
+
},
|
|
2331
|
+
onClick: (event) => {
|
|
2332
|
+
if (event.detail === 0) selectOption(option.value);
|
|
2333
|
+
},
|
|
2334
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
2335
|
+
className: "min-w-0 flex-1 truncate",
|
|
2336
|
+
children: renderOption?.(option) ?? option.label
|
|
2337
|
+
}), isSelected ? /* @__PURE__ */ jsx(Check, { className: "size-4 shrink-0" }) : null]
|
|
2338
|
+
}, option.value);
|
|
2339
|
+
}), filteredOptions.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
2340
|
+
className: "text-muted-foreground px-2 py-4 text-sm",
|
|
2341
|
+
children: emptyMessage
|
|
2342
|
+
}) : null]
|
|
2343
|
+
})]
|
|
2344
|
+
}), portalContainer ?? document.body) : null]
|
|
2345
|
+
});
|
|
2346
|
+
}
|
|
2347
|
+
|
|
2348
|
+
//#endregion
|
|
2349
|
+
//#region src/smart-table/SmartTableActions.tsx
|
|
2350
|
+
const SMART_TABLE_ACTIONS_CONTAINER_CLASS = "flex w-full items-center justify-end gap-1";
|
|
2351
|
+
const actionIcons = {
|
|
2352
|
+
view: Eye,
|
|
2353
|
+
edit: Pencil,
|
|
2354
|
+
delete: Trash2
|
|
2355
|
+
};
|
|
2356
|
+
const actionLabels = {
|
|
2357
|
+
view: "Visualizza",
|
|
2358
|
+
edit: "Edit",
|
|
2359
|
+
delete: "Delete"
|
|
2360
|
+
};
|
|
2361
|
+
function SmartTableActions({ item, actions, actionHandlers, renderActions }) {
|
|
2362
|
+
if (renderActions) return /* @__PURE__ */ jsx("div", {
|
|
2363
|
+
className: SMART_TABLE_ACTIONS_CONTAINER_CLASS,
|
|
2364
|
+
children: renderActions(item)
|
|
2365
|
+
});
|
|
2366
|
+
if (!actions || !actionHandlers) return null;
|
|
2367
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2368
|
+
className: SMART_TABLE_ACTIONS_CONTAINER_CLASS,
|
|
2369
|
+
children: actions.map((action) => {
|
|
2370
|
+
const Icon = actionIcons[action];
|
|
2371
|
+
const handler = actionHandlers[`on${action.charAt(0).toUpperCase()}${action.slice(1)}`];
|
|
2372
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
2373
|
+
variant: "ghost",
|
|
2374
|
+
size: "icon",
|
|
2375
|
+
className: "size-8",
|
|
2376
|
+
"aria-label": actionLabels[action],
|
|
2377
|
+
onClick: (event) => {
|
|
2378
|
+
event.stopPropagation();
|
|
2379
|
+
handler?.(item);
|
|
2380
|
+
},
|
|
2381
|
+
children: /* @__PURE__ */ jsx(Icon, { className: "size-4" })
|
|
2382
|
+
}, action);
|
|
2383
|
+
})
|
|
2384
|
+
});
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2387
|
+
//#endregion
|
|
2388
|
+
//#region src/smart-table/utils.ts
|
|
2389
|
+
function formatListEntry(value) {
|
|
2390
|
+
const formatted = formatValue(value);
|
|
2391
|
+
if (typeof formatted === "string" || typeof formatted === "number") return String(formatted);
|
|
2392
|
+
return "—";
|
|
2393
|
+
}
|
|
2394
|
+
/**
|
|
2395
|
+
* Get nested value from object using dot notation
|
|
2396
|
+
* e.g., getNestedValue(obj, 'user.profile.name')
|
|
2397
|
+
*/
|
|
2398
|
+
function getNestedValue(obj, path) {
|
|
2399
|
+
return path.split(".").reduce((acc, key) => {
|
|
2400
|
+
if (acc && typeof acc === "object" && key in acc) return acc[key];
|
|
2401
|
+
}, obj);
|
|
2402
|
+
}
|
|
2403
|
+
/**
|
|
2404
|
+
* Format a value for display
|
|
2405
|
+
*/
|
|
2406
|
+
function formatValue(value) {
|
|
2407
|
+
if (value === null || value === void 0) return "—";
|
|
2408
|
+
if (typeof value === "boolean") return value ? "Yes" : "No";
|
|
2409
|
+
if (value instanceof Date) return createElement(DisplayDate, { value });
|
|
2410
|
+
if (typeof value === "number") return value.toLocaleString();
|
|
2411
|
+
if (typeof value === "string") return value;
|
|
2412
|
+
if (Array.isArray(value)) {
|
|
2413
|
+
if (value.length === 0) return "—";
|
|
2414
|
+
return value.map((entry) => formatListEntry(entry)).join(", ");
|
|
2415
|
+
}
|
|
2416
|
+
if (typeof value === "object") {
|
|
2417
|
+
const record = value;
|
|
2418
|
+
for (const key of [
|
|
2419
|
+
"label",
|
|
2420
|
+
"name",
|
|
2421
|
+
"title"
|
|
2422
|
+
]) {
|
|
2423
|
+
const nestedValue = record[key];
|
|
2424
|
+
if (typeof nestedValue === "string" || typeof nestedValue === "number") return String(nestedValue);
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
return "[Object]";
|
|
2428
|
+
}
|
|
2429
|
+
function formatValueAsText(value) {
|
|
2430
|
+
if (value === null || value === void 0) return "—";
|
|
2431
|
+
if (typeof value === "boolean") return value ? "Yes" : "No";
|
|
2432
|
+
if (value instanceof Date) return formatDisplayDate(value);
|
|
2433
|
+
if (typeof value === "number") return value.toLocaleString();
|
|
2434
|
+
if (typeof value === "string") return value;
|
|
2435
|
+
if (Array.isArray(value)) {
|
|
2436
|
+
if (value.length === 0) return "—";
|
|
2437
|
+
return value.map((entry) => formatValueAsText(entry) ?? "—").join(", ");
|
|
2438
|
+
}
|
|
2439
|
+
if (typeof value === "object") {
|
|
2440
|
+
const record = value;
|
|
2441
|
+
for (const key of [
|
|
2442
|
+
"label",
|
|
2443
|
+
"name",
|
|
2444
|
+
"title"
|
|
2445
|
+
]) {
|
|
2446
|
+
const nestedValue = record[key];
|
|
2447
|
+
if (typeof nestedValue === "string" || typeof nestedValue === "number") return String(nestedValue);
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
return null;
|
|
2451
|
+
}
|
|
2452
|
+
function getColumnValue(column, item) {
|
|
2453
|
+
if (!column.accessor) return null;
|
|
2454
|
+
if (typeof column.accessor === "string" && column.accessor.includes(".")) return getNestedValue(item, column.accessor);
|
|
2455
|
+
return item[column.accessor];
|
|
2456
|
+
}
|
|
2457
|
+
function renderColumnValue(column, item) {
|
|
2458
|
+
const value = getColumnValue(column, item);
|
|
2459
|
+
if (column.render) return column.render(value, item);
|
|
2460
|
+
return formatValue(value);
|
|
2461
|
+
}
|
|
2462
|
+
function getColumnTooltipText(column, item) {
|
|
2463
|
+
if (column.render) return null;
|
|
2464
|
+
return formatValueAsText(getColumnValue(column, item));
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
//#endregion
|
|
2468
|
+
//#region src/smart-table/sorting.ts
|
|
2469
|
+
function getColumnSortKey(column) {
|
|
2470
|
+
if (column.sortable === false) return null;
|
|
2471
|
+
if (column.sortKey) return column.sortKey;
|
|
2472
|
+
return typeof column.accessor === "string" ? column.accessor : null;
|
|
2473
|
+
}
|
|
2474
|
+
function getNextSortState(currentState, key) {
|
|
2475
|
+
if (currentState?.key !== key) return {
|
|
2476
|
+
key,
|
|
2477
|
+
direction: "asc"
|
|
2478
|
+
};
|
|
2479
|
+
if (currentState.direction === "asc") return {
|
|
2480
|
+
key,
|
|
2481
|
+
direction: "desc"
|
|
2482
|
+
};
|
|
2483
|
+
return null;
|
|
2484
|
+
}
|
|
2485
|
+
function normalizeSortValue(value) {
|
|
2486
|
+
if (value === null || value === void 0 || value === "") return null;
|
|
2487
|
+
if (value instanceof Date) return value.getTime();
|
|
2488
|
+
return value;
|
|
2489
|
+
}
|
|
2490
|
+
function compareSortValues(left, right) {
|
|
2491
|
+
const leftValue = normalizeSortValue(left);
|
|
2492
|
+
const rightValue = normalizeSortValue(right);
|
|
2493
|
+
if (leftValue === null && rightValue === null) return 0;
|
|
2494
|
+
if (leftValue === null) return 1;
|
|
2495
|
+
if (rightValue === null) return -1;
|
|
2496
|
+
if (typeof leftValue === "number" && typeof rightValue === "number") return leftValue - rightValue;
|
|
2497
|
+
if (typeof leftValue === "boolean" && typeof rightValue === "boolean") return Number(leftValue) - Number(rightValue);
|
|
2498
|
+
return String(leftValue).localeCompare(String(rightValue), "it", {
|
|
2499
|
+
numeric: true,
|
|
2500
|
+
sensitivity: "base"
|
|
2501
|
+
});
|
|
2502
|
+
}
|
|
2503
|
+
function getSortValue(column, row) {
|
|
2504
|
+
if (column.sortAccessor) return column.sortAccessor(row);
|
|
2505
|
+
return getColumnValue(column, row);
|
|
2506
|
+
}
|
|
2507
|
+
function getSortColumn(columns, sortState) {
|
|
2508
|
+
if (!sortState) return null;
|
|
2509
|
+
return columns.find((column) => getColumnSortKey(column) === sortState.key) ?? null;
|
|
2510
|
+
}
|
|
2511
|
+
function applyDirection(value, direction) {
|
|
2512
|
+
return direction === "asc" ? value : -value;
|
|
2513
|
+
}
|
|
2514
|
+
function sortTableData(data, columns, sortState) {
|
|
2515
|
+
const sortColumn = getSortColumn(columns, sortState);
|
|
2516
|
+
if (!sortColumn || !sortState) return [...data];
|
|
2517
|
+
return data.map((row, index) => ({
|
|
2518
|
+
row,
|
|
2519
|
+
index
|
|
2520
|
+
})).sort((left, right) => {
|
|
2521
|
+
const compared = compareSortValues(getSortValue(sortColumn, left.row), getSortValue(sortColumn, right.row));
|
|
2522
|
+
return compared === 0 ? left.index - right.index : applyDirection(compared, sortState.direction);
|
|
2523
|
+
}).map(({ row }) => row);
|
|
2524
|
+
}
|
|
2525
|
+
function useTableSorting({ data, columns, initialSortState = null }) {
|
|
2526
|
+
const [sortState, setSortState] = useState(initialSortState);
|
|
2527
|
+
return {
|
|
2528
|
+
sortedData: useMemo(() => sortTableData(data, columns, sortState), [
|
|
2529
|
+
columns,
|
|
2530
|
+
data,
|
|
2531
|
+
sortState
|
|
2532
|
+
]),
|
|
2533
|
+
sortState,
|
|
2534
|
+
setSortState
|
|
2535
|
+
};
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
//#endregion
|
|
2539
|
+
//#region src/smart-table/truncated-content.utils.ts
|
|
2540
|
+
function getTruncatedContentAlignmentClass(align) {
|
|
2541
|
+
if (align === "right") return "text-right";
|
|
2542
|
+
if (align === "center") return "text-center";
|
|
2543
|
+
return "text-left";
|
|
2544
|
+
}
|
|
2545
|
+
function shouldRenderTooltipTrigger(options) {
|
|
2546
|
+
return Boolean(options.tooltip) && options.isOverflowing;
|
|
2547
|
+
}
|
|
2548
|
+
|
|
2549
|
+
//#endregion
|
|
2550
|
+
//#region src/smart-table/TruncatedContent.tsx
|
|
2551
|
+
function TruncatedContent({ children, tooltip, align = "left", className = "" }) {
|
|
2552
|
+
const contentRef = useRef(null);
|
|
2553
|
+
const [isOverflowing, setIsOverflowing] = useState(false);
|
|
2554
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
2555
|
+
const updateOverflowState = () => {
|
|
2556
|
+
const element = contentRef.current;
|
|
2557
|
+
if (!element) {
|
|
2558
|
+
setIsOverflowing(false);
|
|
2559
|
+
return false;
|
|
2560
|
+
}
|
|
2561
|
+
const overflowing = element.scrollWidth > element.clientWidth + 1;
|
|
2562
|
+
setIsOverflowing(overflowing);
|
|
2563
|
+
return overflowing;
|
|
2564
|
+
};
|
|
2565
|
+
useEffect(() => {
|
|
2566
|
+
const element = contentRef.current;
|
|
2567
|
+
if (!element) return;
|
|
2568
|
+
updateOverflowState();
|
|
2569
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
2570
|
+
updateOverflowState();
|
|
2571
|
+
});
|
|
2572
|
+
resizeObserver.observe(element);
|
|
2573
|
+
window.addEventListener("resize", updateOverflowState);
|
|
2574
|
+
return () => {
|
|
2575
|
+
resizeObserver.disconnect();
|
|
2576
|
+
window.removeEventListener("resize", updateOverflowState);
|
|
2577
|
+
};
|
|
2578
|
+
}, [
|
|
2579
|
+
children,
|
|
2580
|
+
className,
|
|
2581
|
+
align,
|
|
2582
|
+
tooltip
|
|
2583
|
+
]);
|
|
2584
|
+
useEffect(() => {
|
|
2585
|
+
if (!isOverflowing && isOpen) setIsOpen(false);
|
|
2586
|
+
}, [isOpen, isOverflowing]);
|
|
2587
|
+
const alignmentClass = getTruncatedContentAlignmentClass(align);
|
|
2588
|
+
const showTooltipTrigger = shouldRenderTooltipTrigger({
|
|
2589
|
+
tooltip,
|
|
2590
|
+
isOverflowing
|
|
2591
|
+
});
|
|
2592
|
+
const content = /* @__PURE__ */ jsx("div", {
|
|
2593
|
+
ref: contentRef,
|
|
2594
|
+
className: [
|
|
2595
|
+
"block min-w-0 w-full truncate",
|
|
2596
|
+
alignmentClass,
|
|
2597
|
+
className
|
|
2598
|
+
].filter(Boolean).join(" "),
|
|
2599
|
+
children
|
|
2600
|
+
});
|
|
2601
|
+
if (!showTooltipTrigger) return content;
|
|
2602
|
+
return /* @__PURE__ */ jsxs(Tooltip, {
|
|
2603
|
+
delayDuration: 100,
|
|
2604
|
+
open: isOverflowing ? isOpen : false,
|
|
2605
|
+
onOpenChange: (nextOpen) => {
|
|
2606
|
+
if (!nextOpen) {
|
|
2607
|
+
setIsOpen(false);
|
|
2608
|
+
return;
|
|
2609
|
+
}
|
|
2610
|
+
setIsOpen(updateOverflowState());
|
|
2611
|
+
},
|
|
2612
|
+
children: [/* @__PURE__ */ jsx(TooltipTrigger, {
|
|
2613
|
+
asChild: true,
|
|
2614
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
2615
|
+
type: "button",
|
|
2616
|
+
className: [
|
|
2617
|
+
"block min-w-0 w-full overflow-hidden bg-transparent p-0 text-inherit outline-none",
|
|
2618
|
+
alignmentClass,
|
|
2619
|
+
isOverflowing ? "cursor-help" : "cursor-default"
|
|
2620
|
+
].filter(Boolean).join(" "),
|
|
2621
|
+
onMouseEnter: () => {
|
|
2622
|
+
updateOverflowState();
|
|
2623
|
+
},
|
|
2624
|
+
onFocus: () => {
|
|
2625
|
+
updateOverflowState();
|
|
2626
|
+
},
|
|
2627
|
+
onClick: (event) => {
|
|
2628
|
+
event.stopPropagation();
|
|
2629
|
+
if (updateOverflowState()) setIsOpen((previous) => !previous);
|
|
2630
|
+
},
|
|
2631
|
+
children: content
|
|
2632
|
+
})
|
|
2633
|
+
}), /* @__PURE__ */ jsx(TooltipContent, {
|
|
2634
|
+
side: "top",
|
|
2635
|
+
align: align === "right" ? "end" : align === "center" ? "center" : "start",
|
|
2636
|
+
sideOffset: 6,
|
|
2637
|
+
collisionPadding: 12,
|
|
2638
|
+
avoidCollisions: true,
|
|
2639
|
+
className: "w-[min(20rem,calc(100vw-1.5rem))] max-w-[calc(100vw-1.5rem)] whitespace-normal break-words text-left",
|
|
2640
|
+
children: tooltip
|
|
2641
|
+
})]
|
|
2642
|
+
});
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2645
|
+
//#endregion
|
|
2646
|
+
//#region src/smart-table/DesktopView.tsx
|
|
2647
|
+
const ACTION_COLUMN_WIDTH_PX = 112;
|
|
2648
|
+
const ACTION_CELL_CLASS_NAME = "w-28 min-w-28 overflow-visible whitespace-nowrap text-right";
|
|
2649
|
+
function SortIcon({ activeDirection }) {
|
|
2650
|
+
if (activeDirection === "asc") return /* @__PURE__ */ jsx(ArrowUp, {
|
|
2651
|
+
className: "size-3.5",
|
|
2652
|
+
"aria-hidden": "true"
|
|
2653
|
+
});
|
|
2654
|
+
if (activeDirection === "desc") return /* @__PURE__ */ jsx(ArrowDown, {
|
|
2655
|
+
className: "size-3.5",
|
|
2656
|
+
"aria-hidden": "true"
|
|
2657
|
+
});
|
|
2658
|
+
return /* @__PURE__ */ jsx(ChevronsUpDown, {
|
|
2659
|
+
className: "size-3.5 opacity-45",
|
|
2660
|
+
"aria-hidden": "true"
|
|
2661
|
+
});
|
|
2662
|
+
}
|
|
2663
|
+
function SortableHeaderContent({ column, sortState, onSortChange }) {
|
|
2664
|
+
const sortKey = getColumnSortKey(column);
|
|
2665
|
+
const activeDirection = sortState?.key === sortKey ? sortState.direction : void 0;
|
|
2666
|
+
if (!sortKey || !onSortChange) return /* @__PURE__ */ jsx(Fragment, { children: column.header });
|
|
2667
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
2668
|
+
type: "button",
|
|
2669
|
+
className: cn("hover:text-foreground focus-visible:ring-ring inline-flex max-w-full items-center gap-1.5 rounded-sm text-left font-medium transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none", column.align === "right" && "ml-auto", column.align === "center" && "mx-auto"),
|
|
2670
|
+
"aria-label": `Sort by ${column.header}`,
|
|
2671
|
+
onClick: () => {
|
|
2672
|
+
onSortChange(getNextSortState(sortState ?? null, sortKey));
|
|
2673
|
+
},
|
|
2674
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
2675
|
+
className: "truncate",
|
|
2676
|
+
children: column.header
|
|
2677
|
+
}), /* @__PURE__ */ jsx(SortIcon, { activeDirection })]
|
|
2678
|
+
});
|
|
2679
|
+
}
|
|
2680
|
+
function DesktopView({ data, columns, isLoading, skeletonRows, actions, actionHandlers, renderActions, noDataMessage, noDataContent, getRowKey, onRowClick, pagination, stickyHeader = false, maxHeight = "calc(100vh - 300px)", fullHeight = false, sortState, onSortChange }) {
|
|
2681
|
+
const resolvedNoDataContent = noDataContent ?? noDataMessage;
|
|
2682
|
+
const hasActions = (actions?.length ?? 0) > 0 || renderActions !== void 0;
|
|
2683
|
+
const actionColumnWidth = hasActions ? `${String(ACTION_COLUMN_WIDTH_PX)}px` : void 0;
|
|
2684
|
+
const specifiedPercentageWidth = columns.reduce((total, column) => {
|
|
2685
|
+
if (typeof column.width === "string" && column.width.endsWith("%")) {
|
|
2686
|
+
const parsedWidth = Number.parseFloat(column.width);
|
|
2687
|
+
return Number.isNaN(parsedWidth) ? total : total + parsedWidth;
|
|
2688
|
+
}
|
|
2689
|
+
return total;
|
|
2690
|
+
}, 0);
|
|
2691
|
+
const remainingPercentageForDataColumns = Math.max(0, 100 - specifiedPercentageWidth - 0);
|
|
2692
|
+
const columnsWithoutWidth = columns.filter((column) => column.width === void 0).length;
|
|
2693
|
+
const defaultPercentageWidth = columnsWithoutWidth > 0 && remainingPercentageForDataColumns > 0 ? `${String(remainingPercentageForDataColumns / columnsWithoutWidth)}%` : void 0;
|
|
2694
|
+
const columnGroup = /* @__PURE__ */ jsxs("colgroup", { children: [columns.map((column) => /* @__PURE__ */ jsx("col", { style: { width: column.width ?? defaultPercentageWidth } }, column.header)), hasActions && actionColumnWidth ? /* @__PURE__ */ jsx("col", { style: {
|
|
2695
|
+
width: actionColumnWidth,
|
|
2696
|
+
minWidth: actionColumnWidth
|
|
2697
|
+
} }) : null] });
|
|
2698
|
+
const tableHeader = /* @__PURE__ */ jsx(TableHeader, {
|
|
2699
|
+
className: cn(stickyHeader && "bg-muted/50 sticky top-0 z-10 backdrop-blur-sm"),
|
|
2700
|
+
children: /* @__PURE__ */ jsxs(TableRow, { children: [columns.map((col) => /* @__PURE__ */ jsx(TableHead, {
|
|
2701
|
+
style: { width: col.width },
|
|
2702
|
+
"aria-sort": sortState?.key === getColumnSortKey(col) ? sortState.direction === "asc" ? "ascending" : "descending" : void 0,
|
|
2703
|
+
className: col.align === "right" ? "text-right" : col.align === "center" ? "text-center" : "",
|
|
2704
|
+
children: /* @__PURE__ */ jsx(SortableHeaderContent, {
|
|
2705
|
+
column: col,
|
|
2706
|
+
sortState,
|
|
2707
|
+
onSortChange
|
|
2708
|
+
})
|
|
2709
|
+
}, col.header)), hasActions ? /* @__PURE__ */ jsx(TableHead, {
|
|
2710
|
+
style: actionColumnWidth ? {
|
|
2711
|
+
width: actionColumnWidth,
|
|
2712
|
+
minWidth: actionColumnWidth
|
|
2713
|
+
} : void 0,
|
|
2714
|
+
className: "w-28 min-w-28 overflow-visible text-right whitespace-nowrap",
|
|
2715
|
+
children: "Actions"
|
|
2716
|
+
}) : null] })
|
|
2717
|
+
});
|
|
2718
|
+
const scrollContainerClass = cn("min-w-0 rounded-lg border overflow-x-auto", fullHeight && "flex-1 min-h-0 overflow-y-auto", !fullHeight && stickyHeader && "overflow-y-auto");
|
|
2719
|
+
const scrollContainerStyle = !fullHeight && stickyHeader ? { maxHeight } : void 0;
|
|
2720
|
+
if (isLoading) return /* @__PURE__ */ jsxs("div", {
|
|
2721
|
+
className: cn("flex flex-col", fullHeight && "min-h-0 flex-1"),
|
|
2722
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
2723
|
+
className: scrollContainerClass,
|
|
2724
|
+
style: scrollContainerStyle,
|
|
2725
|
+
children: /* @__PURE__ */ jsxs(Table, {
|
|
2726
|
+
className: "table-fixed",
|
|
2727
|
+
children: [
|
|
2728
|
+
columnGroup,
|
|
2729
|
+
tableHeader,
|
|
2730
|
+
/* @__PURE__ */ jsx(TableBody, { children: Array.from({ length: skeletonRows }).map((_, i) => /* @__PURE__ */ jsxs(TableRow, { children: [columns.map((col) => /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(Skeleton, { className: "h-5 w-full" }) }, col.header)), hasActions ? /* @__PURE__ */ jsx(TableCell, {
|
|
2731
|
+
className: ACTION_CELL_CLASS_NAME,
|
|
2732
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
2733
|
+
className: "flex w-full justify-end",
|
|
2734
|
+
children: /* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-20" })
|
|
2735
|
+
})
|
|
2736
|
+
}) : null] }, i)) })
|
|
2737
|
+
]
|
|
2738
|
+
})
|
|
2739
|
+
}), pagination && /* @__PURE__ */ jsx(Pagination, {
|
|
2740
|
+
currentPage: pagination.currentPage,
|
|
2741
|
+
totalPages: pagination.totalPages,
|
|
2742
|
+
totalItems: pagination.totalItems,
|
|
2743
|
+
pageSize: pagination.pageSize,
|
|
2744
|
+
startIndex: pagination.startIndex,
|
|
2745
|
+
endIndex: pagination.endIndex,
|
|
2746
|
+
onPageChange: pagination.onPageChange
|
|
2747
|
+
})]
|
|
2748
|
+
});
|
|
2749
|
+
if (data.length === 0) return /* @__PURE__ */ jsxs("div", {
|
|
2750
|
+
className: cn("flex flex-col", fullHeight && "min-h-0 flex-1"),
|
|
2751
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
2752
|
+
className: "w-full py-0",
|
|
2753
|
+
children: typeof resolvedNoDataContent === "string" ? /* @__PURE__ */ jsx("div", {
|
|
2754
|
+
className: "text-muted-foreground",
|
|
2755
|
+
children: resolvedNoDataContent
|
|
2756
|
+
}) : resolvedNoDataContent
|
|
2757
|
+
}), pagination && pagination.totalItems > 0 && /* @__PURE__ */ jsx(Pagination, {
|
|
2758
|
+
currentPage: pagination.currentPage,
|
|
2759
|
+
totalPages: pagination.totalPages,
|
|
2760
|
+
totalItems: pagination.totalItems,
|
|
2761
|
+
pageSize: pagination.pageSize,
|
|
2762
|
+
startIndex: pagination.startIndex,
|
|
2763
|
+
endIndex: pagination.endIndex,
|
|
2764
|
+
onPageChange: pagination.onPageChange
|
|
2765
|
+
})]
|
|
2766
|
+
});
|
|
2767
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2768
|
+
className: cn("flex flex-col", fullHeight && "min-h-0 flex-1"),
|
|
2769
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
2770
|
+
className: scrollContainerClass,
|
|
2771
|
+
style: scrollContainerStyle,
|
|
2772
|
+
children: /* @__PURE__ */ jsxs(Table, {
|
|
2773
|
+
className: "table-fixed",
|
|
2774
|
+
children: [
|
|
2775
|
+
columnGroup,
|
|
2776
|
+
tableHeader,
|
|
2777
|
+
/* @__PURE__ */ jsx(TableBody, { children: data.map((item) => /* @__PURE__ */ jsxs(TableRow, {
|
|
2778
|
+
className: onRowClick ? "hover:bg-muted/50 cursor-pointer" : "",
|
|
2779
|
+
onClick: () => onRowClick?.(item),
|
|
2780
|
+
children: [columns.map((col) => /* @__PURE__ */ jsx(TableCell, {
|
|
2781
|
+
className: cn("max-w-0", col.align === "right" ? "text-right" : col.align === "center" ? "text-center" : ""),
|
|
2782
|
+
children: col.truncate === false ? /* @__PURE__ */ jsx("div", {
|
|
2783
|
+
className: cn("block w-full min-w-0", col.align === "right" ? "text-right" : col.align === "center" ? "text-center" : "text-left"),
|
|
2784
|
+
children: renderColumnValue(col, item)
|
|
2785
|
+
}) : /* @__PURE__ */ jsx(TruncatedContent, {
|
|
2786
|
+
align: col.align,
|
|
2787
|
+
tooltip: getColumnTooltipText(col, item),
|
|
2788
|
+
children: renderColumnValue(col, item)
|
|
2789
|
+
})
|
|
2790
|
+
}, col.header)), hasActions ? /* @__PURE__ */ jsx(TableCell, {
|
|
2791
|
+
className: ACTION_CELL_CLASS_NAME,
|
|
2792
|
+
children: /* @__PURE__ */ jsx(SmartTableActions, {
|
|
2793
|
+
item,
|
|
2794
|
+
actions,
|
|
2795
|
+
actionHandlers,
|
|
2796
|
+
renderActions
|
|
2797
|
+
})
|
|
2798
|
+
}) : null]
|
|
2799
|
+
}, getRowKey(item))) })
|
|
2800
|
+
]
|
|
2801
|
+
})
|
|
2802
|
+
}), pagination && /* @__PURE__ */ jsx(Pagination, {
|
|
2803
|
+
currentPage: pagination.currentPage,
|
|
2804
|
+
totalPages: pagination.totalPages,
|
|
2805
|
+
totalItems: pagination.totalItems,
|
|
2806
|
+
pageSize: pagination.pageSize,
|
|
2807
|
+
startIndex: pagination.startIndex,
|
|
2808
|
+
endIndex: pagination.endIndex,
|
|
2809
|
+
onPageChange: pagination.onPageChange
|
|
2810
|
+
})]
|
|
2811
|
+
});
|
|
2812
|
+
}
|
|
2813
|
+
|
|
2814
|
+
//#endregion
|
|
2815
|
+
//#region src/smart-table/MobileView.tsx
|
|
2816
|
+
function MobileView({ data, columns, isLoading, skeletonRows, actions, actionHandlers, renderActions, noDataMessage, noDataContent, getRowKey, onRowClick, renderMobileCard, pagination, fullHeight = false }) {
|
|
2817
|
+
const resolvedNoDataContent = noDataContent ?? noDataMessage;
|
|
2818
|
+
const visibleColumns = columns.filter((col) => !col.hideOnMobile);
|
|
2819
|
+
const hasActions = (actions?.length ?? 0) > 0 || renderActions !== void 0;
|
|
2820
|
+
const paginationComponent = pagination && /* @__PURE__ */ jsx(Pagination, {
|
|
2821
|
+
currentPage: pagination.currentPage,
|
|
2822
|
+
totalPages: pagination.totalPages,
|
|
2823
|
+
totalItems: pagination.totalItems,
|
|
2824
|
+
pageSize: pagination.pageSize,
|
|
2825
|
+
startIndex: pagination.startIndex,
|
|
2826
|
+
endIndex: pagination.endIndex,
|
|
2827
|
+
onPageChange: pagination.onPageChange
|
|
2828
|
+
});
|
|
2829
|
+
if (isLoading) return /* @__PURE__ */ jsxs("div", {
|
|
2830
|
+
className: cn("flex flex-col", fullHeight && "flex-1 min-h-0"),
|
|
2831
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
2832
|
+
className: cn("space-y-2", fullHeight && "flex-1 min-h-0 overflow-auto px-px"),
|
|
2833
|
+
children: Array.from({ length: skeletonRows }).map((_, i) => /* @__PURE__ */ jsx(Card, {
|
|
2834
|
+
size: "sm",
|
|
2835
|
+
className: "border border-border/80 shadow-none ring-0",
|
|
2836
|
+
children: /* @__PURE__ */ jsx(CardContent, {
|
|
2837
|
+
className: "px-3",
|
|
2838
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
2839
|
+
className: "space-y-2",
|
|
2840
|
+
children: [visibleColumns.map((column) => /* @__PURE__ */ jsxs("div", {
|
|
2841
|
+
className: "flex items-center gap-3",
|
|
2842
|
+
children: [/* @__PURE__ */ jsx(Skeleton, { className: "h-3.5 w-20 shrink-0" }), /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-full" })]
|
|
2843
|
+
}, column.header)), hasActions ? /* @__PURE__ */ jsx("div", {
|
|
2844
|
+
className: "flex justify-end border-t pt-3",
|
|
2845
|
+
children: /* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-24" })
|
|
2846
|
+
}) : null]
|
|
2847
|
+
})
|
|
2848
|
+
})
|
|
2849
|
+
}, i))
|
|
2850
|
+
}), paginationComponent]
|
|
2851
|
+
});
|
|
2852
|
+
if (data.length === 0) return /* @__PURE__ */ jsxs("div", {
|
|
2853
|
+
className: cn("flex flex-col", fullHeight && "flex-1 min-h-0"),
|
|
2854
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
2855
|
+
className: "w-full py-0",
|
|
2856
|
+
children: typeof resolvedNoDataContent === "string" ? /* @__PURE__ */ jsx("div", {
|
|
2857
|
+
className: "text-muted-foreground",
|
|
2858
|
+
children: resolvedNoDataContent
|
|
2859
|
+
}) : resolvedNoDataContent
|
|
2860
|
+
}), pagination && pagination.totalItems > 0 && paginationComponent]
|
|
2861
|
+
});
|
|
2862
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2863
|
+
className: cn("flex flex-col", fullHeight && "flex-1 min-h-0"),
|
|
2864
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
2865
|
+
className: cn("space-y-2", fullHeight && "flex-1 min-h-0 overflow-auto px-px"),
|
|
2866
|
+
children: data.map((item) => /* @__PURE__ */ jsx(Card, {
|
|
2867
|
+
size: "sm",
|
|
2868
|
+
className: cn("border border-border/80 shadow-none ring-0", onRowClick ? "cursor-pointer hover:bg-muted/50" : ""),
|
|
2869
|
+
onClick: () => onRowClick?.(item),
|
|
2870
|
+
children: /* @__PURE__ */ jsx(CardContent, {
|
|
2871
|
+
className: "px-3",
|
|
2872
|
+
children: renderMobileCard ? renderMobileCard(item) : /* @__PURE__ */ jsxs("div", {
|
|
2873
|
+
className: "space-y-2.5",
|
|
2874
|
+
children: [visibleColumns.map((col) => /* @__PURE__ */ jsxs("div", {
|
|
2875
|
+
className: "flex items-center gap-3 overflow-hidden",
|
|
2876
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
2877
|
+
className: "w-20 shrink-0 truncate text-[11px] font-medium uppercase tracking-wide text-muted-foreground",
|
|
2878
|
+
children: col.mobileLabel ?? col.header
|
|
2879
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
2880
|
+
className: "min-w-0 flex-1 text-sm",
|
|
2881
|
+
children: col.truncate === false ? /* @__PURE__ */ jsx("div", {
|
|
2882
|
+
className: "block min-w-0 w-full text-right text-sm",
|
|
2883
|
+
children: renderColumnValue(col, item)
|
|
2884
|
+
}) : /* @__PURE__ */ jsx(TruncatedContent, {
|
|
2885
|
+
align: "right",
|
|
2886
|
+
tooltip: getColumnTooltipText(col, item),
|
|
2887
|
+
className: "text-sm",
|
|
2888
|
+
children: renderColumnValue(col, item)
|
|
2889
|
+
})
|
|
2890
|
+
})]
|
|
2891
|
+
}, col.header)), hasActions ? /* @__PURE__ */ jsx("div", {
|
|
2892
|
+
className: "mt-3 flex justify-end border-t pt-2.5",
|
|
2893
|
+
children: /* @__PURE__ */ jsx(SmartTableActions, {
|
|
2894
|
+
item,
|
|
2895
|
+
actions,
|
|
2896
|
+
actionHandlers,
|
|
2897
|
+
renderActions
|
|
2898
|
+
})
|
|
2899
|
+
}) : null]
|
|
2900
|
+
})
|
|
2901
|
+
})
|
|
2902
|
+
}, getRowKey(item)))
|
|
2903
|
+
}), paginationComponent]
|
|
2904
|
+
});
|
|
2905
|
+
}
|
|
2906
|
+
|
|
2907
|
+
//#endregion
|
|
2908
|
+
//#region src/smart-table/SmartTable.tsx
|
|
2909
|
+
function SmartTable({ data, columns, isLoading, skeletonRows = 5, actions, actionHandlers, renderActions, noDataMessage = "No data available", noDataContent, getRowKey = (item) => {
|
|
2910
|
+
const record = item;
|
|
2911
|
+
if ("_id" in record) return String(record._id);
|
|
2912
|
+
if ("id" in record) return String(record.id);
|
|
2913
|
+
return data.indexOf(item);
|
|
2914
|
+
}, onRowClick, renderMobileCard, pagination, stickyHeader, maxHeight, fullHeight, sortState, onSortChange }) {
|
|
2915
|
+
if (useIsMobile()) return /* @__PURE__ */ jsx("div", {
|
|
2916
|
+
className: cn("min-w-0", fullHeight && "flex min-h-0 flex-1 flex-col"),
|
|
2917
|
+
children: /* @__PURE__ */ jsx(MobileView, {
|
|
2918
|
+
data,
|
|
2919
|
+
columns,
|
|
2920
|
+
isLoading,
|
|
2921
|
+
skeletonRows,
|
|
2922
|
+
actions,
|
|
2923
|
+
actionHandlers,
|
|
2924
|
+
renderActions,
|
|
2925
|
+
noDataMessage,
|
|
2926
|
+
noDataContent,
|
|
2927
|
+
getRowKey,
|
|
2928
|
+
onRowClick,
|
|
2929
|
+
renderMobileCard,
|
|
2930
|
+
pagination,
|
|
2931
|
+
fullHeight
|
|
2932
|
+
})
|
|
2933
|
+
});
|
|
2934
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2935
|
+
className: cn("min-w-0", fullHeight && "flex min-h-0 flex-1 flex-col"),
|
|
2936
|
+
children: /* @__PURE__ */ jsx(DesktopView, {
|
|
2937
|
+
data,
|
|
2938
|
+
columns,
|
|
2939
|
+
isLoading,
|
|
2940
|
+
skeletonRows,
|
|
2941
|
+
actions,
|
|
2942
|
+
actionHandlers,
|
|
2943
|
+
renderActions,
|
|
2944
|
+
noDataMessage,
|
|
2945
|
+
noDataContent,
|
|
2946
|
+
getRowKey,
|
|
2947
|
+
onRowClick,
|
|
2948
|
+
pagination,
|
|
2949
|
+
stickyHeader,
|
|
2950
|
+
maxHeight,
|
|
2951
|
+
fullHeight,
|
|
2952
|
+
sortState,
|
|
2953
|
+
onSortChange
|
|
2954
|
+
})
|
|
2955
|
+
});
|
|
2956
|
+
}
|
|
2957
|
+
|
|
2958
|
+
//#endregion
|
|
2959
|
+
//#region src/table-toolbar/table-toolbar.tsx
|
|
2960
|
+
function buildAllFilterOptionLabel(label, allOptionLabel) {
|
|
2961
|
+
return allOptionLabel ?? `All: ${label}`;
|
|
2962
|
+
}
|
|
2963
|
+
function FilterDropdown({ label, value, options, onChange, className, allowAll = true, allOptionLabel }) {
|
|
2964
|
+
return /* @__PURE__ */ jsx(SearchableSelect, {
|
|
2965
|
+
value,
|
|
2966
|
+
onValueChange: (nextValue) => {
|
|
2967
|
+
onChange(nextValue);
|
|
2968
|
+
},
|
|
2969
|
+
placeholder: label,
|
|
2970
|
+
className: className ?? "w-full sm:w-[140px]",
|
|
2971
|
+
searchPlaceholder: `Search ${label.toLocaleLowerCase()}...`,
|
|
2972
|
+
options: [...allowAll ? [{
|
|
2973
|
+
value: "all",
|
|
2974
|
+
label: buildAllFilterOptionLabel(label, allOptionLabel)
|
|
2975
|
+
}] : [], ...options]
|
|
2976
|
+
});
|
|
2977
|
+
}
|
|
2978
|
+
function SearchInput({ value, onChange, placeholder = "Search...", className }) {
|
|
2979
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2980
|
+
className: cn("relative", className),
|
|
2981
|
+
children: [
|
|
2982
|
+
/* @__PURE__ */ jsx(Search, { className: "text-muted-foreground absolute top-1/2 left-2.5 size-4 -translate-y-1/2" }),
|
|
2983
|
+
/* @__PURE__ */ jsx(Input, {
|
|
2984
|
+
type: "text",
|
|
2985
|
+
placeholder,
|
|
2986
|
+
value,
|
|
2987
|
+
onChange: (event) => {
|
|
2988
|
+
onChange(event.target.value);
|
|
2989
|
+
},
|
|
2990
|
+
className: "pr-9 pl-9"
|
|
2991
|
+
}),
|
|
2992
|
+
value ? /* @__PURE__ */ jsx("button", {
|
|
2993
|
+
type: "button",
|
|
2994
|
+
onClick: () => {
|
|
2995
|
+
onChange("");
|
|
2996
|
+
},
|
|
2997
|
+
className: "text-muted-foreground hover:text-foreground absolute top-1/2 right-2.5 -translate-y-1/2",
|
|
2998
|
+
children: /* @__PURE__ */ jsx(X, { className: "size-4" })
|
|
2999
|
+
}) : null
|
|
3000
|
+
]
|
|
3001
|
+
});
|
|
3002
|
+
}
|
|
3003
|
+
function resolveTableToolbarLabels(labels = {}) {
|
|
3004
|
+
return {
|
|
3005
|
+
filtersButtonLabel: labels.filtersButtonLabel ?? "Filters",
|
|
3006
|
+
filtersTitle: labels.filtersTitle ?? "Filters",
|
|
3007
|
+
filtersDescription: labels.filtersDescription ?? "Narrow the list using the available criteria.",
|
|
3008
|
+
clearFiltersLabel: labels.clearFiltersLabel ?? "Clear",
|
|
3009
|
+
showResultsLabel: labels.showResultsLabel ?? ((resultCount) => typeof resultCount === "number" ? `Show ${resultCount.toLocaleString("en-US")} ${resultCount === 1 ? "result" : "results"}` : "Show results"),
|
|
3010
|
+
rangeMinPlaceholder: labels.rangeMinPlaceholder ?? "Min",
|
|
3011
|
+
rangeMaxPlaceholder: labels.rangeMaxPlaceholder ?? "Max"
|
|
3012
|
+
};
|
|
3013
|
+
}
|
|
3014
|
+
function TableToolbar({ search, filters, textFilters, customFilters, rangeFilters, renderRangeInput, inlineControls, onClearAll, getDraftResultCount, labels, children }) {
|
|
3015
|
+
const resolvedLabels = resolveTableToolbarLabels(labels);
|
|
3016
|
+
const isMobile = useIsMobile();
|
|
3017
|
+
const [filtersOpen, setFiltersOpen] = useState(false);
|
|
3018
|
+
const [draftFilterValues, setDraftFilterValues] = useState({});
|
|
3019
|
+
const hasFilters = Boolean((filters?.length ?? 0) + (textFilters?.length ?? 0) + (customFilters?.length ?? 0) + (rangeFilters?.length ?? 0));
|
|
3020
|
+
const desktopConfirmShortcutEnabled = filtersOpen && !isMobile;
|
|
3021
|
+
const desktopModifierLabel = useDesktopShortcutModifierLabel(desktopConfirmShortcutEnabled);
|
|
3022
|
+
const clearableFilters = filters?.filter((filter) => filter.clearable !== false) ?? [];
|
|
3023
|
+
const clearableCustomFilters = customFilters?.filter((filter) => filter.clearable !== false) ?? [];
|
|
3024
|
+
const filterValues = useMemo(() => ({
|
|
3025
|
+
...Object.fromEntries((filters ?? []).map((filter) => [filter.config.key, filter.value])),
|
|
3026
|
+
...Object.fromEntries((textFilters ?? []).map((filter) => [filter.key, filter.value])),
|
|
3027
|
+
...Object.fromEntries((customFilters ?? []).map((filter) => [filter.key, filter.value])),
|
|
3028
|
+
...Object.fromEntries((rangeFilters ?? []).flatMap((filter) => [[`${filter.key}Min`, filter.minValue], [`${filter.key}Max`, filter.maxValue]]))
|
|
3029
|
+
}), [
|
|
3030
|
+
customFilters,
|
|
3031
|
+
filters,
|
|
3032
|
+
rangeFilters,
|
|
3033
|
+
textFilters
|
|
3034
|
+
]);
|
|
3035
|
+
const activeFilterCount = clearableFilters.filter((filter) => filter.value !== "all").length;
|
|
3036
|
+
const activeCustomFilterCount = clearableCustomFilters.filter((filter) => filter.value !== (filter.clearValue ?? "all")).length;
|
|
3037
|
+
const activeRangeFilterCount = rangeFilters?.filter((filter) => filter.minValue.trim() || filter.maxValue.trim()).length ?? 0;
|
|
3038
|
+
const activeTextFilterCount = textFilters?.filter((filter) => filter.value.trim().length > 0).length ?? 0;
|
|
3039
|
+
const hasDraftFilters = clearableFilters.some((filter) => {
|
|
3040
|
+
return (draftFilterValues[filter.config.key] ?? filter.value) !== "all";
|
|
3041
|
+
}) || clearableCustomFilters.some((filter) => {
|
|
3042
|
+
return (draftFilterValues[filter.key] ?? filter.value) !== (filter.clearValue ?? "all");
|
|
3043
|
+
}) || (rangeFilters ?? []).some((filter) => {
|
|
3044
|
+
const minValue = draftFilterValues[`${filter.key}Min`] ?? filter.minValue;
|
|
3045
|
+
const maxValue = draftFilterValues[`${filter.key}Max`] ?? filter.maxValue;
|
|
3046
|
+
return Boolean(minValue.trim() || maxValue.trim());
|
|
3047
|
+
}) || (textFilters ?? []).some((filter) => {
|
|
3048
|
+
return (draftFilterValues[filter.key] ?? filter.value).trim().length > 0;
|
|
3049
|
+
});
|
|
3050
|
+
const draftResultCount = useMemo(() => filtersOpen ? getDraftResultCount?.(draftFilterValues) : void 0, [
|
|
3051
|
+
draftFilterValues,
|
|
3052
|
+
filtersOpen,
|
|
3053
|
+
getDraftResultCount
|
|
3054
|
+
]);
|
|
3055
|
+
const applyButtonLabel = resolvedLabels.showResultsLabel(draftResultCount);
|
|
3056
|
+
function openFiltersSheet() {
|
|
3057
|
+
setDraftFilterValues(filterValues);
|
|
3058
|
+
setFiltersOpen(true);
|
|
3059
|
+
}
|
|
3060
|
+
const activeFilterTotal = activeFilterCount + activeCustomFilterCount + activeRangeFilterCount + activeTextFilterCount;
|
|
3061
|
+
function updateDraftFilterValue(key, value) {
|
|
3062
|
+
setDraftFilterValues((currentValues) => ({
|
|
3063
|
+
...currentValues,
|
|
3064
|
+
[key]: value
|
|
3065
|
+
}));
|
|
3066
|
+
}
|
|
3067
|
+
function clearAndApplyFilters() {
|
|
3068
|
+
const clearedValues = Object.fromEntries(clearableFilters.map((filter) => [filter.config.key, "all"]));
|
|
3069
|
+
const clearedRangeValues = Object.fromEntries((rangeFilters ?? []).flatMap((filter) => [[`${filter.key}Min`, ""], [`${filter.key}Max`, ""]]));
|
|
3070
|
+
const clearedTextValues = Object.fromEntries((textFilters ?? []).map((filter) => [filter.key, ""]));
|
|
3071
|
+
const clearedCustomValues = Object.fromEntries(clearableCustomFilters.map((filter) => [filter.key, filter.clearValue ?? "all"]));
|
|
3072
|
+
setDraftFilterValues((currentValues) => ({
|
|
3073
|
+
...currentValues,
|
|
3074
|
+
...clearedValues,
|
|
3075
|
+
...clearedRangeValues,
|
|
3076
|
+
...clearedTextValues,
|
|
3077
|
+
...clearedCustomValues
|
|
3078
|
+
}));
|
|
3079
|
+
filters?.forEach((filter) => {
|
|
3080
|
+
const nextValue = filter.clearable === false ? filter.value : "all";
|
|
3081
|
+
if (nextValue !== filter.value) filter.onChange(nextValue);
|
|
3082
|
+
});
|
|
3083
|
+
rangeFilters?.forEach((filter) => {
|
|
3084
|
+
if (filter.minValue) filter.onMinChange("");
|
|
3085
|
+
if (filter.maxValue) filter.onMaxChange("");
|
|
3086
|
+
});
|
|
3087
|
+
textFilters?.forEach((filter) => {
|
|
3088
|
+
if (filter.value) filter.onChange("");
|
|
3089
|
+
});
|
|
3090
|
+
customFilters?.forEach((filter) => {
|
|
3091
|
+
const nextValue = filter.clearable === false ? filter.value : filter.clearValue ?? "all";
|
|
3092
|
+
if (nextValue !== filter.value) filter.onChange(nextValue);
|
|
3093
|
+
});
|
|
3094
|
+
setFiltersOpen(false);
|
|
3095
|
+
}
|
|
3096
|
+
function applyDraftFilters() {
|
|
3097
|
+
filters?.forEach((filter) => {
|
|
3098
|
+
const nextValue = draftFilterValues[filter.config.key] ?? filter.value;
|
|
3099
|
+
if (nextValue !== filter.value) filter.onChange(nextValue);
|
|
3100
|
+
});
|
|
3101
|
+
rangeFilters?.forEach((filter) => {
|
|
3102
|
+
const nextMinValue = draftFilterValues[`${filter.key}Min`] ?? filter.minValue;
|
|
3103
|
+
const nextMaxValue = draftFilterValues[`${filter.key}Max`] ?? filter.maxValue;
|
|
3104
|
+
if (nextMinValue !== filter.minValue) filter.onMinChange(nextMinValue);
|
|
3105
|
+
if (nextMaxValue !== filter.maxValue) filter.onMaxChange(nextMaxValue);
|
|
3106
|
+
});
|
|
3107
|
+
textFilters?.forEach((filter) => {
|
|
3108
|
+
const nextValue = draftFilterValues[filter.key] ?? filter.value;
|
|
3109
|
+
if (nextValue !== filter.value) filter.onChange(nextValue);
|
|
3110
|
+
});
|
|
3111
|
+
customFilters?.forEach((filter) => {
|
|
3112
|
+
const nextValue = draftFilterValues[filter.key] ?? filter.value;
|
|
3113
|
+
if (nextValue !== filter.value) filter.onChange(nextValue);
|
|
3114
|
+
});
|
|
3115
|
+
setFiltersOpen(false);
|
|
3116
|
+
}
|
|
3117
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3118
|
+
className: "space-y-3",
|
|
3119
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
3120
|
+
className: "flex flex-wrap items-center gap-2",
|
|
3121
|
+
children: [
|
|
3122
|
+
search ? /* @__PURE__ */ jsx(SearchInput, {
|
|
3123
|
+
value: search.value,
|
|
3124
|
+
onChange: search.onChange,
|
|
3125
|
+
placeholder: search.placeholder,
|
|
3126
|
+
className: "min-w-0 flex-1 sm:w-64 sm:flex-initial"
|
|
3127
|
+
}) : null,
|
|
3128
|
+
inlineControls,
|
|
3129
|
+
hasFilters ? /* @__PURE__ */ jsxs(Button, {
|
|
3130
|
+
variant: "outline",
|
|
3131
|
+
className: "relative gap-2",
|
|
3132
|
+
onClick: openFiltersSheet,
|
|
3133
|
+
children: [
|
|
3134
|
+
/* @__PURE__ */ jsx(Filter, { className: "size-4" }),
|
|
3135
|
+
resolvedLabels.filtersButtonLabel,
|
|
3136
|
+
activeFilterTotal > 0 ? /* @__PURE__ */ jsx("span", {
|
|
3137
|
+
className: "bg-primary text-primary-foreground absolute -top-1 -right-1 flex size-4 items-center justify-center rounded-full text-[10px]",
|
|
3138
|
+
children: activeFilterTotal
|
|
3139
|
+
}) : null
|
|
3140
|
+
]
|
|
3141
|
+
}) : null,
|
|
3142
|
+
children ? /* @__PURE__ */ jsx("div", {
|
|
3143
|
+
className: "hidden sm:ml-auto sm:flex sm:items-center sm:gap-2",
|
|
3144
|
+
children
|
|
3145
|
+
}) : null
|
|
3146
|
+
]
|
|
3147
|
+
}), hasFilters ? /* @__PURE__ */ jsx(ResponsiveSheet, {
|
|
3148
|
+
open: filtersOpen,
|
|
3149
|
+
onOpenChange: setFiltersOpen,
|
|
3150
|
+
title: resolvedLabels.filtersTitle,
|
|
3151
|
+
description: resolvedLabels.filtersDescription,
|
|
3152
|
+
onConfirm: applyDraftFilters,
|
|
3153
|
+
footer: /* @__PURE__ */ jsxs("div", {
|
|
3154
|
+
className: "flex w-full gap-2",
|
|
3155
|
+
children: [onClearAll ? /* @__PURE__ */ jsxs(Button, {
|
|
3156
|
+
type: "button",
|
|
3157
|
+
variant: "outline",
|
|
3158
|
+
disabled: !hasDraftFilters,
|
|
3159
|
+
onClick: clearAndApplyFilters,
|
|
3160
|
+
className: "min-w-0 flex-1",
|
|
3161
|
+
children: [/* @__PURE__ */ jsx(X, { className: "mr-1 size-4" }), resolvedLabels.clearFiltersLabel]
|
|
3162
|
+
}) : null, /* @__PURE__ */ jsx(Button, {
|
|
3163
|
+
type: "button",
|
|
3164
|
+
onClick: applyDraftFilters,
|
|
3165
|
+
className: cn("relative min-w-0 flex-1", desktopConfirmShortcutEnabled ? "pr-12" : null),
|
|
3166
|
+
children: /* @__PURE__ */ jsxs("span", {
|
|
3167
|
+
className: "inline-flex w-full items-center justify-center",
|
|
3168
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
3169
|
+
className: "min-w-0 truncate",
|
|
3170
|
+
children: applyButtonLabel
|
|
3171
|
+
}), desktopConfirmShortcutEnabled && desktopModifierLabel ? /* @__PURE__ */ jsx("span", {
|
|
3172
|
+
className: "absolute top-1/2 right-2 -translate-y-1/2",
|
|
3173
|
+
children: /* @__PURE__ */ jsx(DesktopConfirmShortcutHint, { desktopModifierLabel })
|
|
3174
|
+
}) : null]
|
|
3175
|
+
})
|
|
3176
|
+
})]
|
|
3177
|
+
}),
|
|
3178
|
+
width: 420,
|
|
3179
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
3180
|
+
className: "space-y-4",
|
|
3181
|
+
children: [
|
|
3182
|
+
filters?.map((filter) => {
|
|
3183
|
+
const Icon = filter.config.icon;
|
|
3184
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3185
|
+
className: "flex flex-col gap-1.5",
|
|
3186
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
3187
|
+
className: "text-foreground flex items-center gap-2 text-sm font-medium",
|
|
3188
|
+
children: [Icon ? /* @__PURE__ */ jsx(Icon, {
|
|
3189
|
+
className: "text-foreground/70 size-3.5 shrink-0",
|
|
3190
|
+
strokeWidth: 1.8
|
|
3191
|
+
}) : null, filter.config.label]
|
|
3192
|
+
}), /* @__PURE__ */ jsx(FilterDropdown, {
|
|
3193
|
+
label: filter.config.label,
|
|
3194
|
+
value: draftFilterValues[filter.config.key] ?? filter.value,
|
|
3195
|
+
options: filter.config.options,
|
|
3196
|
+
onChange: (value) => {
|
|
3197
|
+
updateDraftFilterValue(filter.config.key, value);
|
|
3198
|
+
},
|
|
3199
|
+
className: "w-full",
|
|
3200
|
+
allowAll: filter.allowAll,
|
|
3201
|
+
allOptionLabel: filter.allOptionLabel
|
|
3202
|
+
})]
|
|
3203
|
+
}, filter.config.key);
|
|
3204
|
+
}),
|
|
3205
|
+
customFilters?.map((filter) => {
|
|
3206
|
+
const Icon = filter.icon;
|
|
3207
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3208
|
+
className: "flex flex-col gap-1.5",
|
|
3209
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
3210
|
+
className: "text-foreground flex items-center gap-2 text-sm font-medium",
|
|
3211
|
+
children: [Icon ? /* @__PURE__ */ jsx(Icon, {
|
|
3212
|
+
className: "text-foreground/70 size-3.5 shrink-0",
|
|
3213
|
+
strokeWidth: 1.8
|
|
3214
|
+
}) : null, filter.label]
|
|
3215
|
+
}), filter.render({
|
|
3216
|
+
value: draftFilterValues[filter.key] ?? filter.value,
|
|
3217
|
+
setValue: (value) => {
|
|
3218
|
+
updateDraftFilterValue(filter.key, value);
|
|
3219
|
+
}
|
|
3220
|
+
})]
|
|
3221
|
+
}, filter.key);
|
|
3222
|
+
}),
|
|
3223
|
+
textFilters?.map((filter) => {
|
|
3224
|
+
const Icon = filter.icon;
|
|
3225
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3226
|
+
className: "flex flex-col gap-1.5",
|
|
3227
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
3228
|
+
className: "text-foreground flex items-center gap-2 text-sm font-medium",
|
|
3229
|
+
children: [Icon ? /* @__PURE__ */ jsx(Icon, {
|
|
3230
|
+
className: "text-foreground/70 size-3.5 shrink-0",
|
|
3231
|
+
strokeWidth: 1.8
|
|
3232
|
+
}) : null, filter.label]
|
|
3233
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
3234
|
+
type: "text",
|
|
3235
|
+
value: draftFilterValues[filter.key] ?? filter.value,
|
|
3236
|
+
onChange: (event) => {
|
|
3237
|
+
updateDraftFilterValue(filter.key, event.target.value);
|
|
3238
|
+
},
|
|
3239
|
+
placeholder: filter.placeholder ?? filter.label
|
|
3240
|
+
})]
|
|
3241
|
+
}, filter.key);
|
|
3242
|
+
}),
|
|
3243
|
+
rangeFilters?.map((filter) => {
|
|
3244
|
+
const Icon = filter.icon;
|
|
3245
|
+
const minKey = `${filter.key}Min`;
|
|
3246
|
+
const maxKey = `${filter.key}Max`;
|
|
3247
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3248
|
+
className: "flex flex-col gap-1.5",
|
|
3249
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
3250
|
+
className: "text-foreground flex items-center gap-2 text-sm font-medium",
|
|
3251
|
+
children: [Icon ? /* @__PURE__ */ jsx(Icon, {
|
|
3252
|
+
className: "text-foreground/70 size-3.5 shrink-0",
|
|
3253
|
+
strokeWidth: 1.8
|
|
3254
|
+
}) : null, filter.label]
|
|
3255
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
3256
|
+
className: "grid grid-cols-2 gap-2",
|
|
3257
|
+
children: renderRangeInput ? /* @__PURE__ */ jsxs(Fragment, { children: [renderRangeInput({
|
|
3258
|
+
filter,
|
|
3259
|
+
input: "min",
|
|
3260
|
+
value: draftFilterValues[minKey] ?? filter.minValue,
|
|
3261
|
+
onChange: (value) => {
|
|
3262
|
+
updateDraftFilterValue(minKey, value);
|
|
3263
|
+
},
|
|
3264
|
+
placeholder: filter.minPlaceholder ?? resolvedLabels.rangeMinPlaceholder
|
|
3265
|
+
}), renderRangeInput({
|
|
3266
|
+
filter,
|
|
3267
|
+
input: "max",
|
|
3268
|
+
value: draftFilterValues[maxKey] ?? filter.maxValue,
|
|
3269
|
+
onChange: (value) => {
|
|
3270
|
+
updateDraftFilterValue(maxKey, value);
|
|
3271
|
+
},
|
|
3272
|
+
placeholder: filter.maxPlaceholder ?? resolvedLabels.rangeMaxPlaceholder
|
|
3273
|
+
})] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Input, {
|
|
3274
|
+
type: filter.inputType ?? "text",
|
|
3275
|
+
inputMode: filter.inputMode ?? "decimal",
|
|
3276
|
+
value: draftFilterValues[minKey] ?? filter.minValue,
|
|
3277
|
+
onChange: (event) => {
|
|
3278
|
+
updateDraftFilterValue(minKey, event.target.value);
|
|
3279
|
+
},
|
|
3280
|
+
placeholder: filter.minPlaceholder ?? "Min"
|
|
3281
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
3282
|
+
type: filter.inputType ?? "text",
|
|
3283
|
+
inputMode: filter.inputMode ?? "decimal",
|
|
3284
|
+
value: draftFilterValues[maxKey] ?? filter.maxValue,
|
|
3285
|
+
onChange: (event) => {
|
|
3286
|
+
updateDraftFilterValue(maxKey, event.target.value);
|
|
3287
|
+
},
|
|
3288
|
+
placeholder: filter.maxPlaceholder ?? "Max"
|
|
3289
|
+
})] })
|
|
3290
|
+
})]
|
|
3291
|
+
}, filter.key);
|
|
3292
|
+
})
|
|
3293
|
+
]
|
|
3294
|
+
})
|
|
3295
|
+
}) : null]
|
|
3296
|
+
});
|
|
3297
|
+
}
|
|
3298
|
+
|
|
3299
|
+
//#endregion
|
|
3300
|
+
//#region src/error-page/error-code.tsx
|
|
3301
|
+
function ErrorCode({ code, reference, className }) {
|
|
3302
|
+
return /* @__PURE__ */ jsxs("p", {
|
|
3303
|
+
className: cn("text-xs text-muted-foreground/55", className),
|
|
3304
|
+
children: [
|
|
3305
|
+
"Codice ",
|
|
3306
|
+
code,
|
|
3307
|
+
reference ? /* @__PURE__ */ jsxs("span", {
|
|
3308
|
+
className: "ml-2",
|
|
3309
|
+
children: ["Rif. ", reference]
|
|
3310
|
+
}) : null
|
|
3311
|
+
]
|
|
3312
|
+
});
|
|
3313
|
+
}
|
|
3314
|
+
|
|
3315
|
+
//#endregion
|
|
3316
|
+
//#region src/error-page/error-page-content.ts
|
|
3317
|
+
const NOT_FOUND_STATUS = 404;
|
|
3318
|
+
function getErrorText(error) {
|
|
3319
|
+
if (error instanceof Error) {
|
|
3320
|
+
const digest = "digest" in error ? String(error.digest) : "";
|
|
3321
|
+
return `${error.message} ${digest}`;
|
|
3322
|
+
}
|
|
3323
|
+
if (typeof error === "object" && error !== null) {
|
|
3324
|
+
const record = error;
|
|
3325
|
+
return `${typeof record.message === "string" ? record.message : ""} ${typeof record.digest === "string" ? record.digest : ""}`;
|
|
3326
|
+
}
|
|
3327
|
+
return typeof error === "string" ? error : "";
|
|
3328
|
+
}
|
|
3329
|
+
function getErrorStatusCode(error) {
|
|
3330
|
+
const text = getErrorText(error);
|
|
3331
|
+
const fallbackMatch = /NEXT_HTTP_ERROR_FALLBACK;(\d{3})/.exec(text);
|
|
3332
|
+
if (fallbackMatch) return Number(fallbackMatch[1]);
|
|
3333
|
+
const statusMatch = /\bstatus(?:Code)?[=:]\s*(\d{3})\b/i.exec(text);
|
|
3334
|
+
return statusMatch ? Number(statusMatch[1]) : null;
|
|
3335
|
+
}
|
|
3336
|
+
function resolveErrorPageContent(error, options = {}) {
|
|
3337
|
+
if (getErrorStatusCode(error) === NOT_FOUND_STATUS) return {
|
|
3338
|
+
code: "404",
|
|
3339
|
+
title: "Page not found",
|
|
3340
|
+
description: "The page you are looking for does not exist or has been moved.",
|
|
3341
|
+
shouldCapture: false
|
|
3342
|
+
};
|
|
3343
|
+
return {
|
|
3344
|
+
code: options.fallbackCode ?? "500",
|
|
3345
|
+
title: options.fallbackTitle ?? "An error occurred",
|
|
3346
|
+
description: options.fallbackDescription ?? "An unexpected error occurred. Please try again later.",
|
|
3347
|
+
shouldCapture: true
|
|
3348
|
+
};
|
|
3349
|
+
}
|
|
3350
|
+
function getNotFoundPageContent() {
|
|
3351
|
+
return {
|
|
3352
|
+
code: "404",
|
|
3353
|
+
title: "Page not found",
|
|
3354
|
+
description: "The page you are looking for does not exist or has been moved.",
|
|
3355
|
+
shouldCapture: false
|
|
3356
|
+
};
|
|
3357
|
+
}
|
|
3358
|
+
|
|
3359
|
+
//#endregion
|
|
3360
|
+
//#region src/error-page/posthog-error-capture.ts
|
|
3361
|
+
function getPostHogClient() {
|
|
3362
|
+
if (typeof window === "undefined") return null;
|
|
3363
|
+
const client = window.posthog;
|
|
3364
|
+
return typeof client?.capture === "function" ? client : null;
|
|
3365
|
+
}
|
|
3366
|
+
function normalizeError(error) {
|
|
3367
|
+
if (error instanceof Error) {
|
|
3368
|
+
const digest = "digest" in error && typeof error.digest === "string" ? error.digest : void 0;
|
|
3369
|
+
return {
|
|
3370
|
+
name: error.name,
|
|
3371
|
+
message: error.message,
|
|
3372
|
+
stack: error.stack,
|
|
3373
|
+
digest
|
|
3374
|
+
};
|
|
3375
|
+
}
|
|
3376
|
+
return {
|
|
3377
|
+
name: "UnknownError",
|
|
3378
|
+
message: typeof error === "string" ? error : "Unknown error"
|
|
3379
|
+
};
|
|
3380
|
+
}
|
|
3381
|
+
function createErrorReference(error) {
|
|
3382
|
+
if (error instanceof Error && "digest" in error && typeof error.digest === "string") return error.digest;
|
|
3383
|
+
return `err_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
3384
|
+
}
|
|
3385
|
+
function captureErrorToPostHog({ error, code, reference, source = "error-page", metadata }) {
|
|
3386
|
+
const posthog = getPostHogClient();
|
|
3387
|
+
if (!posthog) return;
|
|
3388
|
+
const normalizedError = normalizeError(error);
|
|
3389
|
+
try {
|
|
3390
|
+
posthog.capture("$exception", {
|
|
3391
|
+
$exception_type: normalizedError.name,
|
|
3392
|
+
$exception_message: normalizedError.message,
|
|
3393
|
+
$exception_stack_trace: normalizedError.stack,
|
|
3394
|
+
code,
|
|
3395
|
+
digest: normalizedError.digest,
|
|
3396
|
+
error_reference: reference,
|
|
3397
|
+
pathname: window.location.pathname,
|
|
3398
|
+
source,
|
|
3399
|
+
...metadata
|
|
3400
|
+
});
|
|
3401
|
+
} catch {}
|
|
3402
|
+
}
|
|
3403
|
+
|
|
3404
|
+
//#endregion
|
|
3405
|
+
//#region src/error-page/saas-error-page.tsx
|
|
3406
|
+
function resolveContent(error, content) {
|
|
3407
|
+
const resolvedContent = error ? resolveErrorPageContent(error) : getNotFoundPageContent();
|
|
3408
|
+
return {
|
|
3409
|
+
...resolvedContent,
|
|
3410
|
+
...content,
|
|
3411
|
+
shouldCapture: content?.shouldCapture ?? resolvedContent.shouldCapture
|
|
3412
|
+
};
|
|
3413
|
+
}
|
|
3414
|
+
function SaasErrorPage({ error, reset, homeHref = "/", homeLabel = "Torna alla home", retryLabel = "Riprova", source, metadata, content, className, classes }) {
|
|
3415
|
+
const resolvedContent = useMemo(() => resolveContent(error, content), [content, error]);
|
|
3416
|
+
const [reference] = useState(() => error ? createErrorReference(error) : null);
|
|
3417
|
+
useEffect(() => {
|
|
3418
|
+
if (!error || !resolvedContent.shouldCapture || !reference) return;
|
|
3419
|
+
captureErrorToPostHog({
|
|
3420
|
+
error,
|
|
3421
|
+
code: resolvedContent.code,
|
|
3422
|
+
reference,
|
|
3423
|
+
source,
|
|
3424
|
+
metadata
|
|
3425
|
+
});
|
|
3426
|
+
}, [
|
|
3427
|
+
error,
|
|
3428
|
+
metadata,
|
|
3429
|
+
reference,
|
|
3430
|
+
resolvedContent.code,
|
|
3431
|
+
resolvedContent.shouldCapture,
|
|
3432
|
+
source
|
|
3433
|
+
]);
|
|
3434
|
+
return /* @__PURE__ */ jsxs("main", {
|
|
3435
|
+
className: cn("bg-background text-foreground flex min-h-screen flex-col items-center justify-center gap-4 px-6 text-center", className, classes?.root),
|
|
3436
|
+
children: [
|
|
3437
|
+
/* @__PURE__ */ jsxs("div", {
|
|
3438
|
+
className: cn("space-y-3", classes?.content),
|
|
3439
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
3440
|
+
className: cn("text-8xl font-semibold leading-none text-muted-foreground/25 sm:text-9xl", classes?.code),
|
|
3441
|
+
children: resolvedContent.code
|
|
3442
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
3443
|
+
className: cn("space-y-2", classes?.text),
|
|
3444
|
+
children: [/* @__PURE__ */ jsx("h1", {
|
|
3445
|
+
className: cn("text-2xl font-semibold tracking-tight", classes?.title),
|
|
3446
|
+
children: resolvedContent.title
|
|
3447
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
3448
|
+
className: cn("mx-auto max-w-md text-sm text-muted-foreground", classes?.description),
|
|
3449
|
+
children: resolvedContent.description
|
|
3450
|
+
})]
|
|
3451
|
+
})]
|
|
3452
|
+
}),
|
|
3453
|
+
/* @__PURE__ */ jsx(ErrorCode, {
|
|
3454
|
+
code: resolvedContent.code,
|
|
3455
|
+
reference
|
|
3456
|
+
}),
|
|
3457
|
+
/* @__PURE__ */ jsxs("div", {
|
|
3458
|
+
className: cn("mt-2 flex flex-wrap justify-center gap-3", classes?.actions),
|
|
3459
|
+
children: [reset ? /* @__PURE__ */ jsx(Button, {
|
|
3460
|
+
type: "button",
|
|
3461
|
+
variant: "outline",
|
|
3462
|
+
onClick: reset,
|
|
3463
|
+
children: retryLabel
|
|
3464
|
+
}) : null, /* @__PURE__ */ jsx(Button, {
|
|
3465
|
+
asChild: true,
|
|
3466
|
+
children: /* @__PURE__ */ jsx("a", {
|
|
3467
|
+
href: homeHref,
|
|
3468
|
+
children: homeLabel
|
|
3469
|
+
})
|
|
3470
|
+
})]
|
|
3471
|
+
})
|
|
3472
|
+
]
|
|
3473
|
+
});
|
|
3474
|
+
}
|
|
3475
|
+
function SaasNotFoundPage({ title, description, ...props }) {
|
|
3476
|
+
return /* @__PURE__ */ jsx(SaasErrorPage, {
|
|
3477
|
+
...props,
|
|
3478
|
+
content: {
|
|
3479
|
+
code: "404",
|
|
3480
|
+
title: title ?? "Page not found",
|
|
3481
|
+
description: description ?? "The page you are looking for does not exist or has been moved.",
|
|
3482
|
+
shouldCapture: false
|
|
3483
|
+
}
|
|
3484
|
+
});
|
|
3485
|
+
}
|
|
3486
|
+
|
|
3487
|
+
//#endregion
|
|
3488
|
+
export { AUTO_SEARCHABLE_SELECT_THRESHOLD, Avatar, AvatarFallback, AvatarImage, Button, CHIP_CLASS_NAMES, Calendar, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Chip, ChipButton, DesktopConfirmShortcutHint, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DisplayDate, Drawer, DrawerContent, DrawerDescription, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, EmptyStateCard, ErrorCode, FieldDetailRow, FileDropzone, FilterDropdown, HelpInfoButton, InitialEmptyState, Input, KeyboardKeycap, Label, NoResultsState, Pagination, Popover, PopoverContent, PopoverTrigger, ResponsiveSheet, SaasErrorPage, SaasNotFoundPage, ScrollFadeArea, SearchInput, SearchableSelect, SegmentedToggle, Select, SelectContent, SelectItem, SelectScrollDownButton, SelectScrollUpButton, SelectTrigger, SelectValue, Sheet, SheetActionFooter, SheetContent, SheetDescription, SheetHeader, SheetTitle, ShortcutModifierKeycap, Skeleton, SmartTable, SmartTableActions, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, TableToolbar, Tabs, TabsContent, TabsList, TabsScrollArea, TabsTrigger, Textarea, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, TruncatedContent, UserPicker, buildAllFilterOptionLabel, buildSearchText, buildUserInitials, buttonVariants, captureErrorToPostHog, cn, createErrorReference, filterAndRankBySearch, filterSelectableUsers, filterUsersBySearch, formatAbsoluteDate, formatDisplayDate, formatSelectedUserSummary, formatUserDisplayName, getChipClassName, getDesktopShortcutModifierLabel, getNotFoundPageContent, getSearchableSelectPortalContainer, isAllowedConfirmShortcutEvent, isSearchableSelectPointerInside, rankBySearch, resolveCollectionEmptyState, resolveErrorPageContent, resolveSearchableSelectDropdownPosition, scoreFuzzyMatch, tabsListVariants, toggleUserSelection, useDesktopConfirmShortcut, useDesktopShortcutModifierLabel, useIsMobile, useMediaQuery, useTableSorting };
|
|
3489
|
+
//# sourceMappingURL=index.mjs.map
|