@yenio-pld/blocks 0.0.0-stage → 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +100 -2
- package/dist/avatar.cjs +88 -0
- package/dist/avatar.cjs.map +1 -0
- package/dist/avatar.d.cts +18 -0
- package/dist/avatar.d.ts +18 -0
- package/dist/avatar.js +84 -0
- package/dist/avatar.js.map +1 -0
- package/dist/consent.cjs +85 -0
- package/dist/consent.cjs.map +1 -0
- package/dist/consent.d.cts +20 -0
- package/dist/consent.d.ts +20 -0
- package/dist/consent.js +73 -0
- package/dist/consent.js.map +1 -0
- package/dist/index.cjs +4053 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +859 -0
- package/dist/index.d.ts +859 -0
- package/dist/index.js +3947 -0
- package/dist/index.js.map +1 -0
- package/dist/theme-init.cjs +12 -0
- package/dist/theme-init.cjs.map +1 -0
- package/dist/theme-init.d.cts +11 -0
- package/dist/theme-init.d.ts +11 -0
- package/dist/theme-init.js +8 -0
- package/dist/theme-init.js.map +1 -0
- package/dist/ui/alert-dialog.d.ts +15 -0
- package/dist/ui/alert-dialog.js +137 -0
- package/dist/ui/alert-dialog.js.map +1 -0
- package/dist/ui/alert.d.ts +11 -0
- package/dist/ui/alert.js +57 -0
- package/dist/ui/alert.js.map +1 -0
- package/dist/ui/avatar.d.ts +13 -0
- package/dist/ui/avatar.js +105 -0
- package/dist/ui/avatar.js.map +1 -0
- package/dist/ui/badge.d.ts +13 -0
- package/dist/ui/badge.js +33 -0
- package/dist/ui/badge.js.map +1 -0
- package/dist/ui/breadcrumb.d.ts +12 -0
- package/dist/ui/breadcrumb.js +121 -0
- package/dist/ui/breadcrumb.js.map +1 -0
- package/dist/ui/bubble.d.ts +19 -0
- package/dist/ui/bubble.js +120 -0
- package/dist/ui/bubble.js.map +1 -0
- package/dist/ui/button.d.ts +12 -0
- package/dist/ui/button.js +59 -0
- package/dist/ui/button.js.map +1 -0
- package/dist/ui/card.d.ts +10 -0
- package/dist/ui/card.js +55 -0
- package/dist/ui/card.js.map +1 -0
- package/dist/ui/collapsible.d.ts +8 -0
- package/dist/ui/collapsible.js +17 -0
- package/dist/ui/collapsible.js.map +1 -0
- package/dist/ui/dialog.d.ts +16 -0
- package/dist/ui/dialog.js +125 -0
- package/dist/ui/dialog.js.map +1 -0
- package/dist/ui/dropdown-menu.d.ts +31 -0
- package/dist/ui/dropdown-menu.js +244 -0
- package/dist/ui/dropdown-menu.js.map +1 -0
- package/dist/ui/input-otp.d.ts +39 -0
- package/dist/ui/input-otp.js +53 -0
- package/dist/ui/input-otp.js.map +1 -0
- package/dist/ui/input.d.ts +5 -0
- package/dist/ui/input.js +28 -0
- package/dist/ui/input.js.map +1 -0
- package/dist/ui/label.d.ts +7 -0
- package/dist/ui/label.js +29 -0
- package/dist/ui/label.js.map +1 -0
- package/dist/ui/message.d.ts +12 -0
- package/dist/ui/message.js +93 -0
- package/dist/ui/message.js.map +1 -0
- package/dist/ui/progress.d.ts +9 -0
- package/dist/ui/progress.js +41 -0
- package/dist/ui/progress.js.map +1 -0
- package/dist/ui/separator.d.ts +6 -0
- package/dist/ui/separator.js +31 -0
- package/dist/ui/separator.js.map +1 -0
- package/dist/ui/sheet.d.ts +16 -0
- package/dist/ui/sheet.js +127 -0
- package/dist/ui/sheet.js.map +1 -0
- package/dist/ui/sidebar.d.ts +69 -0
- package/dist/ui/sidebar.js +624 -0
- package/dist/ui/sidebar.js.map +1 -0
- package/dist/ui/skeleton.d.ts +5 -0
- package/dist/ui/skeleton.js +26 -0
- package/dist/ui/skeleton.js.map +1 -0
- package/dist/ui/sonner.d.ts +6 -0
- package/dist/ui/sonner.js +25 -0
- package/dist/ui/sonner.js.map +1 -0
- package/dist/ui/switch.d.ts +8 -0
- package/dist/ui/switch.js +38 -0
- package/dist/ui/switch.js.map +1 -0
- package/dist/ui/table.d.ts +11 -0
- package/dist/ui/table.js +85 -0
- package/dist/ui/table.js.map +1 -0
- package/dist/ui/tooltip.d.ts +9 -0
- package/dist/ui/tooltip.js +67 -0
- package/dist/ui/tooltip.js.map +1 -0
- package/dist/ui/use-mobile.d.ts +3 -0
- package/dist/ui/use-mobile.js +24 -0
- package/dist/ui/use-mobile.js.map +1 -0
- package/package.json +281 -5
- package/scripts/check-theme.mjs +74 -0
- package/src/theme.css +191 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,4053 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var React = require('react');
|
|
5
|
+
var lucideReact = require('lucide-react');
|
|
6
|
+
var clsx = require('clsx');
|
|
7
|
+
var tailwindMerge = require('tailwind-merge');
|
|
8
|
+
var classVarianceAuthority = require('class-variance-authority');
|
|
9
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
10
|
+
var AlertDialogPrimitive = require('@radix-ui/react-alert-dialog');
|
|
11
|
+
var reactSlot = require('@radix-ui/react-slot');
|
|
12
|
+
var DropdownMenuPrimitive = require('@radix-ui/react-dropdown-menu');
|
|
13
|
+
var nextThemes = require('next-themes');
|
|
14
|
+
var SliderPrimitive = require('@radix-ui/react-slider');
|
|
15
|
+
var TogglePrimitive = require('@radix-ui/react-toggle');
|
|
16
|
+
var DialogPrimitive = require('@radix-ui/react-dialog');
|
|
17
|
+
var vaul = require('vaul');
|
|
18
|
+
var framerMotion = require('framer-motion');
|
|
19
|
+
|
|
20
|
+
function _interopNamespace(e) {
|
|
21
|
+
if (e && e.__esModule) return e;
|
|
22
|
+
var n = Object.create(null);
|
|
23
|
+
if (e) {
|
|
24
|
+
Object.keys(e).forEach(function (k) {
|
|
25
|
+
if (k !== 'default') {
|
|
26
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
27
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
28
|
+
enumerable: true,
|
|
29
|
+
get: function () { return e[k]; }
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
n.default = e;
|
|
35
|
+
return Object.freeze(n);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
var React__namespace = /*#__PURE__*/_interopNamespace(React);
|
|
39
|
+
var AlertDialogPrimitive__namespace = /*#__PURE__*/_interopNamespace(AlertDialogPrimitive);
|
|
40
|
+
var DropdownMenuPrimitive__namespace = /*#__PURE__*/_interopNamespace(DropdownMenuPrimitive);
|
|
41
|
+
var SliderPrimitive__namespace = /*#__PURE__*/_interopNamespace(SliderPrimitive);
|
|
42
|
+
var TogglePrimitive__namespace = /*#__PURE__*/_interopNamespace(TogglePrimitive);
|
|
43
|
+
var DialogPrimitive__namespace = /*#__PURE__*/_interopNamespace(DialogPrimitive);
|
|
44
|
+
|
|
45
|
+
var LanguageContext = React.createContext(null);
|
|
46
|
+
function useLanguageContext() {
|
|
47
|
+
return React.useContext(LanguageContext);
|
|
48
|
+
}
|
|
49
|
+
function cn(...inputs) {
|
|
50
|
+
return tailwindMerge.twMerge(clsx.clsx(inputs));
|
|
51
|
+
}
|
|
52
|
+
function Empty({ className, ...props }) {
|
|
53
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
54
|
+
"div",
|
|
55
|
+
{
|
|
56
|
+
"data-slot": "empty",
|
|
57
|
+
className: cn(
|
|
58
|
+
"flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg p-6 text-center text-balance md:p-12",
|
|
59
|
+
className
|
|
60
|
+
),
|
|
61
|
+
...props
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
function EmptyHeader({ className, ...props }) {
|
|
66
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
67
|
+
"div",
|
|
68
|
+
{
|
|
69
|
+
"data-slot": "empty-header",
|
|
70
|
+
className: cn(
|
|
71
|
+
"flex max-w-sm flex-col items-center gap-2 text-center",
|
|
72
|
+
className
|
|
73
|
+
),
|
|
74
|
+
...props
|
|
75
|
+
}
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
var emptyMediaVariants = classVarianceAuthority.cva(
|
|
79
|
+
"mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
80
|
+
{
|
|
81
|
+
variants: {
|
|
82
|
+
variant: {
|
|
83
|
+
default: "bg-transparent",
|
|
84
|
+
icon: "flex size-10 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground [&_svg:not([class*='size-'])]:size-6"
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
defaultVariants: {
|
|
88
|
+
variant: "default"
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
function EmptyMedia({
|
|
93
|
+
className,
|
|
94
|
+
variant = "default",
|
|
95
|
+
...props
|
|
96
|
+
}) {
|
|
97
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
98
|
+
"div",
|
|
99
|
+
{
|
|
100
|
+
"data-slot": "empty-icon",
|
|
101
|
+
"data-variant": variant,
|
|
102
|
+
className: cn(emptyMediaVariants({ variant, className })),
|
|
103
|
+
...props
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
function EmptyTitle({ className, ...props }) {
|
|
108
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
109
|
+
"div",
|
|
110
|
+
{
|
|
111
|
+
"data-slot": "empty-title",
|
|
112
|
+
className: cn("text-lg font-medium tracking-tight", className),
|
|
113
|
+
...props
|
|
114
|
+
}
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
function EmptyDescription({ className, ...props }) {
|
|
118
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
119
|
+
"div",
|
|
120
|
+
{
|
|
121
|
+
"data-slot": "empty-description",
|
|
122
|
+
className: cn(
|
|
123
|
+
"text-sm/relaxed text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
|
|
124
|
+
className
|
|
125
|
+
),
|
|
126
|
+
...props
|
|
127
|
+
}
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
function EmptyContent({ className, ...props }) {
|
|
131
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
132
|
+
"div",
|
|
133
|
+
{
|
|
134
|
+
"data-slot": "empty-content",
|
|
135
|
+
className: cn(
|
|
136
|
+
"flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance",
|
|
137
|
+
className
|
|
138
|
+
),
|
|
139
|
+
...props
|
|
140
|
+
}
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
function LoadingState({
|
|
144
|
+
label = "Loading\u2026",
|
|
145
|
+
className
|
|
146
|
+
}) {
|
|
147
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
148
|
+
"div",
|
|
149
|
+
{
|
|
150
|
+
role: "status",
|
|
151
|
+
"aria-live": "polite",
|
|
152
|
+
className: cn(
|
|
153
|
+
"flex items-center justify-center gap-2 py-12 text-sm text-muted-foreground",
|
|
154
|
+
className
|
|
155
|
+
),
|
|
156
|
+
children: [
|
|
157
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-4 animate-spin", "aria-hidden": "true" }),
|
|
158
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: label })
|
|
159
|
+
]
|
|
160
|
+
}
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
function ErrorState({
|
|
164
|
+
title = "Something went wrong",
|
|
165
|
+
description,
|
|
166
|
+
action,
|
|
167
|
+
className
|
|
168
|
+
}) {
|
|
169
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
170
|
+
"div",
|
|
171
|
+
{
|
|
172
|
+
role: "alert",
|
|
173
|
+
className: cn(
|
|
174
|
+
"flex flex-col items-center gap-3 rounded-xl border border-dashed border-destructive/40 bg-destructive/5 py-12 text-center",
|
|
175
|
+
className
|
|
176
|
+
),
|
|
177
|
+
children: [
|
|
178
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { className: "size-6 text-destructive", "aria-hidden": "true" }),
|
|
179
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
180
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-medium", children: title }),
|
|
181
|
+
description ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: description }) : null
|
|
182
|
+
] }),
|
|
183
|
+
action
|
|
184
|
+
]
|
|
185
|
+
}
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
function EmptyState({
|
|
189
|
+
title,
|
|
190
|
+
description,
|
|
191
|
+
action,
|
|
192
|
+
icon: Icon = lucideReact.Inbox,
|
|
193
|
+
variant = "default",
|
|
194
|
+
className
|
|
195
|
+
}) {
|
|
196
|
+
const isCompact = variant === "compact";
|
|
197
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
198
|
+
Empty,
|
|
199
|
+
{
|
|
200
|
+
className: cn(
|
|
201
|
+
"gap-3",
|
|
202
|
+
isCompact ? "flex-none rounded-none px-4 py-6" : "flex-none rounded-xl border border-dashed px-6 py-12",
|
|
203
|
+
className
|
|
204
|
+
),
|
|
205
|
+
children: [
|
|
206
|
+
/* @__PURE__ */ jsxRuntime.jsxs(EmptyHeader, { className: "gap-3", children: [
|
|
207
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
208
|
+
EmptyMedia,
|
|
209
|
+
{
|
|
210
|
+
variant: "icon",
|
|
211
|
+
className: cn(
|
|
212
|
+
"mb-0 bg-transparent text-muted-foreground",
|
|
213
|
+
isCompact ? "size-auto [&_svg:not([class*='size-'])]:size-8 [&_svg]:opacity-50" : "size-auto [&_svg:not([class*='size-'])]:size-6"
|
|
214
|
+
),
|
|
215
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { "aria-hidden": "true" })
|
|
216
|
+
}
|
|
217
|
+
),
|
|
218
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
219
|
+
EmptyTitle,
|
|
220
|
+
{
|
|
221
|
+
className: cn(
|
|
222
|
+
"font-medium tracking-normal",
|
|
223
|
+
isCompact ? "text-sm" : "text-base"
|
|
224
|
+
),
|
|
225
|
+
children: title
|
|
226
|
+
}
|
|
227
|
+
),
|
|
228
|
+
description ? /* @__PURE__ */ jsxRuntime.jsx(EmptyDescription, { className: "mt-0", children: description }) : null
|
|
229
|
+
] }),
|
|
230
|
+
action ? /* @__PURE__ */ jsxRuntime.jsx(EmptyContent, { className: "gap-0", children: action }) : null
|
|
231
|
+
]
|
|
232
|
+
}
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
var buttonVariants = classVarianceAuthority.cva(
|
|
236
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
|
237
|
+
{
|
|
238
|
+
variants: {
|
|
239
|
+
variant: {
|
|
240
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
241
|
+
destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
|
242
|
+
outline: "border bg-background text-foreground shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:text-foreground dark:hover:bg-muted dark:hover:text-foreground",
|
|
243
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
244
|
+
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50 dark:hover:text-foreground",
|
|
245
|
+
link: "text-primary underline-offset-4 hover:underline"
|
|
246
|
+
},
|
|
247
|
+
size: {
|
|
248
|
+
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
|
249
|
+
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
|
250
|
+
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
|
251
|
+
icon: "size-9",
|
|
252
|
+
"icon-sm": "size-8",
|
|
253
|
+
"icon-lg": "size-10"
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
defaultVariants: {
|
|
257
|
+
variant: "default",
|
|
258
|
+
size: "default"
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
);
|
|
262
|
+
function Button({
|
|
263
|
+
className,
|
|
264
|
+
variant = "default",
|
|
265
|
+
size = "default",
|
|
266
|
+
asChild = false,
|
|
267
|
+
...props
|
|
268
|
+
}) {
|
|
269
|
+
const Comp = asChild ? reactSlot.Slot : "button";
|
|
270
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
271
|
+
Comp,
|
|
272
|
+
{
|
|
273
|
+
"data-slot": "button",
|
|
274
|
+
"data-variant": variant,
|
|
275
|
+
"data-size": size,
|
|
276
|
+
className: cn(buttonVariants({ variant, size, className })),
|
|
277
|
+
...props
|
|
278
|
+
}
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
function AlertDialog({
|
|
282
|
+
...props
|
|
283
|
+
}) {
|
|
284
|
+
return /* @__PURE__ */ jsxRuntime.jsx(AlertDialogPrimitive__namespace.Root, { "data-slot": "alert-dialog", ...props });
|
|
285
|
+
}
|
|
286
|
+
function AlertDialogPortal({
|
|
287
|
+
...props
|
|
288
|
+
}) {
|
|
289
|
+
return /* @__PURE__ */ jsxRuntime.jsx(AlertDialogPrimitive__namespace.Portal, { "data-slot": "alert-dialog-portal", ...props });
|
|
290
|
+
}
|
|
291
|
+
function AlertDialogOverlay({
|
|
292
|
+
className,
|
|
293
|
+
...props
|
|
294
|
+
}) {
|
|
295
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
296
|
+
AlertDialogPrimitive__namespace.Overlay,
|
|
297
|
+
{
|
|
298
|
+
"data-slot": "alert-dialog-overlay",
|
|
299
|
+
className: cn(
|
|
300
|
+
"fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0",
|
|
301
|
+
className
|
|
302
|
+
),
|
|
303
|
+
...props
|
|
304
|
+
}
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
function AlertDialogContent({
|
|
308
|
+
className,
|
|
309
|
+
size = "default",
|
|
310
|
+
...props
|
|
311
|
+
}) {
|
|
312
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(AlertDialogPortal, { children: [
|
|
313
|
+
/* @__PURE__ */ jsxRuntime.jsx(AlertDialogOverlay, {}),
|
|
314
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
315
|
+
AlertDialogPrimitive__namespace.Content,
|
|
316
|
+
{
|
|
317
|
+
"data-slot": "alert-dialog-content",
|
|
318
|
+
"data-size": size,
|
|
319
|
+
className: cn(
|
|
320
|
+
"group/alert-dialog-content fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 data-[size=sm]:max-w-xs data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[size=default]:sm:max-w-lg",
|
|
321
|
+
className
|
|
322
|
+
),
|
|
323
|
+
...props
|
|
324
|
+
}
|
|
325
|
+
)
|
|
326
|
+
] });
|
|
327
|
+
}
|
|
328
|
+
function AlertDialogHeader({
|
|
329
|
+
className,
|
|
330
|
+
...props
|
|
331
|
+
}) {
|
|
332
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
333
|
+
"div",
|
|
334
|
+
{
|
|
335
|
+
"data-slot": "alert-dialog-header",
|
|
336
|
+
className: cn(
|
|
337
|
+
"flex flex-col gap-1.5 text-center sm:text-left",
|
|
338
|
+
className
|
|
339
|
+
),
|
|
340
|
+
...props
|
|
341
|
+
}
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
function AlertDialogFooter({
|
|
345
|
+
className,
|
|
346
|
+
...props
|
|
347
|
+
}) {
|
|
348
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
349
|
+
"div",
|
|
350
|
+
{
|
|
351
|
+
"data-slot": "alert-dialog-footer",
|
|
352
|
+
className: cn(
|
|
353
|
+
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
|
354
|
+
className
|
|
355
|
+
),
|
|
356
|
+
...props
|
|
357
|
+
}
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
function AlertDialogTitle({
|
|
361
|
+
className,
|
|
362
|
+
...props
|
|
363
|
+
}) {
|
|
364
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
365
|
+
AlertDialogPrimitive__namespace.Title,
|
|
366
|
+
{
|
|
367
|
+
"data-slot": "alert-dialog-title",
|
|
368
|
+
className: cn("text-lg font-semibold", className),
|
|
369
|
+
...props
|
|
370
|
+
}
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
function AlertDialogDescription({
|
|
374
|
+
className,
|
|
375
|
+
...props
|
|
376
|
+
}) {
|
|
377
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
378
|
+
AlertDialogPrimitive__namespace.Description,
|
|
379
|
+
{
|
|
380
|
+
"data-slot": "alert-dialog-description",
|
|
381
|
+
className: cn("text-sm text-muted-foreground", className),
|
|
382
|
+
...props
|
|
383
|
+
}
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
function AlertDialogAction({
|
|
387
|
+
className,
|
|
388
|
+
variant = "default",
|
|
389
|
+
size = "default",
|
|
390
|
+
...props
|
|
391
|
+
}) {
|
|
392
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Button, { variant, size, asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
393
|
+
AlertDialogPrimitive__namespace.Action,
|
|
394
|
+
{
|
|
395
|
+
"data-slot": "alert-dialog-action",
|
|
396
|
+
className: cn(className),
|
|
397
|
+
...props
|
|
398
|
+
}
|
|
399
|
+
) });
|
|
400
|
+
}
|
|
401
|
+
function AlertDialogCancel({
|
|
402
|
+
className,
|
|
403
|
+
variant = "outline",
|
|
404
|
+
size = "default",
|
|
405
|
+
...props
|
|
406
|
+
}) {
|
|
407
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Button, { variant, size, asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
408
|
+
AlertDialogPrimitive__namespace.Cancel,
|
|
409
|
+
{
|
|
410
|
+
"data-slot": "alert-dialog-cancel",
|
|
411
|
+
className: cn(className),
|
|
412
|
+
...props
|
|
413
|
+
}
|
|
414
|
+
) });
|
|
415
|
+
}
|
|
416
|
+
function ConfirmDestructiveDialog({
|
|
417
|
+
open,
|
|
418
|
+
onOpenChange,
|
|
419
|
+
title,
|
|
420
|
+
description,
|
|
421
|
+
confirmLabel = "Delete",
|
|
422
|
+
cancelLabel = "Cancel",
|
|
423
|
+
onConfirm
|
|
424
|
+
}) {
|
|
425
|
+
return /* @__PURE__ */ jsxRuntime.jsx(AlertDialog, { open, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(AlertDialogContent, { size: "sm", children: [
|
|
426
|
+
/* @__PURE__ */ jsxRuntime.jsxs(AlertDialogHeader, { children: [
|
|
427
|
+
/* @__PURE__ */ jsxRuntime.jsx(AlertDialogTitle, { children: title }),
|
|
428
|
+
/* @__PURE__ */ jsxRuntime.jsx(AlertDialogDescription, { children: description })
|
|
429
|
+
] }),
|
|
430
|
+
/* @__PURE__ */ jsxRuntime.jsxs(AlertDialogFooter, { children: [
|
|
431
|
+
/* @__PURE__ */ jsxRuntime.jsx(AlertDialogCancel, { children: cancelLabel }),
|
|
432
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
433
|
+
AlertDialogAction,
|
|
434
|
+
{
|
|
435
|
+
variant: "destructive",
|
|
436
|
+
onClick: () => {
|
|
437
|
+
onConfirm();
|
|
438
|
+
onOpenChange(false);
|
|
439
|
+
},
|
|
440
|
+
children: confirmLabel
|
|
441
|
+
}
|
|
442
|
+
)
|
|
443
|
+
] })
|
|
444
|
+
] }) });
|
|
445
|
+
}
|
|
446
|
+
var miniMenuContentClassName = "max-h-none overflow-x-visible overflow-y-visible";
|
|
447
|
+
var miniMenuContentDefaults = {
|
|
448
|
+
sideOffset: 8,
|
|
449
|
+
collisionPadding: 12
|
|
450
|
+
};
|
|
451
|
+
function DropdownMenu({
|
|
452
|
+
...props
|
|
453
|
+
}) {
|
|
454
|
+
return /* @__PURE__ */ jsxRuntime.jsx(DropdownMenuPrimitive__namespace.Root, { "data-slot": "dropdown-menu", ...props });
|
|
455
|
+
}
|
|
456
|
+
function DropdownMenuTrigger({
|
|
457
|
+
...props
|
|
458
|
+
}) {
|
|
459
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
460
|
+
DropdownMenuPrimitive__namespace.Trigger,
|
|
461
|
+
{
|
|
462
|
+
"data-slot": "dropdown-menu-trigger",
|
|
463
|
+
...props
|
|
464
|
+
}
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
function DropdownMenuContent({
|
|
468
|
+
className,
|
|
469
|
+
sideOffset = 4,
|
|
470
|
+
...props
|
|
471
|
+
}) {
|
|
472
|
+
return /* @__PURE__ */ jsxRuntime.jsx(DropdownMenuPrimitive__namespace.Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
473
|
+
DropdownMenuPrimitive__namespace.Content,
|
|
474
|
+
{
|
|
475
|
+
"data-slot": "dropdown-menu-content",
|
|
476
|
+
sideOffset,
|
|
477
|
+
className: cn(
|
|
478
|
+
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=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 z-[200] max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
|
|
479
|
+
className
|
|
480
|
+
),
|
|
481
|
+
...props
|
|
482
|
+
}
|
|
483
|
+
) });
|
|
484
|
+
}
|
|
485
|
+
function DropdownMenuItem({
|
|
486
|
+
className,
|
|
487
|
+
inset,
|
|
488
|
+
variant = "default",
|
|
489
|
+
...props
|
|
490
|
+
}) {
|
|
491
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
492
|
+
DropdownMenuPrimitive__namespace.Item,
|
|
493
|
+
{
|
|
494
|
+
"data-slot": "dropdown-menu-item",
|
|
495
|
+
"data-inset": inset,
|
|
496
|
+
"data-variant": variant,
|
|
497
|
+
className: cn(
|
|
498
|
+
"relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[highlighted]:[&_svg:not([class*='text-'])]:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:[&_svg:not([class*='text-'])]:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:data-[highlighted]:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 dark:data-[variant=destructive]:data-[highlighted]:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:data-[highlighted]:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
499
|
+
className
|
|
500
|
+
),
|
|
501
|
+
...props
|
|
502
|
+
}
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
var DEFAULT_NOTIFICATION_BELL_LABELS = {
|
|
506
|
+
title: "Notifications",
|
|
507
|
+
empty: "You're all caught up.",
|
|
508
|
+
viewAll: "View all"
|
|
509
|
+
};
|
|
510
|
+
function formatUnreadBadge(count) {
|
|
511
|
+
return count > 9 ? "9+" : String(count);
|
|
512
|
+
}
|
|
513
|
+
function defaultFormatTime(iso) {
|
|
514
|
+
const date = new Date(iso);
|
|
515
|
+
if (Number.isNaN(date.getTime())) return iso;
|
|
516
|
+
return date.toLocaleString(void 0, {
|
|
517
|
+
dateStyle: "short",
|
|
518
|
+
timeStyle: "short"
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
function NotificationListSkeleton({ rows = 3 }) {
|
|
522
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col", "aria-hidden": "true", children: Array.from({ length: rows }, (_, index) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
523
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2 px-4 py-3", children: [
|
|
524
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
525
|
+
"div",
|
|
526
|
+
{
|
|
527
|
+
"data-slot": "skeleton",
|
|
528
|
+
className: "bg-muted h-4 w-36 animate-pulse rounded-md motion-reduce:animate-none"
|
|
529
|
+
}
|
|
530
|
+
),
|
|
531
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
532
|
+
"div",
|
|
533
|
+
{
|
|
534
|
+
"data-slot": "skeleton",
|
|
535
|
+
className: "bg-muted h-3 w-full animate-pulse rounded-md motion-reduce:animate-none"
|
|
536
|
+
}
|
|
537
|
+
),
|
|
538
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
539
|
+
"div",
|
|
540
|
+
{
|
|
541
|
+
"data-slot": "skeleton",
|
|
542
|
+
className: "bg-muted h-3 w-20 animate-pulse rounded-md motion-reduce:animate-none"
|
|
543
|
+
}
|
|
544
|
+
)
|
|
545
|
+
] }),
|
|
546
|
+
index < rows - 1 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-border h-px" }) : null
|
|
547
|
+
] }, index)) });
|
|
548
|
+
}
|
|
549
|
+
function NotificationBell({
|
|
550
|
+
items,
|
|
551
|
+
unreadCount,
|
|
552
|
+
isLoading = false,
|
|
553
|
+
labels: labelOverrides,
|
|
554
|
+
viewAllHref,
|
|
555
|
+
formatTime = defaultFormatTime,
|
|
556
|
+
onOpenChange,
|
|
557
|
+
align = "end",
|
|
558
|
+
className
|
|
559
|
+
}) {
|
|
560
|
+
const labels = { ...DEFAULT_NOTIFICATION_BELL_LABELS, ...labelOverrides };
|
|
561
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(DropdownMenu, { modal: false, onOpenChange, children: [
|
|
562
|
+
/* @__PURE__ */ jsxRuntime.jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
563
|
+
Button,
|
|
564
|
+
{
|
|
565
|
+
type: "button",
|
|
566
|
+
variant: "ghost",
|
|
567
|
+
size: "icon",
|
|
568
|
+
className: cn("relative size-9", className),
|
|
569
|
+
"aria-label": labels.title,
|
|
570
|
+
children: [
|
|
571
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Bell, { className: "size-4" }),
|
|
572
|
+
unreadCount > 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "bg-destructive text-destructive-foreground absolute -top-0.5 -right-0.5 flex size-4 items-center justify-center rounded-full text-[10px] leading-none font-medium tabular-nums", children: formatUnreadBadge(unreadCount) }) : null
|
|
573
|
+
]
|
|
574
|
+
}
|
|
575
|
+
) }),
|
|
576
|
+
/* @__PURE__ */ jsxRuntime.jsxs(DropdownMenuContent, { align, className: "w-80 p-0", children: [
|
|
577
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-between border-b px-4 py-3", children: /* @__PURE__ */ jsxRuntime.jsx("h4", { className: "font-semibold", children: labels.title }) }),
|
|
578
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-h-80 overflow-y-auto", children: isLoading ? /* @__PURE__ */ jsxRuntime.jsx(NotificationListSkeleton, {}) : items.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground px-4 py-8 text-center text-sm", children: labels.empty }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col", children: items.map((item, index) => {
|
|
579
|
+
const body = /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 space-y-1", children: [
|
|
580
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm leading-none font-medium", children: item.title }),
|
|
581
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground line-clamp-2 text-xs", children: item.message }),
|
|
582
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground/70 text-xs", children: formatTime(item.createdAt) })
|
|
583
|
+
] });
|
|
584
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
585
|
+
item.href ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
586
|
+
"a",
|
|
587
|
+
{
|
|
588
|
+
href: item.href,
|
|
589
|
+
className: cn(
|
|
590
|
+
"hover:bg-muted/50 flex gap-3 px-4 py-3 motion-safe:transition-colors",
|
|
591
|
+
!item.read && "bg-muted/30"
|
|
592
|
+
),
|
|
593
|
+
children: body
|
|
594
|
+
}
|
|
595
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
596
|
+
"div",
|
|
597
|
+
{
|
|
598
|
+
className: cn(
|
|
599
|
+
"flex gap-3 px-4 py-3",
|
|
600
|
+
!item.read && "bg-muted/30"
|
|
601
|
+
),
|
|
602
|
+
children: body
|
|
603
|
+
}
|
|
604
|
+
),
|
|
605
|
+
index < items.length - 1 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-border h-px" }) : null
|
|
606
|
+
] }, item.id);
|
|
607
|
+
}) }) }),
|
|
608
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t px-4 py-2", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
609
|
+
"a",
|
|
610
|
+
{
|
|
611
|
+
href: viewAllHref,
|
|
612
|
+
className: "text-primary block text-center text-sm font-medium hover:underline",
|
|
613
|
+
children: labels.viewAll
|
|
614
|
+
}
|
|
615
|
+
) })
|
|
616
|
+
] })
|
|
617
|
+
] });
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// src/components/editable/types.ts
|
|
621
|
+
var DEFAULT_EDITABLE_SECTION_LABELS = {
|
|
622
|
+
edit: "Edit",
|
|
623
|
+
save: "Save",
|
|
624
|
+
saving: "Saving\u2026",
|
|
625
|
+
cancel: "Cancel",
|
|
626
|
+
saved: "Saved",
|
|
627
|
+
discardTitle: "Discard changes?",
|
|
628
|
+
discardDescription: "If you leave now, unsaved changes will be lost.",
|
|
629
|
+
discardConfirm: "Discard",
|
|
630
|
+
discardCancel: "Keep editing"
|
|
631
|
+
};
|
|
632
|
+
|
|
633
|
+
// src/components/editable/useEditableSection.ts
|
|
634
|
+
function focusFirstField(container) {
|
|
635
|
+
if (!container) return;
|
|
636
|
+
const target = container.querySelector(
|
|
637
|
+
"[data-autofocus], input:not([type='hidden']):not([disabled]), select:not([disabled]), textarea:not([disabled]), [contenteditable='true']"
|
|
638
|
+
);
|
|
639
|
+
target?.focus();
|
|
640
|
+
}
|
|
641
|
+
function useEditableSection({
|
|
642
|
+
onSave,
|
|
643
|
+
onDiscard,
|
|
644
|
+
isDirty = false,
|
|
645
|
+
labelledBy,
|
|
646
|
+
title,
|
|
647
|
+
labels,
|
|
648
|
+
stableHeight = false
|
|
649
|
+
}) {
|
|
650
|
+
const resolvedLabels = {
|
|
651
|
+
...DEFAULT_EDITABLE_SECTION_LABELS,
|
|
652
|
+
...labels
|
|
653
|
+
};
|
|
654
|
+
const [mode, setMode] = React.useState("reading");
|
|
655
|
+
const [status, setStatus] = React.useState(null);
|
|
656
|
+
const [confirmOpen, setConfirmOpen] = React.useState(false);
|
|
657
|
+
const [minHeightPx, setMinHeightPx] = React.useState(void 0);
|
|
658
|
+
const contentRef = React.useRef(null);
|
|
659
|
+
const editButtonRef = React.useRef(null);
|
|
660
|
+
const measuredHeightRef = React.useRef(0);
|
|
661
|
+
const shouldFocusEditRef = React.useRef(false);
|
|
662
|
+
const shouldFocusFirstFieldRef = React.useRef(false);
|
|
663
|
+
const editButtonId = React.useId();
|
|
664
|
+
const contentFormId = React.useId();
|
|
665
|
+
const isEditing = mode === "editing" || mode === "saving";
|
|
666
|
+
const isSaving = mode === "saving";
|
|
667
|
+
React.useEffect(() => {
|
|
668
|
+
if (mode === "reading" && shouldFocusEditRef.current) {
|
|
669
|
+
shouldFocusEditRef.current = false;
|
|
670
|
+
editButtonRef.current?.focus();
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
if (mode === "editing" && shouldFocusFirstFieldRef.current) {
|
|
674
|
+
shouldFocusFirstFieldRef.current = false;
|
|
675
|
+
const invalid = contentRef.current?.querySelector(
|
|
676
|
+
"[aria-invalid='true']"
|
|
677
|
+
);
|
|
678
|
+
if (invalid) {
|
|
679
|
+
invalid.focus();
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
focusFirstField(contentRef.current);
|
|
683
|
+
}
|
|
684
|
+
}, [mode]);
|
|
685
|
+
const exitToReading = React.useCallback(() => {
|
|
686
|
+
setConfirmOpen(false);
|
|
687
|
+
shouldFocusEditRef.current = true;
|
|
688
|
+
setMode("reading");
|
|
689
|
+
onDiscard?.();
|
|
690
|
+
}, [onDiscard]);
|
|
691
|
+
const startEdit = React.useCallback(() => {
|
|
692
|
+
setStatus(null);
|
|
693
|
+
shouldFocusFirstFieldRef.current = true;
|
|
694
|
+
setMode("editing");
|
|
695
|
+
}, []);
|
|
696
|
+
const requestCancel = React.useCallback(() => {
|
|
697
|
+
if (isSaving) return;
|
|
698
|
+
if (isDirty) {
|
|
699
|
+
setConfirmOpen(true);
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
exitToReading();
|
|
703
|
+
}, [exitToReading, isDirty, isSaving]);
|
|
704
|
+
const handleSubmit = React.useCallback(
|
|
705
|
+
(event) => {
|
|
706
|
+
event.preventDefault();
|
|
707
|
+
if (isSaving) return;
|
|
708
|
+
void (async () => {
|
|
709
|
+
setMode("saving");
|
|
710
|
+
setStatus(null);
|
|
711
|
+
try {
|
|
712
|
+
await onSave();
|
|
713
|
+
setStatus(resolvedLabels.saved);
|
|
714
|
+
shouldFocusEditRef.current = true;
|
|
715
|
+
setMode("reading");
|
|
716
|
+
} catch {
|
|
717
|
+
shouldFocusFirstFieldRef.current = true;
|
|
718
|
+
setMode("editing");
|
|
719
|
+
}
|
|
720
|
+
})();
|
|
721
|
+
},
|
|
722
|
+
[isSaving, onSave, resolvedLabels.saved]
|
|
723
|
+
);
|
|
724
|
+
const handleFormKeyDown = React.useCallback(
|
|
725
|
+
(event) => {
|
|
726
|
+
if (event.key !== "Escape") return;
|
|
727
|
+
event.preventDefault();
|
|
728
|
+
requestCancel();
|
|
729
|
+
},
|
|
730
|
+
[requestCancel]
|
|
731
|
+
);
|
|
732
|
+
React.useEffect(() => {
|
|
733
|
+
if (!stableHeight || !isEditing) return;
|
|
734
|
+
const el = contentRef.current;
|
|
735
|
+
if (!el) return;
|
|
736
|
+
const measure = () => {
|
|
737
|
+
const height = el.getBoundingClientRect().height;
|
|
738
|
+
if (height > measuredHeightRef.current) {
|
|
739
|
+
measuredHeightRef.current = height;
|
|
740
|
+
setMinHeightPx(height);
|
|
741
|
+
}
|
|
742
|
+
};
|
|
743
|
+
measure();
|
|
744
|
+
const observer = new ResizeObserver(measure);
|
|
745
|
+
observer.observe(el);
|
|
746
|
+
return () => observer.disconnect();
|
|
747
|
+
}, [isEditing, stableHeight]);
|
|
748
|
+
const editButtonNameProps = labelledBy != null ? { "aria-labelledby": `${labelledBy} ${editButtonId}` } : title != null && title.length > 0 ? { "aria-label": `${resolvedLabels.edit} ${title}` } : { "aria-label": resolvedLabels.edit };
|
|
749
|
+
return {
|
|
750
|
+
mode,
|
|
751
|
+
isEditing,
|
|
752
|
+
isSaving,
|
|
753
|
+
startEdit,
|
|
754
|
+
requestCancel,
|
|
755
|
+
formProps: {
|
|
756
|
+
onSubmit: handleSubmit,
|
|
757
|
+
onKeyDown: handleFormKeyDown,
|
|
758
|
+
noValidate: true
|
|
759
|
+
},
|
|
760
|
+
contentRef,
|
|
761
|
+
editButtonRef,
|
|
762
|
+
contentFormId,
|
|
763
|
+
editButtonProps: {
|
|
764
|
+
type: "button",
|
|
765
|
+
id: editButtonId,
|
|
766
|
+
onClick: startEdit,
|
|
767
|
+
ref: editButtonRef,
|
|
768
|
+
...editButtonNameProps
|
|
769
|
+
},
|
|
770
|
+
saveButtonProps: {
|
|
771
|
+
type: "submit",
|
|
772
|
+
disabled: isSaving
|
|
773
|
+
},
|
|
774
|
+
cancelButtonProps: {
|
|
775
|
+
type: "button",
|
|
776
|
+
onClick: requestCancel,
|
|
777
|
+
disabled: isSaving
|
|
778
|
+
},
|
|
779
|
+
status,
|
|
780
|
+
statusProps: {
|
|
781
|
+
role: "status",
|
|
782
|
+
"aria-live": "polite",
|
|
783
|
+
className: "sr-only"
|
|
784
|
+
},
|
|
785
|
+
confirmDiscard: {
|
|
786
|
+
open: confirmOpen,
|
|
787
|
+
onOpenChange: setConfirmOpen,
|
|
788
|
+
title: resolvedLabels.discardTitle,
|
|
789
|
+
description: resolvedLabels.discardDescription,
|
|
790
|
+
confirmLabel: resolvedLabels.discardConfirm,
|
|
791
|
+
cancelLabel: resolvedLabels.discardCancel,
|
|
792
|
+
onConfirm: exitToReading
|
|
793
|
+
},
|
|
794
|
+
resolvedLabels,
|
|
795
|
+
contentStyle: stableHeight && minHeightPx != null ? { minHeight: minHeightPx } : void 0,
|
|
796
|
+
actionsClassName: "flex min-h-9 min-w-[11rem] flex-wrap items-center justify-end gap-2"
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
function EditableSection({
|
|
800
|
+
readView,
|
|
801
|
+
editView,
|
|
802
|
+
children,
|
|
803
|
+
className,
|
|
804
|
+
...options
|
|
805
|
+
}) {
|
|
806
|
+
const section = useEditableSection(options);
|
|
807
|
+
const {
|
|
808
|
+
isEditing,
|
|
809
|
+
isSaving,
|
|
810
|
+
formProps,
|
|
811
|
+
contentRef,
|
|
812
|
+
contentFormId,
|
|
813
|
+
editButtonProps,
|
|
814
|
+
saveButtonProps,
|
|
815
|
+
cancelButtonProps,
|
|
816
|
+
status,
|
|
817
|
+
statusProps,
|
|
818
|
+
confirmDiscard,
|
|
819
|
+
resolvedLabels,
|
|
820
|
+
contentStyle,
|
|
821
|
+
actionsClassName,
|
|
822
|
+
mode
|
|
823
|
+
} = section;
|
|
824
|
+
const actionButtons = !isEditing ? /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "outline", size: "sm", ...editButtonProps, children: resolvedLabels.edit }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
825
|
+
/* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "outline", size: "sm", ...cancelButtonProps, children: resolvedLabels.cancel }),
|
|
826
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
827
|
+
Button,
|
|
828
|
+
{
|
|
829
|
+
size: "sm",
|
|
830
|
+
type: "submit",
|
|
831
|
+
form: contentFormId,
|
|
832
|
+
disabled: saveButtonProps.disabled,
|
|
833
|
+
children: isSaving ? resolvedLabels.saving : resolvedLabels.save
|
|
834
|
+
}
|
|
835
|
+
)
|
|
836
|
+
] });
|
|
837
|
+
const actions = /* @__PURE__ */ jsxRuntime.jsx("div", { "data-slot": "editable-section-actions", className: actionsClassName, children: actionButtons });
|
|
838
|
+
const content = isEditing ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
839
|
+
"form",
|
|
840
|
+
{
|
|
841
|
+
id: contentFormId,
|
|
842
|
+
ref: contentRef,
|
|
843
|
+
"data-slot": "editable-section-content",
|
|
844
|
+
className: cn("yenio-editable-section-content space-y-4"),
|
|
845
|
+
style: contentStyle,
|
|
846
|
+
...formProps,
|
|
847
|
+
children: editView()
|
|
848
|
+
}
|
|
849
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
850
|
+
"div",
|
|
851
|
+
{
|
|
852
|
+
ref: contentRef,
|
|
853
|
+
"data-slot": "editable-section-content",
|
|
854
|
+
className: "yenio-editable-section-content",
|
|
855
|
+
style: contentStyle,
|
|
856
|
+
children: readView()
|
|
857
|
+
}
|
|
858
|
+
);
|
|
859
|
+
const statusNode = /* @__PURE__ */ jsxRuntime.jsx("span", { ...statusProps, "data-slot": "editable-section-status", children: status });
|
|
860
|
+
const dialog = /* @__PURE__ */ jsxRuntime.jsx(
|
|
861
|
+
ConfirmDestructiveDialog,
|
|
862
|
+
{
|
|
863
|
+
open: confirmDiscard.open,
|
|
864
|
+
onOpenChange: confirmDiscard.onOpenChange,
|
|
865
|
+
title: confirmDiscard.title,
|
|
866
|
+
description: confirmDiscard.description,
|
|
867
|
+
confirmLabel: confirmDiscard.confirmLabel,
|
|
868
|
+
cancelLabel: confirmDiscard.cancelLabel,
|
|
869
|
+
onConfirm: confirmDiscard.onConfirm
|
|
870
|
+
}
|
|
871
|
+
);
|
|
872
|
+
if (children) {
|
|
873
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
874
|
+
children({
|
|
875
|
+
actions,
|
|
876
|
+
content,
|
|
877
|
+
status: statusNode,
|
|
878
|
+
mode,
|
|
879
|
+
isSaving,
|
|
880
|
+
isEditing
|
|
881
|
+
}),
|
|
882
|
+
dialog
|
|
883
|
+
] });
|
|
884
|
+
}
|
|
885
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-slot": "editable-section", className: cn("space-y-4", className), children: [
|
|
886
|
+
actions,
|
|
887
|
+
content,
|
|
888
|
+
statusNode,
|
|
889
|
+
dialog
|
|
890
|
+
] });
|
|
891
|
+
}
|
|
892
|
+
function DataList({ className, ...props }) {
|
|
893
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
894
|
+
"dl",
|
|
895
|
+
{
|
|
896
|
+
"data-slot": "data-list",
|
|
897
|
+
className: cn("grid gap-4 sm:grid-cols-2", className),
|
|
898
|
+
...props
|
|
899
|
+
}
|
|
900
|
+
);
|
|
901
|
+
}
|
|
902
|
+
function DataListItem({
|
|
903
|
+
label,
|
|
904
|
+
children,
|
|
905
|
+
empty = "\u2014",
|
|
906
|
+
className,
|
|
907
|
+
labelClassName,
|
|
908
|
+
valueClassName
|
|
909
|
+
}) {
|
|
910
|
+
const isEmpty = children == null || children === false || typeof children === "string" && children.trim().length === 0;
|
|
911
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
912
|
+
"div",
|
|
913
|
+
{
|
|
914
|
+
"data-slot": "data-list-item",
|
|
915
|
+
className: cn("flex flex-col gap-1.5", className),
|
|
916
|
+
children: [
|
|
917
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
918
|
+
"dt",
|
|
919
|
+
{
|
|
920
|
+
className: cn(
|
|
921
|
+
"text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
|
922
|
+
labelClassName
|
|
923
|
+
),
|
|
924
|
+
children: label
|
|
925
|
+
}
|
|
926
|
+
),
|
|
927
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
928
|
+
"dd",
|
|
929
|
+
{
|
|
930
|
+
className: cn(
|
|
931
|
+
"text-sm wrap-break-word",
|
|
932
|
+
isEmpty && "text-muted-foreground",
|
|
933
|
+
valueClassName
|
|
934
|
+
),
|
|
935
|
+
children: isEmpty ? empty : children
|
|
936
|
+
}
|
|
937
|
+
)
|
|
938
|
+
]
|
|
939
|
+
}
|
|
940
|
+
);
|
|
941
|
+
}
|
|
942
|
+
function FieldMessage({
|
|
943
|
+
id,
|
|
944
|
+
children,
|
|
945
|
+
className,
|
|
946
|
+
...props
|
|
947
|
+
}) {
|
|
948
|
+
const hasMessage = children != null && children !== false && !(typeof children === "string" && children.trim().length === 0);
|
|
949
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
950
|
+
"p",
|
|
951
|
+
{
|
|
952
|
+
id,
|
|
953
|
+
role: hasMessage ? "alert" : void 0,
|
|
954
|
+
"data-slot": "field-message",
|
|
955
|
+
className: cn("min-h-5 text-xs text-destructive", className),
|
|
956
|
+
...props,
|
|
957
|
+
children: hasMessage ? children : "\xA0"
|
|
958
|
+
}
|
|
959
|
+
);
|
|
960
|
+
}
|
|
961
|
+
function isBrokenImage(img) {
|
|
962
|
+
return img.complete && img.naturalWidth === 0;
|
|
963
|
+
}
|
|
964
|
+
function SafeImage({
|
|
965
|
+
src,
|
|
966
|
+
fallback,
|
|
967
|
+
alt = "",
|
|
968
|
+
onError,
|
|
969
|
+
...imgProps
|
|
970
|
+
}) {
|
|
971
|
+
const [failed, setFailed] = React.useState(false);
|
|
972
|
+
const imgRef = React.useRef(null);
|
|
973
|
+
const resolvedSrc = typeof src === "string" && src.length > 0 ? src : null;
|
|
974
|
+
React.useLayoutEffect(() => {
|
|
975
|
+
setFailed(false);
|
|
976
|
+
}, [resolvedSrc]);
|
|
977
|
+
React.useLayoutEffect(() => {
|
|
978
|
+
const img = imgRef.current;
|
|
979
|
+
if (img && isBrokenImage(img)) {
|
|
980
|
+
setFailed(true);
|
|
981
|
+
}
|
|
982
|
+
}, [resolvedSrc, failed]);
|
|
983
|
+
if (!resolvedSrc || failed) {
|
|
984
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fallback });
|
|
985
|
+
}
|
|
986
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
987
|
+
"img",
|
|
988
|
+
{
|
|
989
|
+
...imgProps,
|
|
990
|
+
ref: imgRef,
|
|
991
|
+
src: resolvedSrc,
|
|
992
|
+
alt,
|
|
993
|
+
onError: (event) => {
|
|
994
|
+
setFailed(true);
|
|
995
|
+
onError?.(event);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
);
|
|
999
|
+
}
|
|
1000
|
+
var defaultLabels = {
|
|
1001
|
+
theme: "Theme",
|
|
1002
|
+
system: "System",
|
|
1003
|
+
light: "Light",
|
|
1004
|
+
dark: "Dark"
|
|
1005
|
+
};
|
|
1006
|
+
var sizeClasses = {
|
|
1007
|
+
sm: { button: "size-8", icon: "size-4" },
|
|
1008
|
+
md: { button: "size-8", icon: "size-4" },
|
|
1009
|
+
lg: { button: "size-9", icon: "size-4" }
|
|
1010
|
+
};
|
|
1011
|
+
var shapeClasses = {
|
|
1012
|
+
rounded: "rounded-md",
|
|
1013
|
+
pill: "rounded-full"
|
|
1014
|
+
};
|
|
1015
|
+
var iconTriggerClassName = "border border-border bg-transparent text-muted-foreground shadow-none hover:bg-accent hover:text-foreground dark:border-border dark:bg-transparent dark:text-muted-foreground dark:hover:bg-accent/50";
|
|
1016
|
+
var themeIcons = {
|
|
1017
|
+
system: lucideReact.Monitor,
|
|
1018
|
+
light: lucideReact.Sun,
|
|
1019
|
+
dark: lucideReact.Moon
|
|
1020
|
+
};
|
|
1021
|
+
var themes = [
|
|
1022
|
+
{ key: "system", icon: themeIcons.system },
|
|
1023
|
+
{ key: "light", icon: themeIcons.light },
|
|
1024
|
+
{ key: "dark", icon: themeIcons.dark }
|
|
1025
|
+
];
|
|
1026
|
+
function storedTheme(value) {
|
|
1027
|
+
if (value === "system" || value === "light" || value === "dark") return value;
|
|
1028
|
+
return void 0;
|
|
1029
|
+
}
|
|
1030
|
+
function ThemeSwitcher({
|
|
1031
|
+
className,
|
|
1032
|
+
size = "sm",
|
|
1033
|
+
shape = "rounded",
|
|
1034
|
+
align = "end",
|
|
1035
|
+
side = "bottom",
|
|
1036
|
+
labels: userLabels
|
|
1037
|
+
}) {
|
|
1038
|
+
const { theme, setTheme } = nextThemes.useTheme();
|
|
1039
|
+
const [mounted, setMounted] = React.useState(false);
|
|
1040
|
+
const sizes = sizeClasses[size];
|
|
1041
|
+
const shapeClass = shapeClasses[shape];
|
|
1042
|
+
const labels = { ...defaultLabels, ...userLabels };
|
|
1043
|
+
const handleThemeClick = React.useCallback(
|
|
1044
|
+
(themeKey) => {
|
|
1045
|
+
setTheme(themeKey);
|
|
1046
|
+
},
|
|
1047
|
+
[setTheme]
|
|
1048
|
+
);
|
|
1049
|
+
React.useEffect(() => {
|
|
1050
|
+
setMounted(true);
|
|
1051
|
+
}, []);
|
|
1052
|
+
if (!mounted) {
|
|
1053
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1054
|
+
"div",
|
|
1055
|
+
{
|
|
1056
|
+
"aria-hidden": true,
|
|
1057
|
+
className: cn(
|
|
1058
|
+
sizes.button,
|
|
1059
|
+
shapeClass,
|
|
1060
|
+
iconTriggerClassName,
|
|
1061
|
+
className
|
|
1062
|
+
)
|
|
1063
|
+
}
|
|
1064
|
+
);
|
|
1065
|
+
}
|
|
1066
|
+
const currentTheme = storedTheme(theme);
|
|
1067
|
+
const TriggerIcon = currentTheme ? themeIcons[currentTheme] : lucideReact.Monitor;
|
|
1068
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(DropdownMenu, { modal: false, children: [
|
|
1069
|
+
/* @__PURE__ */ jsxRuntime.jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1070
|
+
Button,
|
|
1071
|
+
{
|
|
1072
|
+
"data-testid": "theme-switcher",
|
|
1073
|
+
variant: "outline",
|
|
1074
|
+
size: "icon",
|
|
1075
|
+
"aria-label": labels.theme,
|
|
1076
|
+
className: cn(
|
|
1077
|
+
sizes.button,
|
|
1078
|
+
shapeClass,
|
|
1079
|
+
iconTriggerClassName,
|
|
1080
|
+
className
|
|
1081
|
+
),
|
|
1082
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(TriggerIcon, { className: sizes.icon })
|
|
1083
|
+
}
|
|
1084
|
+
) }),
|
|
1085
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1086
|
+
DropdownMenuContent,
|
|
1087
|
+
{
|
|
1088
|
+
side,
|
|
1089
|
+
align,
|
|
1090
|
+
sideOffset: miniMenuContentDefaults.sideOffset,
|
|
1091
|
+
collisionPadding: miniMenuContentDefaults.collisionPadding,
|
|
1092
|
+
className: miniMenuContentClassName,
|
|
1093
|
+
children: themes.map(({ key, icon: Icon }) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1094
|
+
DropdownMenuItem,
|
|
1095
|
+
{
|
|
1096
|
+
onClick: () => handleThemeClick(key),
|
|
1097
|
+
className: cn(
|
|
1098
|
+
"gap-2 cursor-pointer",
|
|
1099
|
+
currentTheme === key && "bg-accent text-accent-foreground [&_svg]:text-accent-foreground"
|
|
1100
|
+
),
|
|
1101
|
+
children: [
|
|
1102
|
+
/* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "size-4" }),
|
|
1103
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: labels[key] })
|
|
1104
|
+
]
|
|
1105
|
+
},
|
|
1106
|
+
key
|
|
1107
|
+
))
|
|
1108
|
+
}
|
|
1109
|
+
)
|
|
1110
|
+
] });
|
|
1111
|
+
}
|
|
1112
|
+
var defaultLanguages = [
|
|
1113
|
+
{ key: "es-MX", label: "ES", nativeName: "Espa\xF1ol (M\xE9xico)" },
|
|
1114
|
+
{ key: "en", label: "EN", nativeName: "English" }
|
|
1115
|
+
];
|
|
1116
|
+
var sizeClasses2 = {
|
|
1117
|
+
sm: { button: "size-8", icon: "size-4" },
|
|
1118
|
+
md: { button: "size-8", icon: "size-4" },
|
|
1119
|
+
lg: { button: "size-9", icon: "size-4" }
|
|
1120
|
+
};
|
|
1121
|
+
var shapeClasses2 = {
|
|
1122
|
+
rounded: "rounded-md",
|
|
1123
|
+
squared: "rounded-sm",
|
|
1124
|
+
pill: "rounded-full"
|
|
1125
|
+
};
|
|
1126
|
+
var iconTriggerClassName2 = "border border-border bg-transparent text-muted-foreground shadow-none hover:bg-accent hover:text-foreground dark:border-border dark:bg-transparent dark:text-muted-foreground dark:hover:bg-accent/50";
|
|
1127
|
+
function LanguageSwitcher({
|
|
1128
|
+
languages: propLanguages,
|
|
1129
|
+
currentLanguage: propCurrentLanguage,
|
|
1130
|
+
onLanguageChange: propOnLanguageChange,
|
|
1131
|
+
labels = {},
|
|
1132
|
+
className,
|
|
1133
|
+
size = "sm",
|
|
1134
|
+
shape = "rounded",
|
|
1135
|
+
align = "end",
|
|
1136
|
+
side = "bottom"
|
|
1137
|
+
}) {
|
|
1138
|
+
const context = useLanguageContext();
|
|
1139
|
+
const contextLanguages = context?.languages?.map((lang) => ({
|
|
1140
|
+
key: lang.code,
|
|
1141
|
+
label: lang.code.toUpperCase(),
|
|
1142
|
+
nativeName: lang.name
|
|
1143
|
+
}));
|
|
1144
|
+
const languages = propLanguages ?? contextLanguages ?? defaultLanguages;
|
|
1145
|
+
const currentLanguage = propCurrentLanguage ?? context?.language ?? "en";
|
|
1146
|
+
const onLanguageChange = propOnLanguageChange ?? context?.setLanguage;
|
|
1147
|
+
const sizes = sizeClasses2[size];
|
|
1148
|
+
const shapeClass = shapeClasses2[shape];
|
|
1149
|
+
const accessibleName = labels.language ?? "Language";
|
|
1150
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(DropdownMenu, { modal: false, children: [
|
|
1151
|
+
/* @__PURE__ */ jsxRuntime.jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1152
|
+
Button,
|
|
1153
|
+
{
|
|
1154
|
+
variant: "outline",
|
|
1155
|
+
size: "icon",
|
|
1156
|
+
"aria-label": accessibleName,
|
|
1157
|
+
className: cn(
|
|
1158
|
+
sizes.button,
|
|
1159
|
+
shapeClass,
|
|
1160
|
+
iconTriggerClassName2,
|
|
1161
|
+
className
|
|
1162
|
+
),
|
|
1163
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Languages, { className: sizes.icon })
|
|
1164
|
+
}
|
|
1165
|
+
) }),
|
|
1166
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1167
|
+
DropdownMenuContent,
|
|
1168
|
+
{
|
|
1169
|
+
side,
|
|
1170
|
+
align,
|
|
1171
|
+
sideOffset: miniMenuContentDefaults.sideOffset,
|
|
1172
|
+
collisionPadding: miniMenuContentDefaults.collisionPadding,
|
|
1173
|
+
className: cn(miniMenuContentClassName, "min-w-0 w-auto"),
|
|
1174
|
+
children: languages.map(({ key, nativeName }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1175
|
+
DropdownMenuItem,
|
|
1176
|
+
{
|
|
1177
|
+
onClick: () => onLanguageChange?.(key),
|
|
1178
|
+
className: cn(
|
|
1179
|
+
"justify-center text-xs font-semibold cursor-pointer px-3",
|
|
1180
|
+
currentLanguage === key && "bg-accent text-accent-foreground"
|
|
1181
|
+
),
|
|
1182
|
+
children: nativeName
|
|
1183
|
+
},
|
|
1184
|
+
key
|
|
1185
|
+
))
|
|
1186
|
+
}
|
|
1187
|
+
)
|
|
1188
|
+
] });
|
|
1189
|
+
}
|
|
1190
|
+
function Slider({
|
|
1191
|
+
className,
|
|
1192
|
+
defaultValue,
|
|
1193
|
+
value,
|
|
1194
|
+
min = 0,
|
|
1195
|
+
max = 100,
|
|
1196
|
+
...props
|
|
1197
|
+
}) {
|
|
1198
|
+
const _values = React__namespace.useMemo(
|
|
1199
|
+
() => Array.isArray(value) ? value : Array.isArray(defaultValue) ? defaultValue : [min, max],
|
|
1200
|
+
[value, defaultValue, min, max]
|
|
1201
|
+
);
|
|
1202
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1203
|
+
SliderPrimitive__namespace.Root,
|
|
1204
|
+
{
|
|
1205
|
+
"data-slot": "slider",
|
|
1206
|
+
defaultValue,
|
|
1207
|
+
value,
|
|
1208
|
+
min,
|
|
1209
|
+
max,
|
|
1210
|
+
className: cn(
|
|
1211
|
+
"relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col",
|
|
1212
|
+
className
|
|
1213
|
+
),
|
|
1214
|
+
...props,
|
|
1215
|
+
children: [
|
|
1216
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1217
|
+
SliderPrimitive__namespace.Track,
|
|
1218
|
+
{
|
|
1219
|
+
"data-slot": "slider-track",
|
|
1220
|
+
className: cn(
|
|
1221
|
+
"bg-muted relative grow overflow-hidden rounded-full data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5"
|
|
1222
|
+
),
|
|
1223
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1224
|
+
SliderPrimitive__namespace.Range,
|
|
1225
|
+
{
|
|
1226
|
+
"data-slot": "slider-range",
|
|
1227
|
+
className: cn(
|
|
1228
|
+
"bg-primary absolute data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full"
|
|
1229
|
+
)
|
|
1230
|
+
}
|
|
1231
|
+
)
|
|
1232
|
+
}
|
|
1233
|
+
),
|
|
1234
|
+
Array.from({ length: _values.length }, (_, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1235
|
+
SliderPrimitive__namespace.Thumb,
|
|
1236
|
+
{
|
|
1237
|
+
"data-slot": "slider-thumb",
|
|
1238
|
+
className: "border-primary ring-ring/50 block size-4 shrink-0 rounded-full border bg-white shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50"
|
|
1239
|
+
},
|
|
1240
|
+
index
|
|
1241
|
+
))
|
|
1242
|
+
]
|
|
1243
|
+
}
|
|
1244
|
+
);
|
|
1245
|
+
}
|
|
1246
|
+
var toggleVariants = classVarianceAuthority.cva(
|
|
1247
|
+
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-muted hover:text-muted-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] outline-none transition-[color,box-shadow] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive whitespace-nowrap",
|
|
1248
|
+
{
|
|
1249
|
+
variants: {
|
|
1250
|
+
variant: {
|
|
1251
|
+
default: "bg-transparent",
|
|
1252
|
+
outline: "border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground"
|
|
1253
|
+
},
|
|
1254
|
+
size: {
|
|
1255
|
+
default: "h-9 px-2 min-w-9",
|
|
1256
|
+
sm: "h-8 px-1.5 min-w-8",
|
|
1257
|
+
lg: "h-10 px-2.5 min-w-10"
|
|
1258
|
+
}
|
|
1259
|
+
},
|
|
1260
|
+
defaultVariants: {
|
|
1261
|
+
variant: "default",
|
|
1262
|
+
size: "default"
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
);
|
|
1266
|
+
function Toggle({
|
|
1267
|
+
className,
|
|
1268
|
+
variant,
|
|
1269
|
+
size,
|
|
1270
|
+
...props
|
|
1271
|
+
}) {
|
|
1272
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1273
|
+
TogglePrimitive__namespace.Root,
|
|
1274
|
+
{
|
|
1275
|
+
"data-slot": "toggle",
|
|
1276
|
+
className: cn(toggleVariants({ variant, size, className })),
|
|
1277
|
+
...props
|
|
1278
|
+
}
|
|
1279
|
+
);
|
|
1280
|
+
}
|
|
1281
|
+
var DEFAULT_LABELS = {
|
|
1282
|
+
uploadAria: "Upload image",
|
|
1283
|
+
uploadHint: "Click to upload",
|
|
1284
|
+
uploadFormats: "PNG, JPG up to 10MB",
|
|
1285
|
+
dragging: "Dragging",
|
|
1286
|
+
zoomOut: "Zoom out",
|
|
1287
|
+
zoomIn: "Zoom in",
|
|
1288
|
+
zoomLevel: "Zoom level",
|
|
1289
|
+
rotateLeft: "Rotate left 15 degrees",
|
|
1290
|
+
rotateRight: "Rotate right 15 degrees",
|
|
1291
|
+
toggleGrid: "Toggle grid overlay",
|
|
1292
|
+
reset: "Reset all transforms",
|
|
1293
|
+
replace: "Upload new image",
|
|
1294
|
+
discard: "Discard image"
|
|
1295
|
+
};
|
|
1296
|
+
function AvatarEditor({
|
|
1297
|
+
value: _value,
|
|
1298
|
+
onChange,
|
|
1299
|
+
size = 280,
|
|
1300
|
+
showGrid: initialShowGrid = false,
|
|
1301
|
+
outputSize = 256,
|
|
1302
|
+
outputFormat = "png",
|
|
1303
|
+
outputQuality = 0.92,
|
|
1304
|
+
className,
|
|
1305
|
+
controlSize = "default",
|
|
1306
|
+
labels
|
|
1307
|
+
}) {
|
|
1308
|
+
const t = { ...DEFAULT_LABELS, ...labels };
|
|
1309
|
+
const canvasRef = React.useRef(null);
|
|
1310
|
+
const fileInputRef = React.useRef(null);
|
|
1311
|
+
const containerRef = React.useRef(null);
|
|
1312
|
+
const [image, setImage] = React.useState(null);
|
|
1313
|
+
const [imageLoaded, setImageLoaded] = React.useState(false);
|
|
1314
|
+
const [zoom, setZoom] = React.useState(1);
|
|
1315
|
+
const [rotation, setRotation] = React.useState(0);
|
|
1316
|
+
const [position, setPosition] = React.useState({ x: 0, y: 0 });
|
|
1317
|
+
const [isDragging, setIsDragging] = React.useState(false);
|
|
1318
|
+
const [dragStart, setDragStart] = React.useState({ x: 0, y: 0 });
|
|
1319
|
+
const [showGrid, setShowGrid] = React.useState(initialShowGrid);
|
|
1320
|
+
const generateOutput = React.useCallback(() => {
|
|
1321
|
+
if (!canvasRef.current || !image) return null;
|
|
1322
|
+
const outputCanvas = document.createElement("canvas");
|
|
1323
|
+
outputCanvas.width = outputSize;
|
|
1324
|
+
outputCanvas.height = outputSize;
|
|
1325
|
+
const ctx = outputCanvas.getContext("2d");
|
|
1326
|
+
if (!ctx) return null;
|
|
1327
|
+
ctx.drawImage(canvasRef.current, 0, 0, outputSize, outputSize);
|
|
1328
|
+
const mimeType = `image/${outputFormat}`;
|
|
1329
|
+
return outputCanvas.toDataURL(mimeType, outputQuality);
|
|
1330
|
+
}, [image, outputSize, outputFormat, outputQuality]);
|
|
1331
|
+
React.useEffect(() => {
|
|
1332
|
+
if (!canvasRef.current || !image || !imageLoaded) return;
|
|
1333
|
+
const canvas = canvasRef.current;
|
|
1334
|
+
const ctx = canvas.getContext("2d");
|
|
1335
|
+
if (!ctx) return;
|
|
1336
|
+
const displaySize = size;
|
|
1337
|
+
canvas.width = displaySize;
|
|
1338
|
+
canvas.height = displaySize;
|
|
1339
|
+
ctx.clearRect(0, 0, displaySize, displaySize);
|
|
1340
|
+
ctx.save();
|
|
1341
|
+
ctx.translate(displaySize / 2, displaySize / 2);
|
|
1342
|
+
ctx.rotate(rotation * Math.PI / 180);
|
|
1343
|
+
const scale = zoom;
|
|
1344
|
+
const imgAspect = image.width / image.height;
|
|
1345
|
+
let drawWidth, drawHeight;
|
|
1346
|
+
if (imgAspect > 1) {
|
|
1347
|
+
drawHeight = displaySize * scale;
|
|
1348
|
+
drawWidth = drawHeight * imgAspect;
|
|
1349
|
+
} else {
|
|
1350
|
+
drawWidth = displaySize * scale;
|
|
1351
|
+
drawHeight = drawWidth / imgAspect;
|
|
1352
|
+
}
|
|
1353
|
+
ctx.drawImage(
|
|
1354
|
+
image,
|
|
1355
|
+
-drawWidth / 2 + position.x,
|
|
1356
|
+
-drawHeight / 2 + position.y,
|
|
1357
|
+
drawWidth,
|
|
1358
|
+
drawHeight
|
|
1359
|
+
);
|
|
1360
|
+
ctx.restore();
|
|
1361
|
+
const dataUrl = generateOutput();
|
|
1362
|
+
if (dataUrl) {
|
|
1363
|
+
onChange?.(dataUrl);
|
|
1364
|
+
}
|
|
1365
|
+
}, [
|
|
1366
|
+
image,
|
|
1367
|
+
imageLoaded,
|
|
1368
|
+
zoom,
|
|
1369
|
+
rotation,
|
|
1370
|
+
position,
|
|
1371
|
+
size,
|
|
1372
|
+
generateOutput,
|
|
1373
|
+
onChange
|
|
1374
|
+
]);
|
|
1375
|
+
const resetTransforms = React.useCallback(() => {
|
|
1376
|
+
setZoom(1);
|
|
1377
|
+
setRotation(0);
|
|
1378
|
+
setPosition({ x: 0, y: 0 });
|
|
1379
|
+
}, []);
|
|
1380
|
+
const handleFileSelect = React.useCallback(
|
|
1381
|
+
(e) => {
|
|
1382
|
+
const file = e.target.files?.[0];
|
|
1383
|
+
if (!file) return;
|
|
1384
|
+
const reader = new FileReader();
|
|
1385
|
+
reader.onload = (event) => {
|
|
1386
|
+
const img = new Image();
|
|
1387
|
+
img.crossOrigin = "anonymous";
|
|
1388
|
+
img.onload = () => {
|
|
1389
|
+
setImage(img);
|
|
1390
|
+
setImageLoaded(true);
|
|
1391
|
+
resetTransforms();
|
|
1392
|
+
};
|
|
1393
|
+
img.src = event.target?.result;
|
|
1394
|
+
};
|
|
1395
|
+
reader.readAsDataURL(file);
|
|
1396
|
+
},
|
|
1397
|
+
[resetTransforms]
|
|
1398
|
+
);
|
|
1399
|
+
const handleMouseDown = React.useCallback(
|
|
1400
|
+
(e) => {
|
|
1401
|
+
if (!imageLoaded) return;
|
|
1402
|
+
setIsDragging(true);
|
|
1403
|
+
setDragStart({ x: e.clientX - position.x, y: e.clientY - position.y });
|
|
1404
|
+
},
|
|
1405
|
+
[imageLoaded, position]
|
|
1406
|
+
);
|
|
1407
|
+
const handleMouseMove = React.useCallback(
|
|
1408
|
+
(e) => {
|
|
1409
|
+
if (!isDragging) return;
|
|
1410
|
+
setPosition({
|
|
1411
|
+
x: e.clientX - dragStart.x,
|
|
1412
|
+
y: e.clientY - dragStart.y
|
|
1413
|
+
});
|
|
1414
|
+
},
|
|
1415
|
+
[isDragging, dragStart]
|
|
1416
|
+
);
|
|
1417
|
+
const handleMouseUp = React.useCallback(() => {
|
|
1418
|
+
setIsDragging(false);
|
|
1419
|
+
}, []);
|
|
1420
|
+
const handleTouchStart = React.useCallback(
|
|
1421
|
+
(e) => {
|
|
1422
|
+
if (!imageLoaded) return;
|
|
1423
|
+
const touch = e.touches[0];
|
|
1424
|
+
setIsDragging(true);
|
|
1425
|
+
setDragStart({
|
|
1426
|
+
x: touch.clientX - position.x,
|
|
1427
|
+
y: touch.clientY - position.y
|
|
1428
|
+
});
|
|
1429
|
+
},
|
|
1430
|
+
[imageLoaded, position]
|
|
1431
|
+
);
|
|
1432
|
+
const handleTouchMove = React.useCallback(
|
|
1433
|
+
(e) => {
|
|
1434
|
+
if (!isDragging) return;
|
|
1435
|
+
const touch = e.touches[0];
|
|
1436
|
+
setPosition({
|
|
1437
|
+
x: touch.clientX - dragStart.x,
|
|
1438
|
+
y: touch.clientY - dragStart.y
|
|
1439
|
+
});
|
|
1440
|
+
},
|
|
1441
|
+
[isDragging, dragStart]
|
|
1442
|
+
);
|
|
1443
|
+
const handleDiscard = React.useCallback(() => {
|
|
1444
|
+
setImage(null);
|
|
1445
|
+
setImageLoaded(false);
|
|
1446
|
+
resetTransforms();
|
|
1447
|
+
if (fileInputRef.current) {
|
|
1448
|
+
fileInputRef.current.value = "";
|
|
1449
|
+
}
|
|
1450
|
+
onChange?.(null);
|
|
1451
|
+
}, [resetTransforms, onChange]);
|
|
1452
|
+
const handleZoomIn = () => setZoom((z) => Math.min(z + 0.1, 3));
|
|
1453
|
+
const handleZoomOut = () => setZoom((z) => Math.max(z - 0.1, 0.5));
|
|
1454
|
+
const handleRotateLeft = () => setRotation((r) => r - 15);
|
|
1455
|
+
const handleRotateRight = () => setRotation((r) => r + 15);
|
|
1456
|
+
const isLarge = controlSize === "large";
|
|
1457
|
+
const buttonSize = isLarge ? "h-12 w-12" : "h-8 w-8";
|
|
1458
|
+
const iconSize = isLarge ? "w-6 h-6" : "w-4 h-4";
|
|
1459
|
+
const smallIconSize = isLarge ? "w-5 h-5" : "w-3 h-3";
|
|
1460
|
+
const textSize = isLarge ? "text-sm" : "text-xs";
|
|
1461
|
+
const uploadIconSize = isLarge ? "w-12 h-12" : "w-8 h-8";
|
|
1462
|
+
const uploadContainerSize = isLarge ? "w-24 h-24" : "w-16 h-16";
|
|
1463
|
+
const gapSize = isLarge ? "gap-6" : "gap-4";
|
|
1464
|
+
const controlGap = isLarge ? "gap-3" : "gap-2";
|
|
1465
|
+
const sliderHeight = isLarge ? "[&_[role=slider]]:h-6 [&_[role=slider]]:w-6" : "";
|
|
1466
|
+
const guideRadius = size / 2 - 2;
|
|
1467
|
+
const guideCenter = size / 2;
|
|
1468
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1469
|
+
"div",
|
|
1470
|
+
{
|
|
1471
|
+
className: cn("flex flex-col", gapSize, className),
|
|
1472
|
+
style: { width: size },
|
|
1473
|
+
children: [
|
|
1474
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1475
|
+
"div",
|
|
1476
|
+
{
|
|
1477
|
+
ref: containerRef,
|
|
1478
|
+
className: "relative bg-muted rounded-2xl overflow-hidden",
|
|
1479
|
+
style: { width: size, height: size },
|
|
1480
|
+
children: [
|
|
1481
|
+
!imageLoaded && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1482
|
+
"button",
|
|
1483
|
+
{
|
|
1484
|
+
onClick: () => fileInputRef.current?.click(),
|
|
1485
|
+
className: cn(
|
|
1486
|
+
"absolute inset-0 z-10 flex flex-col items-center justify-center text-muted-foreground hover:text-foreground hover:bg-muted/80 transition-colors cursor-pointer",
|
|
1487
|
+
isLarge ? "gap-4" : "gap-3"
|
|
1488
|
+
),
|
|
1489
|
+
"aria-label": t.uploadAria,
|
|
1490
|
+
type: "button",
|
|
1491
|
+
children: [
|
|
1492
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1493
|
+
"div",
|
|
1494
|
+
{
|
|
1495
|
+
className: cn(
|
|
1496
|
+
"rounded-full bg-primary/10 flex items-center justify-center",
|
|
1497
|
+
uploadContainerSize
|
|
1498
|
+
),
|
|
1499
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Upload, { className: cn("text-primary", uploadIconSize) })
|
|
1500
|
+
}
|
|
1501
|
+
),
|
|
1502
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1503
|
+
"span",
|
|
1504
|
+
{
|
|
1505
|
+
className: cn("font-medium", isLarge ? "text-base" : "text-sm"),
|
|
1506
|
+
children: t.uploadHint
|
|
1507
|
+
}
|
|
1508
|
+
),
|
|
1509
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("text-muted-foreground", textSize), children: t.uploadFormats })
|
|
1510
|
+
]
|
|
1511
|
+
}
|
|
1512
|
+
),
|
|
1513
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1514
|
+
"canvas",
|
|
1515
|
+
{
|
|
1516
|
+
ref: canvasRef,
|
|
1517
|
+
className: cn(
|
|
1518
|
+
"absolute inset-0",
|
|
1519
|
+
imageLoaded ? "cursor-move" : "pointer-events-none opacity-0"
|
|
1520
|
+
),
|
|
1521
|
+
style: { width: size, height: size },
|
|
1522
|
+
onMouseDown: handleMouseDown,
|
|
1523
|
+
onMouseMove: handleMouseMove,
|
|
1524
|
+
onMouseUp: handleMouseUp,
|
|
1525
|
+
onMouseLeave: handleMouseUp,
|
|
1526
|
+
onTouchStart: handleTouchStart,
|
|
1527
|
+
onTouchMove: handleTouchMove,
|
|
1528
|
+
onTouchEnd: handleMouseUp
|
|
1529
|
+
}
|
|
1530
|
+
),
|
|
1531
|
+
imageLoaded && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1532
|
+
"svg",
|
|
1533
|
+
{
|
|
1534
|
+
width: size,
|
|
1535
|
+
height: size,
|
|
1536
|
+
className: "pointer-events-none absolute inset-0",
|
|
1537
|
+
"aria-hidden": "true",
|
|
1538
|
+
children: [
|
|
1539
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1540
|
+
"path",
|
|
1541
|
+
{
|
|
1542
|
+
d: `M0 0H${size}V${size}H0Z M${guideCenter - guideRadius} ${guideCenter}a${guideRadius} ${guideRadius} 0 1 0 ${guideRadius * 2} 0a${guideRadius} ${guideRadius} 0 1 0 ${-guideRadius * 2} 0`,
|
|
1543
|
+
fill: "rgb(0 0 0 / 0.45)",
|
|
1544
|
+
fillRule: "evenodd"
|
|
1545
|
+
}
|
|
1546
|
+
),
|
|
1547
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1548
|
+
"circle",
|
|
1549
|
+
{
|
|
1550
|
+
cx: guideCenter,
|
|
1551
|
+
cy: guideCenter,
|
|
1552
|
+
r: guideRadius,
|
|
1553
|
+
fill: "none",
|
|
1554
|
+
stroke: "hsl(var(--primary))",
|
|
1555
|
+
strokeWidth: "3"
|
|
1556
|
+
}
|
|
1557
|
+
)
|
|
1558
|
+
]
|
|
1559
|
+
}
|
|
1560
|
+
),
|
|
1561
|
+
showGrid && imageLoaded && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1562
|
+
"div",
|
|
1563
|
+
{
|
|
1564
|
+
className: "absolute inset-0 pointer-events-none",
|
|
1565
|
+
style: { width: size, height: size },
|
|
1566
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: size, height: size, className: "opacity-30", children: [
|
|
1567
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1568
|
+
"line",
|
|
1569
|
+
{
|
|
1570
|
+
x1: size / 3,
|
|
1571
|
+
y1: 0,
|
|
1572
|
+
x2: size / 3,
|
|
1573
|
+
y2: size,
|
|
1574
|
+
stroke: "white",
|
|
1575
|
+
strokeWidth: "1"
|
|
1576
|
+
}
|
|
1577
|
+
),
|
|
1578
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1579
|
+
"line",
|
|
1580
|
+
{
|
|
1581
|
+
x1: size * 2 / 3,
|
|
1582
|
+
y1: 0,
|
|
1583
|
+
x2: size * 2 / 3,
|
|
1584
|
+
y2: size,
|
|
1585
|
+
stroke: "white",
|
|
1586
|
+
strokeWidth: "1"
|
|
1587
|
+
}
|
|
1588
|
+
),
|
|
1589
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1590
|
+
"line",
|
|
1591
|
+
{
|
|
1592
|
+
x1: 0,
|
|
1593
|
+
y1: size / 3,
|
|
1594
|
+
x2: size,
|
|
1595
|
+
y2: size / 3,
|
|
1596
|
+
stroke: "white",
|
|
1597
|
+
strokeWidth: "1"
|
|
1598
|
+
}
|
|
1599
|
+
),
|
|
1600
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1601
|
+
"line",
|
|
1602
|
+
{
|
|
1603
|
+
x1: 0,
|
|
1604
|
+
y1: size * 2 / 3,
|
|
1605
|
+
x2: size,
|
|
1606
|
+
y2: size * 2 / 3,
|
|
1607
|
+
stroke: "white",
|
|
1608
|
+
strokeWidth: "1"
|
|
1609
|
+
}
|
|
1610
|
+
),
|
|
1611
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1612
|
+
"line",
|
|
1613
|
+
{
|
|
1614
|
+
x1: size / 2 - 10,
|
|
1615
|
+
y1: size / 2,
|
|
1616
|
+
x2: size / 2 + 10,
|
|
1617
|
+
y2: size / 2,
|
|
1618
|
+
stroke: "white",
|
|
1619
|
+
strokeWidth: "1"
|
|
1620
|
+
}
|
|
1621
|
+
),
|
|
1622
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1623
|
+
"line",
|
|
1624
|
+
{
|
|
1625
|
+
x1: size / 2,
|
|
1626
|
+
y1: size / 2 - 10,
|
|
1627
|
+
x2: size / 2,
|
|
1628
|
+
y2: size / 2 + 10,
|
|
1629
|
+
stroke: "white",
|
|
1630
|
+
strokeWidth: "1"
|
|
1631
|
+
}
|
|
1632
|
+
)
|
|
1633
|
+
] })
|
|
1634
|
+
}
|
|
1635
|
+
),
|
|
1636
|
+
imageLoaded && isDragging && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1637
|
+
"div",
|
|
1638
|
+
{
|
|
1639
|
+
className: cn(
|
|
1640
|
+
"absolute top-2 left-1/2 -translate-x-1/2 bg-black/60 rounded text-white flex items-center",
|
|
1641
|
+
isLarge ? "px-3 py-2 text-sm gap-2" : "px-2 py-1 text-xs gap-1"
|
|
1642
|
+
),
|
|
1643
|
+
children: [
|
|
1644
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Move, { className: smallIconSize }),
|
|
1645
|
+
t.dragging
|
|
1646
|
+
]
|
|
1647
|
+
}
|
|
1648
|
+
)
|
|
1649
|
+
]
|
|
1650
|
+
}
|
|
1651
|
+
),
|
|
1652
|
+
imageLoaded && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("space-y-4", isLarge && "space-y-5"), children: [
|
|
1653
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex items-center", controlGap), children: [
|
|
1654
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1655
|
+
Button,
|
|
1656
|
+
{
|
|
1657
|
+
variant: "ghost",
|
|
1658
|
+
size: "icon",
|
|
1659
|
+
className: cn(buttonSize, "shrink-0"),
|
|
1660
|
+
onClick: handleZoomOut,
|
|
1661
|
+
"aria-label": t.zoomOut,
|
|
1662
|
+
type: "button",
|
|
1663
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ZoomOut, { className: iconSize })
|
|
1664
|
+
}
|
|
1665
|
+
),
|
|
1666
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1667
|
+
Slider,
|
|
1668
|
+
{
|
|
1669
|
+
value: [zoom],
|
|
1670
|
+
onValueChange: ([v]) => setZoom(v),
|
|
1671
|
+
min: 0.5,
|
|
1672
|
+
max: 3,
|
|
1673
|
+
step: 0.01,
|
|
1674
|
+
className: cn("flex-1", sliderHeight),
|
|
1675
|
+
"aria-label": t.zoomLevel
|
|
1676
|
+
}
|
|
1677
|
+
),
|
|
1678
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1679
|
+
Button,
|
|
1680
|
+
{
|
|
1681
|
+
variant: "ghost",
|
|
1682
|
+
size: "icon",
|
|
1683
|
+
className: cn(buttonSize, "shrink-0"),
|
|
1684
|
+
onClick: handleZoomIn,
|
|
1685
|
+
"aria-label": t.zoomIn,
|
|
1686
|
+
type: "button",
|
|
1687
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ZoomIn, { className: iconSize })
|
|
1688
|
+
}
|
|
1689
|
+
)
|
|
1690
|
+
] }),
|
|
1691
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex items-center justify-between", controlGap), children: [
|
|
1692
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1693
|
+
"div",
|
|
1694
|
+
{
|
|
1695
|
+
className: cn("flex items-center", isLarge ? "gap-2" : "gap-1"),
|
|
1696
|
+
children: [
|
|
1697
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1698
|
+
Button,
|
|
1699
|
+
{
|
|
1700
|
+
variant: "ghost",
|
|
1701
|
+
size: "icon",
|
|
1702
|
+
className: buttonSize,
|
|
1703
|
+
onClick: handleRotateLeft,
|
|
1704
|
+
"aria-label": t.rotateLeft,
|
|
1705
|
+
type: "button",
|
|
1706
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RotateCcw, { className: iconSize })
|
|
1707
|
+
}
|
|
1708
|
+
),
|
|
1709
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1710
|
+
"span",
|
|
1711
|
+
{
|
|
1712
|
+
className: cn(
|
|
1713
|
+
"text-muted-foreground text-center tabular-nums",
|
|
1714
|
+
textSize,
|
|
1715
|
+
isLarge ? "w-16" : "w-12"
|
|
1716
|
+
),
|
|
1717
|
+
children: [
|
|
1718
|
+
rotation,
|
|
1719
|
+
"\xB0"
|
|
1720
|
+
]
|
|
1721
|
+
}
|
|
1722
|
+
),
|
|
1723
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1724
|
+
Button,
|
|
1725
|
+
{
|
|
1726
|
+
variant: "ghost",
|
|
1727
|
+
size: "icon",
|
|
1728
|
+
className: buttonSize,
|
|
1729
|
+
onClick: handleRotateRight,
|
|
1730
|
+
"aria-label": t.rotateRight,
|
|
1731
|
+
type: "button",
|
|
1732
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RotateCw, { className: iconSize })
|
|
1733
|
+
}
|
|
1734
|
+
)
|
|
1735
|
+
]
|
|
1736
|
+
}
|
|
1737
|
+
),
|
|
1738
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1739
|
+
"div",
|
|
1740
|
+
{
|
|
1741
|
+
className: cn("flex items-center", isLarge ? "gap-2" : "gap-1"),
|
|
1742
|
+
children: [
|
|
1743
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1744
|
+
Toggle,
|
|
1745
|
+
{
|
|
1746
|
+
pressed: showGrid,
|
|
1747
|
+
onPressedChange: setShowGrid,
|
|
1748
|
+
size: "sm",
|
|
1749
|
+
className: cn(buttonSize, "p-0"),
|
|
1750
|
+
"aria-label": t.toggleGrid,
|
|
1751
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Grid3X3, { className: iconSize })
|
|
1752
|
+
}
|
|
1753
|
+
),
|
|
1754
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1755
|
+
Button,
|
|
1756
|
+
{
|
|
1757
|
+
variant: "ghost",
|
|
1758
|
+
size: "icon",
|
|
1759
|
+
className: buttonSize,
|
|
1760
|
+
onClick: resetTransforms,
|
|
1761
|
+
"aria-label": t.reset,
|
|
1762
|
+
type: "button",
|
|
1763
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: iconSize })
|
|
1764
|
+
}
|
|
1765
|
+
),
|
|
1766
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1767
|
+
Button,
|
|
1768
|
+
{
|
|
1769
|
+
variant: "ghost",
|
|
1770
|
+
size: "icon",
|
|
1771
|
+
className: buttonSize,
|
|
1772
|
+
onClick: () => fileInputRef.current?.click(),
|
|
1773
|
+
"aria-label": t.replace,
|
|
1774
|
+
type: "button",
|
|
1775
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Upload, { className: iconSize })
|
|
1776
|
+
}
|
|
1777
|
+
),
|
|
1778
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1779
|
+
Button,
|
|
1780
|
+
{
|
|
1781
|
+
variant: "ghost",
|
|
1782
|
+
size: "icon",
|
|
1783
|
+
className: cn(
|
|
1784
|
+
buttonSize,
|
|
1785
|
+
"text-destructive hover:text-destructive hover:bg-destructive/10"
|
|
1786
|
+
),
|
|
1787
|
+
onClick: handleDiscard,
|
|
1788
|
+
"aria-label": t.discard,
|
|
1789
|
+
type: "button",
|
|
1790
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: iconSize })
|
|
1791
|
+
}
|
|
1792
|
+
)
|
|
1793
|
+
]
|
|
1794
|
+
}
|
|
1795
|
+
)
|
|
1796
|
+
] })
|
|
1797
|
+
] }),
|
|
1798
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1799
|
+
"input",
|
|
1800
|
+
{
|
|
1801
|
+
ref: fileInputRef,
|
|
1802
|
+
type: "file",
|
|
1803
|
+
accept: "image/*",
|
|
1804
|
+
onChange: handleFileSelect,
|
|
1805
|
+
className: "hidden",
|
|
1806
|
+
"aria-hidden": "true"
|
|
1807
|
+
}
|
|
1808
|
+
)
|
|
1809
|
+
]
|
|
1810
|
+
}
|
|
1811
|
+
);
|
|
1812
|
+
}
|
|
1813
|
+
function Dialog({
|
|
1814
|
+
...props
|
|
1815
|
+
}) {
|
|
1816
|
+
return /* @__PURE__ */ jsxRuntime.jsx(DialogPrimitive__namespace.Root, { "data-slot": "dialog", ...props });
|
|
1817
|
+
}
|
|
1818
|
+
function DialogPortal({
|
|
1819
|
+
...props
|
|
1820
|
+
}) {
|
|
1821
|
+
return /* @__PURE__ */ jsxRuntime.jsx(DialogPrimitive__namespace.Portal, { "data-slot": "dialog-portal", ...props });
|
|
1822
|
+
}
|
|
1823
|
+
function DialogClose({
|
|
1824
|
+
...props
|
|
1825
|
+
}) {
|
|
1826
|
+
return /* @__PURE__ */ jsxRuntime.jsx(DialogPrimitive__namespace.Close, { "data-slot": "dialog-close", ...props });
|
|
1827
|
+
}
|
|
1828
|
+
function DialogOverlay({
|
|
1829
|
+
className,
|
|
1830
|
+
...props
|
|
1831
|
+
}) {
|
|
1832
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1833
|
+
DialogPrimitive__namespace.Overlay,
|
|
1834
|
+
{
|
|
1835
|
+
"data-slot": "dialog-overlay",
|
|
1836
|
+
className: cn(
|
|
1837
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
|
1838
|
+
className
|
|
1839
|
+
),
|
|
1840
|
+
...props
|
|
1841
|
+
}
|
|
1842
|
+
);
|
|
1843
|
+
}
|
|
1844
|
+
function DialogContent({
|
|
1845
|
+
className,
|
|
1846
|
+
children,
|
|
1847
|
+
showCloseButton = true,
|
|
1848
|
+
...props
|
|
1849
|
+
}) {
|
|
1850
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(DialogPortal, { "data-slot": "dialog-portal", children: [
|
|
1851
|
+
/* @__PURE__ */ jsxRuntime.jsx(DialogOverlay, {}),
|
|
1852
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1853
|
+
DialogPrimitive__namespace.Content,
|
|
1854
|
+
{
|
|
1855
|
+
"data-slot": "dialog-content",
|
|
1856
|
+
className: cn(
|
|
1857
|
+
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 outline-none sm:max-w-lg",
|
|
1858
|
+
className
|
|
1859
|
+
),
|
|
1860
|
+
...props,
|
|
1861
|
+
children: [
|
|
1862
|
+
children,
|
|
1863
|
+
showCloseButton && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1864
|
+
DialogPrimitive__namespace.Close,
|
|
1865
|
+
{
|
|
1866
|
+
"data-slot": "dialog-close",
|
|
1867
|
+
className: "ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
1868
|
+
children: [
|
|
1869
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.XIcon, {}),
|
|
1870
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", children: "Close" })
|
|
1871
|
+
]
|
|
1872
|
+
}
|
|
1873
|
+
)
|
|
1874
|
+
]
|
|
1875
|
+
}
|
|
1876
|
+
)
|
|
1877
|
+
] });
|
|
1878
|
+
}
|
|
1879
|
+
function DialogHeader({ className, ...props }) {
|
|
1880
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1881
|
+
"div",
|
|
1882
|
+
{
|
|
1883
|
+
"data-slot": "dialog-header",
|
|
1884
|
+
className: cn("flex flex-col gap-2 text-center sm:text-left", className),
|
|
1885
|
+
...props
|
|
1886
|
+
}
|
|
1887
|
+
);
|
|
1888
|
+
}
|
|
1889
|
+
function DialogFooter({ className, ...props }) {
|
|
1890
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1891
|
+
"div",
|
|
1892
|
+
{
|
|
1893
|
+
"data-slot": "dialog-footer",
|
|
1894
|
+
className: cn(
|
|
1895
|
+
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
|
1896
|
+
className
|
|
1897
|
+
),
|
|
1898
|
+
...props
|
|
1899
|
+
}
|
|
1900
|
+
);
|
|
1901
|
+
}
|
|
1902
|
+
function DialogTitle({
|
|
1903
|
+
className,
|
|
1904
|
+
...props
|
|
1905
|
+
}) {
|
|
1906
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1907
|
+
DialogPrimitive__namespace.Title,
|
|
1908
|
+
{
|
|
1909
|
+
"data-slot": "dialog-title",
|
|
1910
|
+
className: cn("text-lg leading-none font-semibold", className),
|
|
1911
|
+
...props
|
|
1912
|
+
}
|
|
1913
|
+
);
|
|
1914
|
+
}
|
|
1915
|
+
function Drawer({
|
|
1916
|
+
...props
|
|
1917
|
+
}) {
|
|
1918
|
+
return /* @__PURE__ */ jsxRuntime.jsx(vaul.Drawer.Root, { "data-slot": "drawer", ...props });
|
|
1919
|
+
}
|
|
1920
|
+
function DrawerPortal({
|
|
1921
|
+
...props
|
|
1922
|
+
}) {
|
|
1923
|
+
return /* @__PURE__ */ jsxRuntime.jsx(vaul.Drawer.Portal, { "data-slot": "drawer-portal", ...props });
|
|
1924
|
+
}
|
|
1925
|
+
function DrawerClose({
|
|
1926
|
+
...props
|
|
1927
|
+
}) {
|
|
1928
|
+
return /* @__PURE__ */ jsxRuntime.jsx(vaul.Drawer.Close, { "data-slot": "drawer-close", ...props });
|
|
1929
|
+
}
|
|
1930
|
+
function DrawerOverlay({
|
|
1931
|
+
className,
|
|
1932
|
+
...props
|
|
1933
|
+
}) {
|
|
1934
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1935
|
+
vaul.Drawer.Overlay,
|
|
1936
|
+
{
|
|
1937
|
+
"data-slot": "drawer-overlay",
|
|
1938
|
+
className: cn(
|
|
1939
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
|
1940
|
+
className
|
|
1941
|
+
),
|
|
1942
|
+
...props
|
|
1943
|
+
}
|
|
1944
|
+
);
|
|
1945
|
+
}
|
|
1946
|
+
function DrawerContent({
|
|
1947
|
+
className,
|
|
1948
|
+
children,
|
|
1949
|
+
...props
|
|
1950
|
+
}) {
|
|
1951
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(DrawerPortal, { "data-slot": "drawer-portal", children: [
|
|
1952
|
+
/* @__PURE__ */ jsxRuntime.jsx(DrawerOverlay, {}),
|
|
1953
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1954
|
+
vaul.Drawer.Content,
|
|
1955
|
+
{
|
|
1956
|
+
"data-slot": "drawer-content",
|
|
1957
|
+
className: cn(
|
|
1958
|
+
"group/drawer-content bg-background fixed z-50 flex h-auto flex-col",
|
|
1959
|
+
"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-lg data-[vaul-drawer-direction=top]:border-b",
|
|
1960
|
+
"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-lg data-[vaul-drawer-direction=bottom]:border-t",
|
|
1961
|
+
"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]:border-l data-[vaul-drawer-direction=right]:sm:max-w-sm",
|
|
1962
|
+
"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]:border-r data-[vaul-drawer-direction=left]:sm:max-w-sm",
|
|
1963
|
+
className
|
|
1964
|
+
),
|
|
1965
|
+
...props,
|
|
1966
|
+
children: [
|
|
1967
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-muted mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full group-data-[vaul-drawer-direction=bottom]/drawer-content:block" }),
|
|
1968
|
+
children
|
|
1969
|
+
]
|
|
1970
|
+
}
|
|
1971
|
+
)
|
|
1972
|
+
] });
|
|
1973
|
+
}
|
|
1974
|
+
function DrawerHeader({ className, ...props }) {
|
|
1975
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1976
|
+
"div",
|
|
1977
|
+
{
|
|
1978
|
+
"data-slot": "drawer-header",
|
|
1979
|
+
className: cn(
|
|
1980
|
+
"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-1.5 md:text-left",
|
|
1981
|
+
className
|
|
1982
|
+
),
|
|
1983
|
+
...props
|
|
1984
|
+
}
|
|
1985
|
+
);
|
|
1986
|
+
}
|
|
1987
|
+
function DrawerFooter({ className, ...props }) {
|
|
1988
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1989
|
+
"div",
|
|
1990
|
+
{
|
|
1991
|
+
"data-slot": "drawer-footer",
|
|
1992
|
+
className: cn("mt-auto flex flex-col gap-2 p-4", className),
|
|
1993
|
+
...props
|
|
1994
|
+
}
|
|
1995
|
+
);
|
|
1996
|
+
}
|
|
1997
|
+
function DrawerTitle({
|
|
1998
|
+
className,
|
|
1999
|
+
...props
|
|
2000
|
+
}) {
|
|
2001
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2002
|
+
vaul.Drawer.Title,
|
|
2003
|
+
{
|
|
2004
|
+
"data-slot": "drawer-title",
|
|
2005
|
+
className: cn("text-foreground font-semibold", className),
|
|
2006
|
+
...props
|
|
2007
|
+
}
|
|
2008
|
+
);
|
|
2009
|
+
}
|
|
2010
|
+
function useMediaQuery(query) {
|
|
2011
|
+
const [matches, setMatches] = React.useState(false);
|
|
2012
|
+
React.useEffect(() => {
|
|
2013
|
+
const media = window.matchMedia(query);
|
|
2014
|
+
if (media.matches !== matches) {
|
|
2015
|
+
setMatches(media.matches);
|
|
2016
|
+
}
|
|
2017
|
+
const listener = () => setMatches(media.matches);
|
|
2018
|
+
media.addEventListener("change", listener);
|
|
2019
|
+
return () => media.removeEventListener("change", listener);
|
|
2020
|
+
}, [matches, query]);
|
|
2021
|
+
return matches;
|
|
2022
|
+
}
|
|
2023
|
+
function AvatarEditorDialog({
|
|
2024
|
+
value,
|
|
2025
|
+
onChange,
|
|
2026
|
+
onSave,
|
|
2027
|
+
displaySize = 120,
|
|
2028
|
+
editorSize = 280,
|
|
2029
|
+
outputSize = 256,
|
|
2030
|
+
placeholder = "Add Photo",
|
|
2031
|
+
editLabel = "Edit avatar",
|
|
2032
|
+
dialogTitle = "Edit Avatar",
|
|
2033
|
+
acceptText = "Accept",
|
|
2034
|
+
cancelText = "Cancel",
|
|
2035
|
+
savingText = "Saving...",
|
|
2036
|
+
savedText = "Saved!",
|
|
2037
|
+
successMessage = "Avatar saved successfully!",
|
|
2038
|
+
errorMessage = "Failed to save avatar. Please try again.",
|
|
2039
|
+
className,
|
|
2040
|
+
editorLabels
|
|
2041
|
+
}) {
|
|
2042
|
+
const [isOpen, setIsOpen] = React.useState(false);
|
|
2043
|
+
const [editedValue, setEditedValue] = React.useState(value ?? null);
|
|
2044
|
+
const [displaySrc, setDisplaySrc] = React.useState(value ?? null);
|
|
2045
|
+
const [isSaving, setIsSaving] = React.useState(false);
|
|
2046
|
+
const [feedback, setFeedback] = React.useState(null);
|
|
2047
|
+
React.useEffect(() => {
|
|
2048
|
+
setDisplaySrc(value ?? null);
|
|
2049
|
+
}, [value]);
|
|
2050
|
+
const isMobile = useMediaQuery("(max-width: 640px)");
|
|
2051
|
+
const caption = (placeholder ?? "").trim();
|
|
2052
|
+
const handleOpenChange = React.useCallback(
|
|
2053
|
+
(open) => {
|
|
2054
|
+
if (open) {
|
|
2055
|
+
setEditedValue(value ?? null);
|
|
2056
|
+
setFeedback(null);
|
|
2057
|
+
}
|
|
2058
|
+
setIsOpen(open);
|
|
2059
|
+
},
|
|
2060
|
+
[value]
|
|
2061
|
+
);
|
|
2062
|
+
const handleAccept = React.useCallback(async () => {
|
|
2063
|
+
if (!editedValue) return;
|
|
2064
|
+
setIsSaving(true);
|
|
2065
|
+
setFeedback(null);
|
|
2066
|
+
try {
|
|
2067
|
+
if (onSave) {
|
|
2068
|
+
const result = await onSave(editedValue);
|
|
2069
|
+
if (result) {
|
|
2070
|
+
setDisplaySrc(editedValue);
|
|
2071
|
+
onChange?.(editedValue);
|
|
2072
|
+
setFeedback({ type: "success", message: successMessage });
|
|
2073
|
+
setTimeout(() => {
|
|
2074
|
+
setIsOpen(false);
|
|
2075
|
+
setFeedback(null);
|
|
2076
|
+
}, 1500);
|
|
2077
|
+
} else {
|
|
2078
|
+
setFeedback({ type: "error", message: errorMessage });
|
|
2079
|
+
}
|
|
2080
|
+
} else {
|
|
2081
|
+
setDisplaySrc(editedValue);
|
|
2082
|
+
onChange?.(editedValue);
|
|
2083
|
+
setFeedback({ type: "success", message: successMessage });
|
|
2084
|
+
setTimeout(() => {
|
|
2085
|
+
setIsOpen(false);
|
|
2086
|
+
setFeedback(null);
|
|
2087
|
+
}, 1500);
|
|
2088
|
+
}
|
|
2089
|
+
} catch {
|
|
2090
|
+
setFeedback({ type: "error", message: errorMessage });
|
|
2091
|
+
} finally {
|
|
2092
|
+
setIsSaving(false);
|
|
2093
|
+
}
|
|
2094
|
+
}, [editedValue, onChange, onSave, successMessage, errorMessage]);
|
|
2095
|
+
const AvatarDisplay = /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2096
|
+
"div",
|
|
2097
|
+
{
|
|
2098
|
+
className: cn("relative group", className),
|
|
2099
|
+
style: { width: displaySize, height: displaySize },
|
|
2100
|
+
children: [
|
|
2101
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2102
|
+
"div",
|
|
2103
|
+
{
|
|
2104
|
+
className: "relative w-full h-full rounded-full overflow-hidden bg-muted border-2 border-border flex items-center justify-center",
|
|
2105
|
+
style: { width: displaySize, height: displaySize },
|
|
2106
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2107
|
+
SafeImage,
|
|
2108
|
+
{
|
|
2109
|
+
src: displaySrc,
|
|
2110
|
+
alt: "Avatar",
|
|
2111
|
+
className: "absolute inset-0 h-full w-full object-cover",
|
|
2112
|
+
fallback: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2113
|
+
"div",
|
|
2114
|
+
{
|
|
2115
|
+
className: cn(
|
|
2116
|
+
"flex flex-col items-center justify-center text-muted-foreground",
|
|
2117
|
+
caption ? "gap-1" : "gap-0"
|
|
2118
|
+
),
|
|
2119
|
+
children: [
|
|
2120
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2121
|
+
lucideReact.User,
|
|
2122
|
+
{
|
|
2123
|
+
className: "h-1/2 w-1/2 max-h-[55%] max-w-[55%] shrink-0",
|
|
2124
|
+
"aria-hidden": true
|
|
2125
|
+
}
|
|
2126
|
+
),
|
|
2127
|
+
caption ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-center px-1", children: caption }) : null
|
|
2128
|
+
]
|
|
2129
|
+
}
|
|
2130
|
+
)
|
|
2131
|
+
}
|
|
2132
|
+
)
|
|
2133
|
+
}
|
|
2134
|
+
),
|
|
2135
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2136
|
+
"button",
|
|
2137
|
+
{
|
|
2138
|
+
onClick: () => handleOpenChange(true),
|
|
2139
|
+
className: "absolute bottom-0 right-0 w-8 h-8 rounded-full bg-primary text-primary-foreground flex items-center justify-center shadow-lg hover:bg-primary/90 transition-colors focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2",
|
|
2140
|
+
"aria-label": editLabel,
|
|
2141
|
+
type: "button",
|
|
2142
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Pencil, { className: "w-4 h-4" })
|
|
2143
|
+
}
|
|
2144
|
+
)
|
|
2145
|
+
]
|
|
2146
|
+
}
|
|
2147
|
+
);
|
|
2148
|
+
const mobileEditorSize = isMobile ? Math.min(editorSize + 40, window.innerWidth - 48) : editorSize;
|
|
2149
|
+
const EditorContent = /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-4", children: [
|
|
2150
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2151
|
+
AvatarEditor,
|
|
2152
|
+
{
|
|
2153
|
+
value: editedValue,
|
|
2154
|
+
onChange: setEditedValue,
|
|
2155
|
+
size: mobileEditorSize,
|
|
2156
|
+
outputSize,
|
|
2157
|
+
controlSize: isMobile ? "large" : "default",
|
|
2158
|
+
labels: editorLabels
|
|
2159
|
+
}
|
|
2160
|
+
),
|
|
2161
|
+
/* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: feedback && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2162
|
+
framerMotion.motion.div,
|
|
2163
|
+
{
|
|
2164
|
+
initial: { opacity: 0, y: -10 },
|
|
2165
|
+
animate: { opacity: 1, y: 0 },
|
|
2166
|
+
exit: { opacity: 0, y: -10 },
|
|
2167
|
+
className: cn(
|
|
2168
|
+
"flex items-center gap-2 rounded-lg font-medium",
|
|
2169
|
+
isMobile ? "px-5 py-3 text-base" : "px-4 py-2 text-sm",
|
|
2170
|
+
feedback.type === "success" ? "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400" : "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400"
|
|
2171
|
+
),
|
|
2172
|
+
children: [
|
|
2173
|
+
feedback.type === "success" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: isMobile ? "w-5 h-5" : "w-4 h-4" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: isMobile ? "w-5 h-5" : "w-4 h-4" }),
|
|
2174
|
+
feedback.message
|
|
2175
|
+
]
|
|
2176
|
+
}
|
|
2177
|
+
) })
|
|
2178
|
+
] });
|
|
2179
|
+
const FooterButtons = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2180
|
+
isMobile ? /* @__PURE__ */ jsxRuntime.jsx(DrawerClose, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2181
|
+
Button,
|
|
2182
|
+
{
|
|
2183
|
+
variant: "outline",
|
|
2184
|
+
disabled: isSaving,
|
|
2185
|
+
size: "lg",
|
|
2186
|
+
className: "flex-1 bg-transparent",
|
|
2187
|
+
children: cancelText
|
|
2188
|
+
}
|
|
2189
|
+
) }) : /* @__PURE__ */ jsxRuntime.jsx(DialogClose, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "outline", disabled: isSaving, children: cancelText }) }),
|
|
2190
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2191
|
+
Button,
|
|
2192
|
+
{
|
|
2193
|
+
onClick: handleAccept,
|
|
2194
|
+
disabled: !editedValue || isSaving || feedback?.type === "success",
|
|
2195
|
+
size: isMobile ? "lg" : "default",
|
|
2196
|
+
className: isMobile ? "flex-1" : "",
|
|
2197
|
+
children: isSaving ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2198
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2199
|
+
lucideReact.Loader2,
|
|
2200
|
+
{
|
|
2201
|
+
className: cn(
|
|
2202
|
+
"mr-2 animate-spin",
|
|
2203
|
+
isMobile ? "w-5 h-5" : "w-4 h-4"
|
|
2204
|
+
)
|
|
2205
|
+
}
|
|
2206
|
+
),
|
|
2207
|
+
savingText
|
|
2208
|
+
] }) : feedback?.type === "success" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2209
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: cn("mr-2", isMobile ? "w-5 h-5" : "w-4 h-4") }),
|
|
2210
|
+
savedText
|
|
2211
|
+
] }) : acceptText
|
|
2212
|
+
}
|
|
2213
|
+
)
|
|
2214
|
+
] });
|
|
2215
|
+
if (isMobile) {
|
|
2216
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2217
|
+
AvatarDisplay,
|
|
2218
|
+
/* @__PURE__ */ jsxRuntime.jsx(Drawer, { open: isOpen, onOpenChange: handleOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(DrawerContent, { className: "max-h-[96vh]", children: [
|
|
2219
|
+
/* @__PURE__ */ jsxRuntime.jsx(DrawerHeader, { className: "text-center", children: /* @__PURE__ */ jsxRuntime.jsx(DrawerTitle, { className: "text-xl", children: dialogTitle }) }),
|
|
2220
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 pb-6 overflow-y-auto", children: EditorContent }),
|
|
2221
|
+
/* @__PURE__ */ jsxRuntime.jsx(DrawerFooter, { className: "flex-row gap-3 px-6 pb-8", children: FooterButtons })
|
|
2222
|
+
] }) })
|
|
2223
|
+
] });
|
|
2224
|
+
}
|
|
2225
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2226
|
+
AvatarDisplay,
|
|
2227
|
+
/* @__PURE__ */ jsxRuntime.jsx(Dialog, { open: isOpen, onOpenChange: handleOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(DialogContent, { className: "sm:max-w-md", children: [
|
|
2228
|
+
/* @__PURE__ */ jsxRuntime.jsx(DialogHeader, { children: /* @__PURE__ */ jsxRuntime.jsx(DialogTitle, { children: dialogTitle }) }),
|
|
2229
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-center py-4", children: EditorContent }),
|
|
2230
|
+
/* @__PURE__ */ jsxRuntime.jsx(DialogFooter, { className: "sm:gap-3", children: FooterButtons })
|
|
2231
|
+
] }) })
|
|
2232
|
+
] });
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2235
|
+
// src/components/upload/part-math.ts
|
|
2236
|
+
function partRanges(fileSize, partSize) {
|
|
2237
|
+
if (partSize <= 0) {
|
|
2238
|
+
throw new Error("partSize must be greater than 0");
|
|
2239
|
+
}
|
|
2240
|
+
if (fileSize <= 0) {
|
|
2241
|
+
return [{ partNumber: 1, start: 0, end: 0 }];
|
|
2242
|
+
}
|
|
2243
|
+
const count = Math.ceil(fileSize / partSize);
|
|
2244
|
+
return Array.from({ length: count }, (_, index) => {
|
|
2245
|
+
const start = index * partSize;
|
|
2246
|
+
return {
|
|
2247
|
+
partNumber: index + 1,
|
|
2248
|
+
start,
|
|
2249
|
+
end: Math.min(fileSize, start + partSize)
|
|
2250
|
+
};
|
|
2251
|
+
});
|
|
2252
|
+
}
|
|
2253
|
+
var BYTE_UNITS = ["B", "KB", "MB", "GB", "TB"];
|
|
2254
|
+
function formatUploadBytes(bytes) {
|
|
2255
|
+
if (!Number.isFinite(bytes) || bytes <= 0) {
|
|
2256
|
+
return "0 B";
|
|
2257
|
+
}
|
|
2258
|
+
let value = bytes;
|
|
2259
|
+
let unit = 0;
|
|
2260
|
+
while (value >= 1024 && unit < BYTE_UNITS.length - 1) {
|
|
2261
|
+
value /= 1024;
|
|
2262
|
+
unit += 1;
|
|
2263
|
+
}
|
|
2264
|
+
if (unit === 0 || value >= 10) {
|
|
2265
|
+
return `${Math.round(value)} ${BYTE_UNITS[unit]}`;
|
|
2266
|
+
}
|
|
2267
|
+
const tenths = Math.round(value * 10) / 10;
|
|
2268
|
+
const label = Number.isInteger(tenths) ? String(tenths) : tenths.toFixed(1);
|
|
2269
|
+
return `${label} ${BYTE_UNITS[unit]}`;
|
|
2270
|
+
}
|
|
2271
|
+
function progressFromBytes(loaded, total) {
|
|
2272
|
+
const safeTotal = Math.max(0, total);
|
|
2273
|
+
const safeLoaded = Math.min(Math.max(0, loaded), safeTotal);
|
|
2274
|
+
const percent = safeTotal > 0 ? Math.round(safeLoaded / safeTotal * 100) : 0;
|
|
2275
|
+
return { loaded: safeLoaded, total: safeTotal, percent };
|
|
2276
|
+
}
|
|
2277
|
+
function fileMatchesAccept(file, accept) {
|
|
2278
|
+
if (!accept?.trim()) return true;
|
|
2279
|
+
const type = file.type.toLowerCase();
|
|
2280
|
+
const name = file.name.toLowerCase();
|
|
2281
|
+
return accept.split(",").some((token) => {
|
|
2282
|
+
const trimmed = token.trim().toLowerCase();
|
|
2283
|
+
if (!trimmed) return false;
|
|
2284
|
+
if (trimmed.endsWith("/*")) {
|
|
2285
|
+
return type.startsWith(trimmed.slice(0, -1));
|
|
2286
|
+
}
|
|
2287
|
+
if (trimmed.startsWith(".")) {
|
|
2288
|
+
return name.endsWith(trimmed);
|
|
2289
|
+
}
|
|
2290
|
+
return type === trimmed;
|
|
2291
|
+
});
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2294
|
+
// src/components/upload/types.ts
|
|
2295
|
+
var DEFAULT_FILE_DROPZONE_LABELS = {
|
|
2296
|
+
idle: "Drop a file here or browse",
|
|
2297
|
+
hint: "Click or press Enter to choose a file",
|
|
2298
|
+
browse: "Browse",
|
|
2299
|
+
rejectType: "This file type is not allowed",
|
|
2300
|
+
rejectSize: "This file is too large"
|
|
2301
|
+
};
|
|
2302
|
+
var DEFAULT_FILE_UPLOAD_TRAY_LABELS = {
|
|
2303
|
+
title: "Uploads",
|
|
2304
|
+
pause: "Pause",
|
|
2305
|
+
resume: "Resume",
|
|
2306
|
+
cancel: "Cancel",
|
|
2307
|
+
uploading: "Uploading",
|
|
2308
|
+
paused: "Paused",
|
|
2309
|
+
completing: "Finishing",
|
|
2310
|
+
completed: "Uploaded",
|
|
2311
|
+
cancelled: "Cancelled",
|
|
2312
|
+
error: "Failed"
|
|
2313
|
+
};
|
|
2314
|
+
var DEFAULT_FILE_UPLOAD_LABELS = {
|
|
2315
|
+
dropzone: DEFAULT_FILE_DROPZONE_LABELS,
|
|
2316
|
+
tray: DEFAULT_FILE_UPLOAD_TRAY_LABELS
|
|
2317
|
+
};
|
|
2318
|
+
function FileDropzone({
|
|
2319
|
+
accept,
|
|
2320
|
+
maxSize,
|
|
2321
|
+
disabled = false,
|
|
2322
|
+
labels: labelOverrides,
|
|
2323
|
+
onFile,
|
|
2324
|
+
onReject,
|
|
2325
|
+
className
|
|
2326
|
+
}) {
|
|
2327
|
+
const labels = { ...DEFAULT_FILE_DROPZONE_LABELS, ...labelOverrides };
|
|
2328
|
+
const inputId = React.useId();
|
|
2329
|
+
const inputRef = React.useRef(null);
|
|
2330
|
+
const [dragging, setDragging] = React.useState(false);
|
|
2331
|
+
const [message, setMessage] = React.useState(null);
|
|
2332
|
+
function takeFile(file) {
|
|
2333
|
+
if (!file || disabled) return;
|
|
2334
|
+
if (!fileMatchesAccept(file, accept)) {
|
|
2335
|
+
setMessage(labels.rejectType);
|
|
2336
|
+
onReject?.("type");
|
|
2337
|
+
return;
|
|
2338
|
+
}
|
|
2339
|
+
if (maxSize != null && file.size > maxSize) {
|
|
2340
|
+
setMessage(labels.rejectSize);
|
|
2341
|
+
onReject?.("size");
|
|
2342
|
+
return;
|
|
2343
|
+
}
|
|
2344
|
+
setMessage(null);
|
|
2345
|
+
onFile(file);
|
|
2346
|
+
}
|
|
2347
|
+
function onDrop(event) {
|
|
2348
|
+
event.preventDefault();
|
|
2349
|
+
setDragging(false);
|
|
2350
|
+
takeFile(event.dataTransfer.files[0]);
|
|
2351
|
+
}
|
|
2352
|
+
function onKeyDown(event) {
|
|
2353
|
+
if (disabled) return;
|
|
2354
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
2355
|
+
event.preventDefault();
|
|
2356
|
+
inputRef.current?.click();
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2360
|
+
"div",
|
|
2361
|
+
{
|
|
2362
|
+
className: cn("relative flex w-full min-w-0 flex-col gap-2", className),
|
|
2363
|
+
children: [
|
|
2364
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2365
|
+
"div",
|
|
2366
|
+
{
|
|
2367
|
+
role: "button",
|
|
2368
|
+
tabIndex: disabled ? -1 : 0,
|
|
2369
|
+
"aria-disabled": disabled,
|
|
2370
|
+
"aria-describedby": message ? `${inputId}-message` : `${inputId}-hint`,
|
|
2371
|
+
"aria-controls": inputId,
|
|
2372
|
+
"data-dragging": dragging || void 0,
|
|
2373
|
+
className: cn(
|
|
2374
|
+
"border-input bg-background focus-visible:border-ring focus-visible:ring-ring/50 w-full min-w-0 rounded-lg border border-dashed px-4 py-8 text-center outline-none focus-visible:ring-[3px]",
|
|
2375
|
+
dragging && "border-primary bg-primary/5",
|
|
2376
|
+
disabled && "pointer-events-none opacity-50"
|
|
2377
|
+
),
|
|
2378
|
+
onClick: () => {
|
|
2379
|
+
if (!disabled) inputRef.current?.click();
|
|
2380
|
+
},
|
|
2381
|
+
onKeyDown,
|
|
2382
|
+
onDragEnter: (event) => {
|
|
2383
|
+
event.preventDefault();
|
|
2384
|
+
if (!disabled) setDragging(true);
|
|
2385
|
+
},
|
|
2386
|
+
onDragOver: (event) => {
|
|
2387
|
+
event.preventDefault();
|
|
2388
|
+
},
|
|
2389
|
+
onDragLeave: (event) => {
|
|
2390
|
+
if (event.currentTarget.contains(event.relatedTarget)) {
|
|
2391
|
+
return;
|
|
2392
|
+
}
|
|
2393
|
+
setDragging(false);
|
|
2394
|
+
},
|
|
2395
|
+
onDrop,
|
|
2396
|
+
children: [
|
|
2397
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium", children: labels.idle }),
|
|
2398
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2399
|
+
"p",
|
|
2400
|
+
{
|
|
2401
|
+
id: `${inputId}-hint`,
|
|
2402
|
+
className: "text-muted-foreground mt-1 text-xs",
|
|
2403
|
+
children: labels.hint
|
|
2404
|
+
}
|
|
2405
|
+
),
|
|
2406
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-primary mt-3 inline-flex text-sm font-medium underline-offset-4", children: labels.browse })
|
|
2407
|
+
]
|
|
2408
|
+
}
|
|
2409
|
+
),
|
|
2410
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2411
|
+
"input",
|
|
2412
|
+
{
|
|
2413
|
+
id: inputId,
|
|
2414
|
+
ref: inputRef,
|
|
2415
|
+
type: "file",
|
|
2416
|
+
accept,
|
|
2417
|
+
disabled,
|
|
2418
|
+
"aria-label": labels.browse,
|
|
2419
|
+
className: "sr-only",
|
|
2420
|
+
onChange: (event) => {
|
|
2421
|
+
takeFile(event.target.files?.[0]);
|
|
2422
|
+
event.target.value = "";
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
),
|
|
2426
|
+
message ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2427
|
+
"p",
|
|
2428
|
+
{
|
|
2429
|
+
id: `${inputId}-message`,
|
|
2430
|
+
role: "alert",
|
|
2431
|
+
className: "text-destructive text-sm",
|
|
2432
|
+
children: message
|
|
2433
|
+
}
|
|
2434
|
+
) : null
|
|
2435
|
+
]
|
|
2436
|
+
}
|
|
2437
|
+
);
|
|
2438
|
+
}
|
|
2439
|
+
function statusLabel(status, labels) {
|
|
2440
|
+
switch (status) {
|
|
2441
|
+
case "uploading":
|
|
2442
|
+
return labels.uploading;
|
|
2443
|
+
case "paused":
|
|
2444
|
+
return labels.paused;
|
|
2445
|
+
case "completing":
|
|
2446
|
+
return labels.completing;
|
|
2447
|
+
case "completed":
|
|
2448
|
+
return labels.completed;
|
|
2449
|
+
case "cancelled":
|
|
2450
|
+
return labels.cancelled;
|
|
2451
|
+
case "error":
|
|
2452
|
+
return labels.error;
|
|
2453
|
+
default: {
|
|
2454
|
+
const _exhaustive = status;
|
|
2455
|
+
return _exhaustive;
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
function progressLabel(job) {
|
|
2460
|
+
return `${job.percent}% \xB7 ${formatUploadBytes(job.loaded)} / ${formatUploadBytes(job.total)}`;
|
|
2461
|
+
}
|
|
2462
|
+
function FileUploadTray({
|
|
2463
|
+
jobs,
|
|
2464
|
+
labels: labelOverrides,
|
|
2465
|
+
onPause,
|
|
2466
|
+
onResume,
|
|
2467
|
+
onCancel,
|
|
2468
|
+
className
|
|
2469
|
+
}) {
|
|
2470
|
+
const labels = { ...DEFAULT_FILE_UPLOAD_TRAY_LABELS, ...labelOverrides };
|
|
2471
|
+
const visible = jobs.filter((job) => job.status !== "cancelled");
|
|
2472
|
+
if (visible.length === 0) return null;
|
|
2473
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2474
|
+
"section",
|
|
2475
|
+
{
|
|
2476
|
+
"aria-label": labels.title,
|
|
2477
|
+
className: cn("grid min-w-0 gap-3", className),
|
|
2478
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "grid min-w-0 gap-3", children: visible.map((job) => {
|
|
2479
|
+
const active = job.status === "uploading" || job.status === "completing";
|
|
2480
|
+
const canPause = job.status === "uploading";
|
|
2481
|
+
const canResume = job.status === "paused";
|
|
2482
|
+
const canCancel = job.status === "uploading" || job.status === "paused" || job.status === "error";
|
|
2483
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "grid min-w-0 gap-1.5", children: [
|
|
2484
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2485
|
+
"p",
|
|
2486
|
+
{
|
|
2487
|
+
className: "min-w-0 line-clamp-3 text-sm leading-5 font-medium break-all",
|
|
2488
|
+
title: job.name,
|
|
2489
|
+
children: job.name
|
|
2490
|
+
}
|
|
2491
|
+
),
|
|
2492
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
|
|
2493
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground shrink-0 text-xs", children: statusLabel(job.status, labels) }),
|
|
2494
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground shrink-0 text-xs tabular-nums", children: progressLabel(job) }),
|
|
2495
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1" }),
|
|
2496
|
+
canPause ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2497
|
+
Button,
|
|
2498
|
+
{
|
|
2499
|
+
type: "button",
|
|
2500
|
+
variant: "ghost",
|
|
2501
|
+
size: "icon-sm",
|
|
2502
|
+
className: "shrink-0",
|
|
2503
|
+
"aria-label": `${labels.pause} ${job.name}`,
|
|
2504
|
+
onClick: () => onPause(job.id),
|
|
2505
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Pause, {})
|
|
2506
|
+
}
|
|
2507
|
+
) : null,
|
|
2508
|
+
canResume ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2509
|
+
Button,
|
|
2510
|
+
{
|
|
2511
|
+
type: "button",
|
|
2512
|
+
variant: "ghost",
|
|
2513
|
+
size: "icon-sm",
|
|
2514
|
+
className: "shrink-0",
|
|
2515
|
+
"aria-label": `${labels.resume} ${job.name}`,
|
|
2516
|
+
onClick: () => onResume(job.id),
|
|
2517
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Play, {})
|
|
2518
|
+
}
|
|
2519
|
+
) : null,
|
|
2520
|
+
canCancel ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2521
|
+
Button,
|
|
2522
|
+
{
|
|
2523
|
+
type: "button",
|
|
2524
|
+
variant: "ghost",
|
|
2525
|
+
size: "icon-sm",
|
|
2526
|
+
className: "shrink-0",
|
|
2527
|
+
"aria-label": `${labels.cancel} ${job.name}`,
|
|
2528
|
+
onClick: () => onCancel(job.id),
|
|
2529
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, {})
|
|
2530
|
+
}
|
|
2531
|
+
) : null
|
|
2532
|
+
] }),
|
|
2533
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2534
|
+
"div",
|
|
2535
|
+
{
|
|
2536
|
+
role: "progressbar",
|
|
2537
|
+
"aria-valuemin": 0,
|
|
2538
|
+
"aria-valuemax": 100,
|
|
2539
|
+
"aria-valuenow": job.percent,
|
|
2540
|
+
"aria-label": job.name,
|
|
2541
|
+
className: "bg-muted h-2 overflow-hidden rounded-full",
|
|
2542
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2543
|
+
"div",
|
|
2544
|
+
{
|
|
2545
|
+
className: cn(
|
|
2546
|
+
"bg-primary h-full",
|
|
2547
|
+
active && "motion-safe:transition-[width]"
|
|
2548
|
+
),
|
|
2549
|
+
style: { width: `${job.percent}%` }
|
|
2550
|
+
}
|
|
2551
|
+
)
|
|
2552
|
+
}
|
|
2553
|
+
),
|
|
2554
|
+
job.error ? /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: "text-destructive text-xs", children: job.error }) : null
|
|
2555
|
+
] }, job.id);
|
|
2556
|
+
}) })
|
|
2557
|
+
}
|
|
2558
|
+
);
|
|
2559
|
+
}
|
|
2560
|
+
function formatActiveBadge(count) {
|
|
2561
|
+
return count > 9 ? "9+" : String(count);
|
|
2562
|
+
}
|
|
2563
|
+
function isActiveStatus(status) {
|
|
2564
|
+
return status === "uploading" || status === "paused" || status === "completing";
|
|
2565
|
+
}
|
|
2566
|
+
function FileUploadMenu({
|
|
2567
|
+
jobs,
|
|
2568
|
+
labels: labelOverrides,
|
|
2569
|
+
onPause,
|
|
2570
|
+
onResume,
|
|
2571
|
+
onCancel,
|
|
2572
|
+
open,
|
|
2573
|
+
onOpenChange,
|
|
2574
|
+
align = "end",
|
|
2575
|
+
className
|
|
2576
|
+
}) {
|
|
2577
|
+
const labels = { ...DEFAULT_FILE_UPLOAD_TRAY_LABELS, ...labelOverrides };
|
|
2578
|
+
const visible = jobs.filter((job) => job.status !== "cancelled");
|
|
2579
|
+
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(false);
|
|
2580
|
+
const isControlled = open !== void 0;
|
|
2581
|
+
const resolvedOpen = isControlled ? open : uncontrolledOpen;
|
|
2582
|
+
function setResolvedOpen(next) {
|
|
2583
|
+
if (!isControlled) setUncontrolledOpen(next);
|
|
2584
|
+
onOpenChange?.(next);
|
|
2585
|
+
}
|
|
2586
|
+
if (visible.length === 0) return null;
|
|
2587
|
+
const activeCount = visible.filter(
|
|
2588
|
+
(job) => isActiveStatus(job.status)
|
|
2589
|
+
).length;
|
|
2590
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2591
|
+
DropdownMenu,
|
|
2592
|
+
{
|
|
2593
|
+
modal: false,
|
|
2594
|
+
open: resolvedOpen,
|
|
2595
|
+
onOpenChange: setResolvedOpen,
|
|
2596
|
+
children: [
|
|
2597
|
+
/* @__PURE__ */ jsxRuntime.jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2598
|
+
Button,
|
|
2599
|
+
{
|
|
2600
|
+
type: "button",
|
|
2601
|
+
variant: "ghost",
|
|
2602
|
+
size: "icon",
|
|
2603
|
+
className: cn("relative size-9", className),
|
|
2604
|
+
"aria-label": labels.title,
|
|
2605
|
+
children: [
|
|
2606
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Upload, { className: "size-4" }),
|
|
2607
|
+
activeCount > 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "bg-destructive text-destructive-foreground absolute -top-0.5 -right-0.5 flex size-4 items-center justify-center rounded-full text-[10px] leading-none font-medium tabular-nums", children: formatActiveBadge(activeCount) }) : null
|
|
2608
|
+
]
|
|
2609
|
+
}
|
|
2610
|
+
) }),
|
|
2611
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2612
|
+
DropdownMenuContent,
|
|
2613
|
+
{
|
|
2614
|
+
align,
|
|
2615
|
+
className: "w-80 min-w-0 max-w-[calc(100vw-2rem)] overflow-x-hidden p-0",
|
|
2616
|
+
children: [
|
|
2617
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-between border-b px-4 py-3", children: /* @__PURE__ */ jsxRuntime.jsx("h4", { className: "font-semibold", children: labels.title }) }),
|
|
2618
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-h-80 min-w-0 overflow-x-hidden overflow-y-auto p-3", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2619
|
+
FileUploadTray,
|
|
2620
|
+
{
|
|
2621
|
+
jobs: visible,
|
|
2622
|
+
labels,
|
|
2623
|
+
onPause,
|
|
2624
|
+
onResume,
|
|
2625
|
+
onCancel
|
|
2626
|
+
}
|
|
2627
|
+
) })
|
|
2628
|
+
]
|
|
2629
|
+
}
|
|
2630
|
+
)
|
|
2631
|
+
]
|
|
2632
|
+
}
|
|
2633
|
+
);
|
|
2634
|
+
}
|
|
2635
|
+
|
|
2636
|
+
// src/components/upload/createMultipartUploadController.ts
|
|
2637
|
+
var PART_ATTEMPTS = 3;
|
|
2638
|
+
var PART_RETRY_BACKOFF_MS = [500, 1500];
|
|
2639
|
+
function defaultSleep(ms) {
|
|
2640
|
+
return new Promise((resolve) => {
|
|
2641
|
+
setTimeout(resolve, ms);
|
|
2642
|
+
});
|
|
2643
|
+
}
|
|
2644
|
+
function isAbortError(error) {
|
|
2645
|
+
return error instanceof DOMException && error.name === "AbortError";
|
|
2646
|
+
}
|
|
2647
|
+
function isMissingEtagError(error) {
|
|
2648
|
+
return error instanceof Error && error.message.includes("missing ETag");
|
|
2649
|
+
}
|
|
2650
|
+
function isRetryableStatus(status) {
|
|
2651
|
+
return status === 0 || status === 403 || status === 408 || status === 429 || status >= 500;
|
|
2652
|
+
}
|
|
2653
|
+
function putPartFailure(partNumber, status) {
|
|
2654
|
+
return new Error(`Upload failed (part ${partNumber}, status ${status})`);
|
|
2655
|
+
}
|
|
2656
|
+
function parsePutStatus(error) {
|
|
2657
|
+
if (!(error instanceof Error)) return null;
|
|
2658
|
+
const match = error.message.match(/status (\d+)\)$/);
|
|
2659
|
+
if (!match?.[1]) return null;
|
|
2660
|
+
return Number(match[1]);
|
|
2661
|
+
}
|
|
2662
|
+
function isRetryablePutError(error) {
|
|
2663
|
+
if (isAbortError(error) || isMissingEtagError(error)) return false;
|
|
2664
|
+
const status = parsePutStatus(error);
|
|
2665
|
+
if (status === null) return false;
|
|
2666
|
+
return isRetryableStatus(status);
|
|
2667
|
+
}
|
|
2668
|
+
function readEtag(xhr) {
|
|
2669
|
+
const raw = xhr.getResponseHeader("ETag") ?? xhr.getResponseHeader("etag") ?? "";
|
|
2670
|
+
const etag = raw.trim();
|
|
2671
|
+
if (!etag) {
|
|
2672
|
+
throw new Error("Upload part missing ETag");
|
|
2673
|
+
}
|
|
2674
|
+
return etag;
|
|
2675
|
+
}
|
|
2676
|
+
function putPart(input) {
|
|
2677
|
+
return new Promise((resolve, reject) => {
|
|
2678
|
+
input.xhr.open("PUT", input.url);
|
|
2679
|
+
input.xhr.upload.onprogress = (event) => {
|
|
2680
|
+
input.onProgress(event.loaded);
|
|
2681
|
+
};
|
|
2682
|
+
input.xhr.onload = () => {
|
|
2683
|
+
if (input.xhr.status >= 200 && input.xhr.status < 300) {
|
|
2684
|
+
try {
|
|
2685
|
+
resolve(readEtag(input.xhr));
|
|
2686
|
+
} catch (error) {
|
|
2687
|
+
reject(error);
|
|
2688
|
+
}
|
|
2689
|
+
return;
|
|
2690
|
+
}
|
|
2691
|
+
reject(putPartFailure(input.partNumber, input.xhr.status));
|
|
2692
|
+
};
|
|
2693
|
+
input.xhr.onerror = () => {
|
|
2694
|
+
reject(putPartFailure(input.partNumber, 0));
|
|
2695
|
+
};
|
|
2696
|
+
input.xhr.onabort = () => {
|
|
2697
|
+
reject(new DOMException("The operation was aborted.", "AbortError"));
|
|
2698
|
+
};
|
|
2699
|
+
input.xhr.send(input.body);
|
|
2700
|
+
});
|
|
2701
|
+
}
|
|
2702
|
+
function createMultipartUploadController(options) {
|
|
2703
|
+
const ranges = partRanges(options.file.size, options.partSize);
|
|
2704
|
+
const sleep = options.sleep ?? defaultSleep;
|
|
2705
|
+
const state = { status: "idle" };
|
|
2706
|
+
const seeded = /* @__PURE__ */ new Map();
|
|
2707
|
+
for (const part of options.completedParts ?? []) {
|
|
2708
|
+
if (part.partNumber < 1 || part.partNumber > ranges.length) continue;
|
|
2709
|
+
seeded.set(part.partNumber, part);
|
|
2710
|
+
}
|
|
2711
|
+
const completed = [...seeded.values()].sort(
|
|
2712
|
+
(left, right) => left.partNumber - right.partNumber
|
|
2713
|
+
);
|
|
2714
|
+
const done = new Set(completed.map((part) => part.partNumber));
|
|
2715
|
+
let currentXhr = null;
|
|
2716
|
+
let run = null;
|
|
2717
|
+
let resumeIndex = ranges.findIndex((range) => !done.has(range.partNumber));
|
|
2718
|
+
if (resumeIndex < 0) resumeIndex = ranges.length;
|
|
2719
|
+
function currentStatus() {
|
|
2720
|
+
return state.status;
|
|
2721
|
+
}
|
|
2722
|
+
function isStopped() {
|
|
2723
|
+
const status = currentStatus();
|
|
2724
|
+
return status === "paused" || status === "cancelled";
|
|
2725
|
+
}
|
|
2726
|
+
function emitProgress(currentPartLoaded = 0) {
|
|
2727
|
+
const completedBytes = completed.reduce((sum, part) => {
|
|
2728
|
+
const range = ranges[part.partNumber - 1];
|
|
2729
|
+
return sum + (range ? range.end - range.start : 0);
|
|
2730
|
+
}, 0);
|
|
2731
|
+
options.onProgress?.(
|
|
2732
|
+
progressFromBytes(completedBytes + currentPartLoaded, options.file.size)
|
|
2733
|
+
);
|
|
2734
|
+
}
|
|
2735
|
+
async function putPartWithRetry(partNumber, body) {
|
|
2736
|
+
let lastError;
|
|
2737
|
+
for (let attempt = 1; attempt <= PART_ATTEMPTS; attempt += 1) {
|
|
2738
|
+
if (state.status !== "uploading") {
|
|
2739
|
+
throw lastError ?? new DOMException("The operation was aborted.", "AbortError");
|
|
2740
|
+
}
|
|
2741
|
+
const url = await options.adapter.requestPartUrl(partNumber);
|
|
2742
|
+
if (state.status !== "uploading") {
|
|
2743
|
+
throw new DOMException("The operation was aborted.", "AbortError");
|
|
2744
|
+
}
|
|
2745
|
+
const xhr = options.xhrFactory ? options.xhrFactory() : new XMLHttpRequest();
|
|
2746
|
+
currentXhr = xhr;
|
|
2747
|
+
try {
|
|
2748
|
+
return await putPart({
|
|
2749
|
+
url,
|
|
2750
|
+
body,
|
|
2751
|
+
xhr,
|
|
2752
|
+
partNumber,
|
|
2753
|
+
onProgress: (loaded) => emitProgress(loaded)
|
|
2754
|
+
});
|
|
2755
|
+
} catch (error) {
|
|
2756
|
+
lastError = error;
|
|
2757
|
+
if (currentXhr === xhr) currentXhr = null;
|
|
2758
|
+
if (isStopped() || !isRetryablePutError(error) || attempt === PART_ATTEMPTS) {
|
|
2759
|
+
throw error;
|
|
2760
|
+
}
|
|
2761
|
+
const delay = PART_RETRY_BACKOFF_MS[attempt - 1] ?? PART_RETRY_BACKOFF_MS[PART_RETRY_BACKOFF_MS.length - 1];
|
|
2762
|
+
await sleep(delay);
|
|
2763
|
+
} finally {
|
|
2764
|
+
if (currentXhr === xhr) currentXhr = null;
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
throw lastError;
|
|
2768
|
+
}
|
|
2769
|
+
async function uploadFrom(startIndex) {
|
|
2770
|
+
state.status = "uploading";
|
|
2771
|
+
for (let index = startIndex; index < ranges.length; index += 1) {
|
|
2772
|
+
if (state.status !== "uploading") return;
|
|
2773
|
+
resumeIndex = index;
|
|
2774
|
+
const range = ranges[index];
|
|
2775
|
+
if (!range) continue;
|
|
2776
|
+
if (done.has(range.partNumber)) continue;
|
|
2777
|
+
const body = options.file.slice(range.start, range.end);
|
|
2778
|
+
try {
|
|
2779
|
+
const etag = await putPartWithRetry(range.partNumber, body);
|
|
2780
|
+
if (state.status !== "uploading") return;
|
|
2781
|
+
completed.push({ partNumber: range.partNumber, etag });
|
|
2782
|
+
done.add(range.partNumber);
|
|
2783
|
+
emitProgress(0);
|
|
2784
|
+
} catch (error) {
|
|
2785
|
+
currentXhr = null;
|
|
2786
|
+
if (isStopped()) return;
|
|
2787
|
+
state.status = "error";
|
|
2788
|
+
throw error;
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
if (state.status !== "uploading") return;
|
|
2792
|
+
state.status = "completing";
|
|
2793
|
+
await options.adapter.complete([...completed]);
|
|
2794
|
+
state.status = "completed";
|
|
2795
|
+
emitProgress(0);
|
|
2796
|
+
}
|
|
2797
|
+
async function start() {
|
|
2798
|
+
if (run) return run;
|
|
2799
|
+
emitProgress(0);
|
|
2800
|
+
run = uploadFrom(resumeIndex).catch((error) => {
|
|
2801
|
+
if (isStopped()) return;
|
|
2802
|
+
state.status = "error";
|
|
2803
|
+
throw error;
|
|
2804
|
+
});
|
|
2805
|
+
return run;
|
|
2806
|
+
}
|
|
2807
|
+
function pause() {
|
|
2808
|
+
if (state.status !== "uploading") return;
|
|
2809
|
+
state.status = "paused";
|
|
2810
|
+
currentXhr?.abort();
|
|
2811
|
+
}
|
|
2812
|
+
async function resume() {
|
|
2813
|
+
if (state.status !== "paused") return;
|
|
2814
|
+
run = uploadFrom(resumeIndex).catch((error) => {
|
|
2815
|
+
if (isStopped()) return;
|
|
2816
|
+
state.status = "error";
|
|
2817
|
+
throw error;
|
|
2818
|
+
});
|
|
2819
|
+
return run;
|
|
2820
|
+
}
|
|
2821
|
+
async function cancel() {
|
|
2822
|
+
if (state.status === "completed" || state.status === "cancelled" || state.status === "idle") {
|
|
2823
|
+
state.status = "cancelled";
|
|
2824
|
+
return;
|
|
2825
|
+
}
|
|
2826
|
+
state.status = "cancelled";
|
|
2827
|
+
currentXhr?.abort();
|
|
2828
|
+
await options.adapter.abort?.();
|
|
2829
|
+
}
|
|
2830
|
+
return {
|
|
2831
|
+
start,
|
|
2832
|
+
pause,
|
|
2833
|
+
resume,
|
|
2834
|
+
cancel,
|
|
2835
|
+
getStatus: () => state.status
|
|
2836
|
+
};
|
|
2837
|
+
}
|
|
2838
|
+
function useMediaQuery2(query) {
|
|
2839
|
+
const [matches, setMatches] = React.useState(false);
|
|
2840
|
+
React.useEffect(() => {
|
|
2841
|
+
if (typeof window === "undefined" || !window.matchMedia) return;
|
|
2842
|
+
const mql = window.matchMedia(query);
|
|
2843
|
+
const onChange = () => setMatches(mql.matches);
|
|
2844
|
+
onChange();
|
|
2845
|
+
mql.addEventListener("change", onChange);
|
|
2846
|
+
return () => mql.removeEventListener("change", onChange);
|
|
2847
|
+
}, [query]);
|
|
2848
|
+
return matches;
|
|
2849
|
+
}
|
|
2850
|
+
|
|
2851
|
+
// src/lib/avatar.ts
|
|
2852
|
+
var EMPTY_COLORS = {
|
|
2853
|
+
backgroundColor: "#9ca3af",
|
|
2854
|
+
color: "#111111"
|
|
2855
|
+
};
|
|
2856
|
+
var AVATAR_PALETTE = [
|
|
2857
|
+
"#4f6d8c",
|
|
2858
|
+
// slate blue
|
|
2859
|
+
"#3d7a6a",
|
|
2860
|
+
// teal
|
|
2861
|
+
"#6b5b8a",
|
|
2862
|
+
// muted purple
|
|
2863
|
+
"#8a5a4a",
|
|
2864
|
+
// warm brown
|
|
2865
|
+
"#4a7a8a",
|
|
2866
|
+
// steel cyan
|
|
2867
|
+
"#7a6b4a",
|
|
2868
|
+
// olive
|
|
2869
|
+
"#5a6b8a",
|
|
2870
|
+
// periwinkle
|
|
2871
|
+
"#8a4a5a",
|
|
2872
|
+
// rose
|
|
2873
|
+
"#4a8a6b",
|
|
2874
|
+
// green
|
|
2875
|
+
"#6b4a7a",
|
|
2876
|
+
// plum
|
|
2877
|
+
"#8a6b4a",
|
|
2878
|
+
// amber brown
|
|
2879
|
+
"#4a5a8a",
|
|
2880
|
+
// indigo
|
|
2881
|
+
"#7a4a6b",
|
|
2882
|
+
// mauve
|
|
2883
|
+
"#5a8a4a",
|
|
2884
|
+
// leaf
|
|
2885
|
+
"#8a5a7a",
|
|
2886
|
+
// dusty pink
|
|
2887
|
+
"#3d6b7a"
|
|
2888
|
+
// deep teal
|
|
2889
|
+
];
|
|
2890
|
+
function hashString(input) {
|
|
2891
|
+
let hash = 2166136261;
|
|
2892
|
+
for (let i = 0; i < input.length; i++) {
|
|
2893
|
+
hash ^= input.charCodeAt(i);
|
|
2894
|
+
hash = Math.imul(hash, 16777619);
|
|
2895
|
+
}
|
|
2896
|
+
return hash >>> 0;
|
|
2897
|
+
}
|
|
2898
|
+
function srgbToLinear(channel) {
|
|
2899
|
+
const c = channel / 255;
|
|
2900
|
+
return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
|
2901
|
+
}
|
|
2902
|
+
function relativeLuminance(hex) {
|
|
2903
|
+
const normalized = hex.replace("#", "");
|
|
2904
|
+
const r = Number.parseInt(normalized.slice(0, 2), 16);
|
|
2905
|
+
const g = Number.parseInt(normalized.slice(2, 4), 16);
|
|
2906
|
+
const b = Number.parseInt(normalized.slice(4, 6), 16);
|
|
2907
|
+
return 0.2126 * srgbToLinear(r) + 0.7152 * srgbToLinear(g) + 0.0722 * srgbToLinear(b);
|
|
2908
|
+
}
|
|
2909
|
+
function contrastingTextColor(backgroundHex) {
|
|
2910
|
+
return relativeLuminance(backgroundHex) > 0.179 ? "#111111" : "#ffffff";
|
|
2911
|
+
}
|
|
2912
|
+
function getInitials(name, max = 2) {
|
|
2913
|
+
const parts = name.trim().split(/\s+/).filter(Boolean);
|
|
2914
|
+
if (parts.length === 0) return "?";
|
|
2915
|
+
return parts.slice(0, Math.max(1, max)).map((part) => part.charAt(0)).join("").toUpperCase();
|
|
2916
|
+
}
|
|
2917
|
+
function getAvatarColors(name) {
|
|
2918
|
+
const normalized = name.trim().toLowerCase();
|
|
2919
|
+
if (!normalized) {
|
|
2920
|
+
return { ...EMPTY_COLORS };
|
|
2921
|
+
}
|
|
2922
|
+
const backgroundColor = AVATAR_PALETTE[hashString(normalized) % AVATAR_PALETTE.length];
|
|
2923
|
+
return {
|
|
2924
|
+
backgroundColor,
|
|
2925
|
+
color: contrastingTextColor(backgroundColor)
|
|
2926
|
+
};
|
|
2927
|
+
}
|
|
2928
|
+
function avatarFallbackStyle(name) {
|
|
2929
|
+
return getAvatarColors(name);
|
|
2930
|
+
}
|
|
2931
|
+
function StatHighlightCard({
|
|
2932
|
+
label,
|
|
2933
|
+
value,
|
|
2934
|
+
hint,
|
|
2935
|
+
delta,
|
|
2936
|
+
deltaTone = "neutral",
|
|
2937
|
+
className
|
|
2938
|
+
}) {
|
|
2939
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2940
|
+
"div",
|
|
2941
|
+
{
|
|
2942
|
+
className: cn(
|
|
2943
|
+
"bg-card text-card-foreground rounded-xl border p-6 shadow-sm",
|
|
2944
|
+
className
|
|
2945
|
+
),
|
|
2946
|
+
children: [
|
|
2947
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground text-sm", children: label }),
|
|
2948
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-2xl font-semibold tracking-tight", children: value }),
|
|
2949
|
+
delta ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2950
|
+
"p",
|
|
2951
|
+
{
|
|
2952
|
+
className: cn(
|
|
2953
|
+
"mt-1 text-sm",
|
|
2954
|
+
deltaTone === "up" && "text-emerald-600 dark:text-emerald-400",
|
|
2955
|
+
deltaTone === "down" && "text-destructive",
|
|
2956
|
+
deltaTone === "neutral" && "text-muted-foreground"
|
|
2957
|
+
),
|
|
2958
|
+
children: delta
|
|
2959
|
+
}
|
|
2960
|
+
) : null,
|
|
2961
|
+
hint ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground mt-1 text-xs", children: hint }) : null
|
|
2962
|
+
]
|
|
2963
|
+
}
|
|
2964
|
+
);
|
|
2965
|
+
}
|
|
2966
|
+
function MetricChartCard({
|
|
2967
|
+
title,
|
|
2968
|
+
description,
|
|
2969
|
+
action,
|
|
2970
|
+
children,
|
|
2971
|
+
className
|
|
2972
|
+
}) {
|
|
2973
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2974
|
+
"div",
|
|
2975
|
+
{
|
|
2976
|
+
className: cn(
|
|
2977
|
+
"bg-card text-card-foreground rounded-xl border shadow-sm",
|
|
2978
|
+
className
|
|
2979
|
+
),
|
|
2980
|
+
children: [
|
|
2981
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-3 p-6 pb-2", children: [
|
|
2982
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2983
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "leading-none font-semibold tracking-tight", children: title }),
|
|
2984
|
+
description ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground mt-1.5 text-sm", children: description }) : null
|
|
2985
|
+
] }),
|
|
2986
|
+
action
|
|
2987
|
+
] }),
|
|
2988
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-6 pt-4", children })
|
|
2989
|
+
]
|
|
2990
|
+
}
|
|
2991
|
+
);
|
|
2992
|
+
}
|
|
2993
|
+
function polyline(points) {
|
|
2994
|
+
if (points.length === 0) return "";
|
|
2995
|
+
const min = Math.min(...points);
|
|
2996
|
+
const max = Math.max(...points);
|
|
2997
|
+
const span = max - min || 1;
|
|
2998
|
+
return points.map((point, index) => {
|
|
2999
|
+
const x = points.length === 1 ? 50 : index / (points.length - 1) * 100;
|
|
3000
|
+
const y = 28 - (point - min) / span * 24;
|
|
3001
|
+
return `${x.toFixed(2)},${y.toFixed(2)}`;
|
|
3002
|
+
}).join(" ");
|
|
3003
|
+
}
|
|
3004
|
+
function SparklineStat({
|
|
3005
|
+
label,
|
|
3006
|
+
value,
|
|
3007
|
+
points,
|
|
3008
|
+
className
|
|
3009
|
+
}) {
|
|
3010
|
+
const reduced = useMediaQuery2("(prefers-reduced-motion: reduce)");
|
|
3011
|
+
const path = polyline(points);
|
|
3012
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3013
|
+
"div",
|
|
3014
|
+
{
|
|
3015
|
+
className: cn(
|
|
3016
|
+
"bg-card text-card-foreground flex items-end justify-between gap-3 rounded-xl border p-4 shadow-sm",
|
|
3017
|
+
className
|
|
3018
|
+
),
|
|
3019
|
+
children: [
|
|
3020
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
3021
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground text-xs", children: label }),
|
|
3022
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-lg font-semibold tracking-tight", children: value })
|
|
3023
|
+
] }),
|
|
3024
|
+
path ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
3025
|
+
"svg",
|
|
3026
|
+
{
|
|
3027
|
+
viewBox: "0 0 100 32",
|
|
3028
|
+
className: cn(
|
|
3029
|
+
"text-primary h-8 w-24",
|
|
3030
|
+
!reduced && "motion-safe:transition-opacity"
|
|
3031
|
+
),
|
|
3032
|
+
"aria-hidden": "true",
|
|
3033
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3034
|
+
"polyline",
|
|
3035
|
+
{
|
|
3036
|
+
fill: "none",
|
|
3037
|
+
stroke: "currentColor",
|
|
3038
|
+
strokeWidth: "2",
|
|
3039
|
+
strokeLinejoin: "round",
|
|
3040
|
+
strokeLinecap: "round",
|
|
3041
|
+
points: path
|
|
3042
|
+
}
|
|
3043
|
+
)
|
|
3044
|
+
}
|
|
3045
|
+
) : null
|
|
3046
|
+
]
|
|
3047
|
+
}
|
|
3048
|
+
);
|
|
3049
|
+
}
|
|
3050
|
+
var TIME_RANGE_OPTIONS = ["day", "week", "month", "year"];
|
|
3051
|
+
var DEFAULT_LABELS2 = {
|
|
3052
|
+
day: "Day",
|
|
3053
|
+
week: "Week",
|
|
3054
|
+
month: "Month",
|
|
3055
|
+
year: "Year"
|
|
3056
|
+
};
|
|
3057
|
+
function TimeRangePicker({
|
|
3058
|
+
value,
|
|
3059
|
+
onChange,
|
|
3060
|
+
labels,
|
|
3061
|
+
className
|
|
3062
|
+
}) {
|
|
3063
|
+
const copy = { ...DEFAULT_LABELS2, ...labels };
|
|
3064
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3065
|
+
"div",
|
|
3066
|
+
{
|
|
3067
|
+
role: "group",
|
|
3068
|
+
"aria-label": "Time range",
|
|
3069
|
+
className: cn("flex flex-wrap gap-1", className),
|
|
3070
|
+
children: TIME_RANGE_OPTIONS.map((option) => {
|
|
3071
|
+
const selected = option === value;
|
|
3072
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3073
|
+
Button,
|
|
3074
|
+
{
|
|
3075
|
+
type: "button",
|
|
3076
|
+
size: "sm",
|
|
3077
|
+
variant: selected ? "default" : "outline",
|
|
3078
|
+
"aria-pressed": selected,
|
|
3079
|
+
onClick: () => onChange(option),
|
|
3080
|
+
children: copy[option]
|
|
3081
|
+
},
|
|
3082
|
+
option
|
|
3083
|
+
);
|
|
3084
|
+
})
|
|
3085
|
+
}
|
|
3086
|
+
);
|
|
3087
|
+
}
|
|
3088
|
+
function DimensionBreakdownTable({
|
|
3089
|
+
title,
|
|
3090
|
+
rows,
|
|
3091
|
+
empty,
|
|
3092
|
+
className
|
|
3093
|
+
}) {
|
|
3094
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3095
|
+
"div",
|
|
3096
|
+
{
|
|
3097
|
+
className: cn(
|
|
3098
|
+
"bg-card text-card-foreground overflow-hidden rounded-xl border shadow-sm",
|
|
3099
|
+
className
|
|
3100
|
+
),
|
|
3101
|
+
children: [
|
|
3102
|
+
title ? /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "px-6 pt-6 text-sm font-semibold tracking-tight", children: title }) : null,
|
|
3103
|
+
rows.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-6", children: empty }) : /* @__PURE__ */ jsxRuntime.jsx("table", { className: "w-full text-sm", children: /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: rows.map((row) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "border-t first:border-t-0", children: [
|
|
3104
|
+
/* @__PURE__ */ jsxRuntime.jsx("th", { scope: "row", className: "px-6 py-3 text-left font-medium", children: row.label }),
|
|
3105
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-3 text-right tabular-nums", children: row.value }),
|
|
3106
|
+
row.share ? /* @__PURE__ */ jsxRuntime.jsx("td", { className: "text-muted-foreground px-6 py-3 text-right tabular-nums", children: row.share }) : null
|
|
3107
|
+
] }, row.id)) }) })
|
|
3108
|
+
]
|
|
3109
|
+
}
|
|
3110
|
+
);
|
|
3111
|
+
}
|
|
3112
|
+
function CostBreakdownCard({
|
|
3113
|
+
title,
|
|
3114
|
+
total,
|
|
3115
|
+
items,
|
|
3116
|
+
className
|
|
3117
|
+
}) {
|
|
3118
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3119
|
+
"div",
|
|
3120
|
+
{
|
|
3121
|
+
className: cn(
|
|
3122
|
+
"bg-card text-card-foreground rounded-xl border p-6 shadow-sm",
|
|
3123
|
+
className
|
|
3124
|
+
),
|
|
3125
|
+
children: [
|
|
3126
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-baseline justify-between gap-3", children: [
|
|
3127
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-semibold tracking-tight", children: title }),
|
|
3128
|
+
total ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-lg font-semibold tabular-nums", children: total }) : null
|
|
3129
|
+
] }),
|
|
3130
|
+
/* @__PURE__ */ jsxRuntime.jsx("ul", { className: "mt-4 space-y-2", children: items.map((item) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3131
|
+
"li",
|
|
3132
|
+
{
|
|
3133
|
+
className: "flex items-baseline justify-between gap-3 text-sm",
|
|
3134
|
+
children: [
|
|
3135
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
3136
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { children: item.label }),
|
|
3137
|
+
item.basis ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground text-xs", children: item.basis }) : null
|
|
3138
|
+
] }),
|
|
3139
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "tabular-nums", children: item.amount })
|
|
3140
|
+
]
|
|
3141
|
+
},
|
|
3142
|
+
item.id
|
|
3143
|
+
)) })
|
|
3144
|
+
]
|
|
3145
|
+
}
|
|
3146
|
+
);
|
|
3147
|
+
}
|
|
3148
|
+
function MetricEmptyState({
|
|
3149
|
+
title,
|
|
3150
|
+
description,
|
|
3151
|
+
action,
|
|
3152
|
+
icon = lucideReact.BarChart3,
|
|
3153
|
+
className
|
|
3154
|
+
}) {
|
|
3155
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3156
|
+
EmptyState,
|
|
3157
|
+
{
|
|
3158
|
+
title,
|
|
3159
|
+
description,
|
|
3160
|
+
action,
|
|
3161
|
+
icon,
|
|
3162
|
+
className
|
|
3163
|
+
}
|
|
3164
|
+
);
|
|
3165
|
+
}
|
|
3166
|
+
function MetricSkeleton({ className, bars = 4 }) {
|
|
3167
|
+
const reduced = useMediaQuery2("(prefers-reduced-motion: reduce)");
|
|
3168
|
+
const count = Math.max(1, bars);
|
|
3169
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3170
|
+
"div",
|
|
3171
|
+
{
|
|
3172
|
+
role: "status",
|
|
3173
|
+
"aria-live": "polite",
|
|
3174
|
+
className: cn("bg-card rounded-xl border p-6 shadow-sm", className),
|
|
3175
|
+
children: [
|
|
3176
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3177
|
+
"div",
|
|
3178
|
+
{
|
|
3179
|
+
className: cn(
|
|
3180
|
+
"bg-muted mb-4 h-4 w-32 rounded",
|
|
3181
|
+
!reduced && "animate-pulse"
|
|
3182
|
+
)
|
|
3183
|
+
}
|
|
3184
|
+
),
|
|
3185
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-32 items-end gap-2", children: Array.from({ length: count }, (_, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3186
|
+
"div",
|
|
3187
|
+
{
|
|
3188
|
+
className: cn(
|
|
3189
|
+
"bg-muted flex-1 rounded-t",
|
|
3190
|
+
!reduced && "animate-pulse"
|
|
3191
|
+
),
|
|
3192
|
+
style: { height: `${40 + index * 17 % 50}%` }
|
|
3193
|
+
},
|
|
3194
|
+
index
|
|
3195
|
+
)) }),
|
|
3196
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", children: "Loading metrics" })
|
|
3197
|
+
]
|
|
3198
|
+
}
|
|
3199
|
+
);
|
|
3200
|
+
}
|
|
3201
|
+
|
|
3202
|
+
// src/lib/cookie-consent.ts
|
|
3203
|
+
var COOKIE_CONSENT_NAME = "yenio-cookie-consent";
|
|
3204
|
+
var COOKIE_CONSENT_VALUES = ["necessary", "all"];
|
|
3205
|
+
var COOKIE_CONSENT_MAX_AGE = 180 * 24 * 60 * 60;
|
|
3206
|
+
function isCookieConsentValue(value) {
|
|
3207
|
+
return typeof value === "string" && COOKIE_CONSENT_VALUES.includes(value);
|
|
3208
|
+
}
|
|
3209
|
+
function cookieConsentAllowsReplay(value) {
|
|
3210
|
+
return value === "all";
|
|
3211
|
+
}
|
|
3212
|
+
function cookieDomainFromHostname(hostname) {
|
|
3213
|
+
const host = hostname.toLowerCase();
|
|
3214
|
+
if (host === "localhost" || host === "127.0.0.1") return void 0;
|
|
3215
|
+
if (host.endsWith(".yenio.mx") || host === "yenio.mx") return ".yenio.mx";
|
|
3216
|
+
if (host.endsWith(".yenio-pld.workers.dev") || host === "yenio-pld.workers.dev") {
|
|
3217
|
+
return ".yenio-pld.workers.dev";
|
|
3218
|
+
}
|
|
3219
|
+
return void 0;
|
|
3220
|
+
}
|
|
3221
|
+
function readCookieMap(cookieSource) {
|
|
3222
|
+
const map = /* @__PURE__ */ new Map();
|
|
3223
|
+
for (const part of cookieSource.split(";")) {
|
|
3224
|
+
const trimmed = part.trim();
|
|
3225
|
+
if (!trimmed) continue;
|
|
3226
|
+
const eq = trimmed.indexOf("=");
|
|
3227
|
+
if (eq < 0) continue;
|
|
3228
|
+
const name = trimmed.slice(0, eq).trim();
|
|
3229
|
+
const raw = trimmed.slice(eq + 1).trim();
|
|
3230
|
+
map.set(name, decodeURIComponent(raw));
|
|
3231
|
+
}
|
|
3232
|
+
return map;
|
|
3233
|
+
}
|
|
3234
|
+
function parseCookieConsent(cookieSource) {
|
|
3235
|
+
const raw = readCookieMap(cookieSource).get(COOKIE_CONSENT_NAME);
|
|
3236
|
+
return isCookieConsentValue(raw) ? raw : null;
|
|
3237
|
+
}
|
|
3238
|
+
function readCookieConsent() {
|
|
3239
|
+
if (typeof document === "undefined") return null;
|
|
3240
|
+
return parseCookieConsent(document.cookie);
|
|
3241
|
+
}
|
|
3242
|
+
function serializeCookieConsent(value, options = {}) {
|
|
3243
|
+
const maxAge = options.maxAge ?? COOKIE_CONSENT_MAX_AGE;
|
|
3244
|
+
const secure = options.secure ?? (typeof window !== "undefined" && window.location.protocol === "https:");
|
|
3245
|
+
const parts = [
|
|
3246
|
+
`${COOKIE_CONSENT_NAME}=${encodeURIComponent(value)}`,
|
|
3247
|
+
"Path=/",
|
|
3248
|
+
`Max-Age=${maxAge}`,
|
|
3249
|
+
"SameSite=Lax"
|
|
3250
|
+
];
|
|
3251
|
+
if (options.domain) {
|
|
3252
|
+
parts.push(`Domain=${options.domain}`);
|
|
3253
|
+
}
|
|
3254
|
+
if (secure) {
|
|
3255
|
+
parts.push("Secure");
|
|
3256
|
+
}
|
|
3257
|
+
return parts.join("; ");
|
|
3258
|
+
}
|
|
3259
|
+
function writeCookieConsent(value, options = {}) {
|
|
3260
|
+
if (typeof document === "undefined") return;
|
|
3261
|
+
document.cookie = serializeCookieConsent(value, options);
|
|
3262
|
+
}
|
|
3263
|
+
function isEmbeddedWindow() {
|
|
3264
|
+
if (typeof window === "undefined") return false;
|
|
3265
|
+
try {
|
|
3266
|
+
return window.self !== window.top;
|
|
3267
|
+
} catch {
|
|
3268
|
+
return true;
|
|
3269
|
+
}
|
|
3270
|
+
}
|
|
3271
|
+
var DEFAULT_COOKIE_CONSENT_BANNER_LABELS = {
|
|
3272
|
+
message: "We use necessary cookies to run Yenio. Extra cookies can be used for diagnostics if you opt in.",
|
|
3273
|
+
acceptAll: "Accept all",
|
|
3274
|
+
necessaryOnly: "Necessary only",
|
|
3275
|
+
policy: "Cookies policy"
|
|
3276
|
+
};
|
|
3277
|
+
function CookieConsentBanner({
|
|
3278
|
+
domain,
|
|
3279
|
+
policyHref,
|
|
3280
|
+
labels,
|
|
3281
|
+
onChoice,
|
|
3282
|
+
variant = "default",
|
|
3283
|
+
className
|
|
3284
|
+
}) {
|
|
3285
|
+
const copy = { ...DEFAULT_COOKIE_CONSENT_BANNER_LABELS, ...labels };
|
|
3286
|
+
const [consent, setConsent] = React.useState(
|
|
3287
|
+
"pending"
|
|
3288
|
+
);
|
|
3289
|
+
React.useEffect(() => {
|
|
3290
|
+
if (variant === "embed" || isEmbeddedWindow()) {
|
|
3291
|
+
setConsent("necessary");
|
|
3292
|
+
return;
|
|
3293
|
+
}
|
|
3294
|
+
setConsent(readCookieConsent());
|
|
3295
|
+
}, [variant]);
|
|
3296
|
+
if (variant === "embed") return null;
|
|
3297
|
+
if (consent === "pending" || consent) return null;
|
|
3298
|
+
const choose = (value) => {
|
|
3299
|
+
const resolvedDomain = domain ?? (typeof window === "undefined" ? void 0 : cookieDomainFromHostname(window.location.hostname));
|
|
3300
|
+
writeCookieConsent(value, { domain: resolvedDomain });
|
|
3301
|
+
setConsent(value);
|
|
3302
|
+
onChoice?.(value);
|
|
3303
|
+
};
|
|
3304
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3305
|
+
"div",
|
|
3306
|
+
{
|
|
3307
|
+
role: "dialog",
|
|
3308
|
+
"aria-label": copy.message,
|
|
3309
|
+
className: cn(
|
|
3310
|
+
"fixed inset-x-0 bottom-0 z-50 border-t bg-background/95 p-4 shadow-lg backdrop-blur-sm",
|
|
3311
|
+
className
|
|
3312
|
+
),
|
|
3313
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto flex w-full max-w-5xl flex-col gap-3 sm:flex-row sm:items-center sm:justify-between", children: [
|
|
3314
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-muted-foreground", children: [
|
|
3315
|
+
copy.message,
|
|
3316
|
+
policyHref ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
3317
|
+
" ",
|
|
3318
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3319
|
+
"a",
|
|
3320
|
+
{
|
|
3321
|
+
href: policyHref,
|
|
3322
|
+
className: "text-foreground underline underline-offset-4",
|
|
3323
|
+
children: copy.policy
|
|
3324
|
+
}
|
|
3325
|
+
)
|
|
3326
|
+
] }) : null
|
|
3327
|
+
] }),
|
|
3328
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 flex-wrap gap-2", children: [
|
|
3329
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3330
|
+
Button,
|
|
3331
|
+
{
|
|
3332
|
+
type: "button",
|
|
3333
|
+
variant: "outline",
|
|
3334
|
+
onClick: () => choose("necessary"),
|
|
3335
|
+
children: copy.necessaryOnly
|
|
3336
|
+
}
|
|
3337
|
+
),
|
|
3338
|
+
/* @__PURE__ */ jsxRuntime.jsx(Button, { type: "button", onClick: () => choose("all"), children: copy.acceptAll })
|
|
3339
|
+
] })
|
|
3340
|
+
] })
|
|
3341
|
+
}
|
|
3342
|
+
);
|
|
3343
|
+
}
|
|
3344
|
+
function LegalDocLink({
|
|
3345
|
+
href,
|
|
3346
|
+
children
|
|
3347
|
+
}) {
|
|
3348
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3349
|
+
"a",
|
|
3350
|
+
{
|
|
3351
|
+
href,
|
|
3352
|
+
target: "_blank",
|
|
3353
|
+
rel: "noopener noreferrer",
|
|
3354
|
+
className: "underline underline-offset-4",
|
|
3355
|
+
children
|
|
3356
|
+
}
|
|
3357
|
+
);
|
|
3358
|
+
}
|
|
3359
|
+
function wrapPhrases(label, terms, privacy, termsHref, privacyHref) {
|
|
3360
|
+
const hits = [];
|
|
3361
|
+
if (terms) {
|
|
3362
|
+
const start = label.indexOf(terms);
|
|
3363
|
+
if (start >= 0) {
|
|
3364
|
+
hits.push({
|
|
3365
|
+
start,
|
|
3366
|
+
end: start + terms.length,
|
|
3367
|
+
href: termsHref,
|
|
3368
|
+
text: terms
|
|
3369
|
+
});
|
|
3370
|
+
}
|
|
3371
|
+
}
|
|
3372
|
+
if (privacy) {
|
|
3373
|
+
const start = label.indexOf(privacy);
|
|
3374
|
+
if (start >= 0) {
|
|
3375
|
+
hits.push({
|
|
3376
|
+
start,
|
|
3377
|
+
end: start + privacy.length,
|
|
3378
|
+
href: privacyHref,
|
|
3379
|
+
text: privacy
|
|
3380
|
+
});
|
|
3381
|
+
}
|
|
3382
|
+
}
|
|
3383
|
+
hits.sort((a, b) => a.start - b.start || b.end - b.start - (a.end - a.start));
|
|
3384
|
+
const kept = [];
|
|
3385
|
+
for (const hit of hits) {
|
|
3386
|
+
const last = kept[kept.length - 1];
|
|
3387
|
+
if (last && hit.start < last.end) {
|
|
3388
|
+
if (hit.end - hit.start > last.end - last.start) {
|
|
3389
|
+
kept[kept.length - 1] = hit;
|
|
3390
|
+
}
|
|
3391
|
+
continue;
|
|
3392
|
+
}
|
|
3393
|
+
kept.push(hit);
|
|
3394
|
+
}
|
|
3395
|
+
if (kept.length === 0) {
|
|
3396
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
3397
|
+
label,
|
|
3398
|
+
" ",
|
|
3399
|
+
/* @__PURE__ */ jsxRuntime.jsx(LegalDocLink, { href: termsHref, children: terms }),
|
|
3400
|
+
" \xB7 ",
|
|
3401
|
+
/* @__PURE__ */ jsxRuntime.jsx(LegalDocLink, { href: privacyHref, children: privacy })
|
|
3402
|
+
] });
|
|
3403
|
+
}
|
|
3404
|
+
const nodes = [];
|
|
3405
|
+
let cursor = 0;
|
|
3406
|
+
kept.forEach((hit, index) => {
|
|
3407
|
+
if (hit.start > cursor) {
|
|
3408
|
+
nodes.push(label.slice(cursor, hit.start));
|
|
3409
|
+
}
|
|
3410
|
+
nodes.push(
|
|
3411
|
+
/* @__PURE__ */ jsxRuntime.jsx(LegalDocLink, { href: hit.href, children: hit.text }, `${hit.text}-${index}`)
|
|
3412
|
+
);
|
|
3413
|
+
cursor = hit.end;
|
|
3414
|
+
});
|
|
3415
|
+
if (cursor < label.length) {
|
|
3416
|
+
nodes.push(label.slice(cursor));
|
|
3417
|
+
}
|
|
3418
|
+
return nodes;
|
|
3419
|
+
}
|
|
3420
|
+
function renderLegalAcceptLabel(label, terms, privacy, termsHref, privacyHref) {
|
|
3421
|
+
if (label.includes("{terms}") || label.includes("{privacy}")) {
|
|
3422
|
+
return label.split(/(\{terms\}|\{privacy\})/g).map((part, index) => {
|
|
3423
|
+
if (part === "{terms}") {
|
|
3424
|
+
return /* @__PURE__ */ jsxRuntime.jsx(LegalDocLink, { href: termsHref, children: terms }, `terms-${index}`);
|
|
3425
|
+
}
|
|
3426
|
+
if (part === "{privacy}") {
|
|
3427
|
+
return /* @__PURE__ */ jsxRuntime.jsx(LegalDocLink, { href: privacyHref, children: privacy }, `privacy-${index}`);
|
|
3428
|
+
}
|
|
3429
|
+
return part;
|
|
3430
|
+
});
|
|
3431
|
+
}
|
|
3432
|
+
return wrapPhrases(label, terms, privacy, termsHref, privacyHref);
|
|
3433
|
+
}
|
|
3434
|
+
var DEFAULT_LEGAL_ACCEPT_DIALOG_LABELS = {
|
|
3435
|
+
title: "Updated terms",
|
|
3436
|
+
description: "We updated the Terms of Service and Privacy Policy. Please accept to continue.",
|
|
3437
|
+
accept: "Accept",
|
|
3438
|
+
terms: "Terms of Service",
|
|
3439
|
+
privacy: "Privacy Policy"
|
|
3440
|
+
};
|
|
3441
|
+
function LegalAcceptDialog({
|
|
3442
|
+
open,
|
|
3443
|
+
termsHref,
|
|
3444
|
+
privacyHref,
|
|
3445
|
+
summary,
|
|
3446
|
+
accepting = false,
|
|
3447
|
+
labels,
|
|
3448
|
+
onAccept
|
|
3449
|
+
}) {
|
|
3450
|
+
const copy = { ...DEFAULT_LEGAL_ACCEPT_DIALOG_LABELS, ...labels };
|
|
3451
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3452
|
+
AlertDialog,
|
|
3453
|
+
{
|
|
3454
|
+
open,
|
|
3455
|
+
onOpenChange: (next) => {
|
|
3456
|
+
if (next) return;
|
|
3457
|
+
},
|
|
3458
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3459
|
+
AlertDialogContent,
|
|
3460
|
+
{
|
|
3461
|
+
onEscapeKeyDown: (event) => {
|
|
3462
|
+
event.preventDefault();
|
|
3463
|
+
},
|
|
3464
|
+
children: [
|
|
3465
|
+
/* @__PURE__ */ jsxRuntime.jsxs(AlertDialogHeader, { children: [
|
|
3466
|
+
/* @__PURE__ */ jsxRuntime.jsx(AlertDialogTitle, { children: copy.title }),
|
|
3467
|
+
/* @__PURE__ */ jsxRuntime.jsxs(AlertDialogDescription, { children: [
|
|
3468
|
+
renderLegalAcceptLabel(
|
|
3469
|
+
copy.description,
|
|
3470
|
+
copy.terms,
|
|
3471
|
+
copy.privacy,
|
|
3472
|
+
termsHref,
|
|
3473
|
+
privacyHref
|
|
3474
|
+
),
|
|
3475
|
+
summary ? ` ${summary}` : ""
|
|
3476
|
+
] })
|
|
3477
|
+
] }),
|
|
3478
|
+
/* @__PURE__ */ jsxRuntime.jsx(AlertDialogFooter, { children: /* @__PURE__ */ jsxRuntime.jsx(Button, { type: "button", disabled: accepting, onClick: onAccept, children: copy.accept }) })
|
|
3479
|
+
]
|
|
3480
|
+
}
|
|
3481
|
+
)
|
|
3482
|
+
}
|
|
3483
|
+
);
|
|
3484
|
+
}
|
|
3485
|
+
var DEFAULT_LEGAL_ACCEPT_CHECKBOX_LABELS = {
|
|
3486
|
+
label: "I agree to the {terms} and {privacy}",
|
|
3487
|
+
terms: "Terms of Service",
|
|
3488
|
+
privacy: "Privacy Policy"
|
|
3489
|
+
};
|
|
3490
|
+
function LegalAcceptCheckbox({
|
|
3491
|
+
id = "legal-accept",
|
|
3492
|
+
checked,
|
|
3493
|
+
onCheckedChange,
|
|
3494
|
+
termsHref,
|
|
3495
|
+
privacyHref,
|
|
3496
|
+
labels,
|
|
3497
|
+
disabled = false,
|
|
3498
|
+
className
|
|
3499
|
+
}) {
|
|
3500
|
+
const copy = { ...DEFAULT_LEGAL_ACCEPT_CHECKBOX_LABELS, ...labels };
|
|
3501
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex items-start gap-3", className), children: [
|
|
3502
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3503
|
+
"input",
|
|
3504
|
+
{
|
|
3505
|
+
id,
|
|
3506
|
+
type: "checkbox",
|
|
3507
|
+
checked,
|
|
3508
|
+
disabled,
|
|
3509
|
+
onChange: (event) => onCheckedChange(event.target.checked),
|
|
3510
|
+
className: "mt-1 size-4 shrink-0 accent-primary"
|
|
3511
|
+
}
|
|
3512
|
+
),
|
|
3513
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: id, className: "text-sm leading-5", children: renderLegalAcceptLabel(
|
|
3514
|
+
copy.label,
|
|
3515
|
+
copy.terms,
|
|
3516
|
+
copy.privacy,
|
|
3517
|
+
termsHref,
|
|
3518
|
+
privacyHref
|
|
3519
|
+
) })
|
|
3520
|
+
] });
|
|
3521
|
+
}
|
|
3522
|
+
function sanitizeHref(href) {
|
|
3523
|
+
const trimmed = href.trim();
|
|
3524
|
+
if (/^https?:\/\//i.test(trimmed) || /^mailto:/i.test(trimmed)) {
|
|
3525
|
+
return trimmed;
|
|
3526
|
+
}
|
|
3527
|
+
if (trimmed.startsWith("/") && !trimmed.startsWith("//")) {
|
|
3528
|
+
return trimmed;
|
|
3529
|
+
}
|
|
3530
|
+
return null;
|
|
3531
|
+
}
|
|
3532
|
+
function renderInline(text) {
|
|
3533
|
+
const nodes = [];
|
|
3534
|
+
const pattern = /(\*\*[^*]+\*\*|\*[^*]+\*|`[^`]+`|\[[^\]]+\]\([^)]+\))/g;
|
|
3535
|
+
let last = 0;
|
|
3536
|
+
let match;
|
|
3537
|
+
let key = 0;
|
|
3538
|
+
while ((match = pattern.exec(text)) !== null) {
|
|
3539
|
+
if (match.index > last) {
|
|
3540
|
+
nodes.push(text.slice(last, match.index));
|
|
3541
|
+
}
|
|
3542
|
+
const token = match[0];
|
|
3543
|
+
if (token.startsWith("**")) {
|
|
3544
|
+
nodes.push(/* @__PURE__ */ jsxRuntime.jsx("strong", { children: token.slice(2, -2) }, key));
|
|
3545
|
+
} else if (token.startsWith("*")) {
|
|
3546
|
+
nodes.push(/* @__PURE__ */ jsxRuntime.jsx("em", { children: token.slice(1, -1) }, key));
|
|
3547
|
+
} else if (token.startsWith("`")) {
|
|
3548
|
+
nodes.push(
|
|
3549
|
+
/* @__PURE__ */ jsxRuntime.jsx("code", { className: "rounded bg-muted px-1 py-0.5 text-[0.9em]", children: token.slice(1, -1) }, key)
|
|
3550
|
+
);
|
|
3551
|
+
} else {
|
|
3552
|
+
const link = token.match(/^\[([^\]]+)\]\(([^)]+)\)$/);
|
|
3553
|
+
const href = link ? sanitizeHref(link[2] ?? "") : null;
|
|
3554
|
+
if (link && href) {
|
|
3555
|
+
nodes.push(
|
|
3556
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3557
|
+
"a",
|
|
3558
|
+
{
|
|
3559
|
+
href,
|
|
3560
|
+
className: "underline underline-offset-4",
|
|
3561
|
+
rel: "noopener noreferrer",
|
|
3562
|
+
children: link[1]
|
|
3563
|
+
},
|
|
3564
|
+
key
|
|
3565
|
+
)
|
|
3566
|
+
);
|
|
3567
|
+
} else {
|
|
3568
|
+
nodes.push(token);
|
|
3569
|
+
}
|
|
3570
|
+
}
|
|
3571
|
+
key += 1;
|
|
3572
|
+
last = match.index + token.length;
|
|
3573
|
+
}
|
|
3574
|
+
if (last < text.length) {
|
|
3575
|
+
nodes.push(text.slice(last));
|
|
3576
|
+
}
|
|
3577
|
+
return nodes;
|
|
3578
|
+
}
|
|
3579
|
+
function headingTag(line) {
|
|
3580
|
+
if (line.startsWith("### ")) return { Tag: "h3", text: line.slice(4) };
|
|
3581
|
+
if (line.startsWith("## ")) return { Tag: "h2", text: line.slice(3) };
|
|
3582
|
+
if (line.startsWith("# ")) return { Tag: "h1", text: line.slice(2) };
|
|
3583
|
+
return null;
|
|
3584
|
+
}
|
|
3585
|
+
function isUnorderedItem(line) {
|
|
3586
|
+
return /^[-*] /.test(line);
|
|
3587
|
+
}
|
|
3588
|
+
function isOrderedItem(line) {
|
|
3589
|
+
return /^\d+\. /.test(line);
|
|
3590
|
+
}
|
|
3591
|
+
function LegalMarkdown({ markdown, className }) {
|
|
3592
|
+
const blocks = markdown.replace(/\r\n/g, "\n").trim().split(/\n{2,}/);
|
|
3593
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3594
|
+
"div",
|
|
3595
|
+
{
|
|
3596
|
+
className: cn(
|
|
3597
|
+
"legal-markdown space-y-4 text-sm leading-6 [&_h1]:text-3xl [&_h1]:font-semibold [&_h2]:text-xl [&_h2]:font-semibold [&_h3]:text-lg [&_h3]:font-semibold [&_ul]:list-disc [&_ul]:pl-5 [&_ol]:list-decimal [&_ol]:pl-5",
|
|
3598
|
+
className
|
|
3599
|
+
),
|
|
3600
|
+
children: blocks.map((block, index) => {
|
|
3601
|
+
const lines = block.split("\n");
|
|
3602
|
+
const heading = headingTag(lines[0] ?? "");
|
|
3603
|
+
if (heading && lines.length === 1) {
|
|
3604
|
+
const { Tag, text } = heading;
|
|
3605
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Tag, { children: renderInline(text) }, index);
|
|
3606
|
+
}
|
|
3607
|
+
if (lines.every(isUnorderedItem)) {
|
|
3608
|
+
return /* @__PURE__ */ jsxRuntime.jsx("ul", { children: lines.map((line, itemIndex) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: renderInline(line.replace(/^[-*] /, "")) }, itemIndex)) }, index);
|
|
3609
|
+
}
|
|
3610
|
+
if (lines.every(isOrderedItem)) {
|
|
3611
|
+
return /* @__PURE__ */ jsxRuntime.jsx("ol", { children: lines.map((line, itemIndex) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: renderInline(line.replace(/^\d+\. /, "")) }, itemIndex)) }, index);
|
|
3612
|
+
}
|
|
3613
|
+
return /* @__PURE__ */ jsxRuntime.jsx("p", { children: renderInline(lines.join(" ")) }, index);
|
|
3614
|
+
})
|
|
3615
|
+
}
|
|
3616
|
+
);
|
|
3617
|
+
}
|
|
3618
|
+
|
|
3619
|
+
// src/theme-init.ts
|
|
3620
|
+
var THEME_STORAGE_KEY = "theme";
|
|
3621
|
+
var THEME_INIT_SCRIPT_ID = "yenio-theme-init";
|
|
3622
|
+
var THEME_INIT_SCRIPT = `(function(){var t="light";var s;try{s=localStorage.getItem("${THEME_STORAGE_KEY}")}catch(e){}if(s==="dark"||s==="light"){t=s}else{try{if(window.matchMedia("(prefers-color-scheme: dark)").matches)t="dark"}catch(e){}}var r=document.documentElement;r.classList.add(t);r.style.colorScheme=t})();`;
|
|
3623
|
+
var DEFAULT_AUDIT_TIMELINE_LABELS = {
|
|
3624
|
+
emptyTitle: "No events",
|
|
3625
|
+
emptyDescription: "Audited activity will appear here.",
|
|
3626
|
+
actor: "Actor",
|
|
3627
|
+
subject: "Subject"
|
|
3628
|
+
};
|
|
3629
|
+
function formatOccurredAt(occurredAt) {
|
|
3630
|
+
const date = new Date(occurredAt);
|
|
3631
|
+
if (Number.isNaN(date.getTime())) return String(occurredAt);
|
|
3632
|
+
return new Intl.DateTimeFormat("en", {
|
|
3633
|
+
dateStyle: "medium",
|
|
3634
|
+
timeStyle: "short",
|
|
3635
|
+
timeZone: "America/Mexico_City"
|
|
3636
|
+
}).format(date);
|
|
3637
|
+
}
|
|
3638
|
+
function actorLine(actor) {
|
|
3639
|
+
const role = actor.organizationRole ? ` (${actor.organizationRole})` : "";
|
|
3640
|
+
return `${actor.kind} ${actor.id}${role}`;
|
|
3641
|
+
}
|
|
3642
|
+
function AuditTimeline({
|
|
3643
|
+
items,
|
|
3644
|
+
labels,
|
|
3645
|
+
emptyAction,
|
|
3646
|
+
className,
|
|
3647
|
+
formatOccurredAt: format = formatOccurredAt
|
|
3648
|
+
}) {
|
|
3649
|
+
const copy = { ...DEFAULT_AUDIT_TIMELINE_LABELS, ...labels };
|
|
3650
|
+
if (items.length === 0) {
|
|
3651
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3652
|
+
EmptyState,
|
|
3653
|
+
{
|
|
3654
|
+
className,
|
|
3655
|
+
title: copy.emptyTitle,
|
|
3656
|
+
description: copy.emptyDescription,
|
|
3657
|
+
action: emptyAction
|
|
3658
|
+
}
|
|
3659
|
+
);
|
|
3660
|
+
}
|
|
3661
|
+
return /* @__PURE__ */ jsxRuntime.jsx("ol", { className: cn("flex flex-col gap-3", className), children: items.map((item) => {
|
|
3662
|
+
const occurred = new Date(item.occurredAt);
|
|
3663
|
+
const iso = Number.isNaN(occurred.getTime()) ? void 0 : occurred.toISOString();
|
|
3664
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3665
|
+
"li",
|
|
3666
|
+
{
|
|
3667
|
+
className: "rounded-xl border border-border bg-card p-4 text-card-foreground",
|
|
3668
|
+
children: [
|
|
3669
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-baseline justify-between gap-2", children: [
|
|
3670
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-mono text-sm font-medium", children: item.type }),
|
|
3671
|
+
/* @__PURE__ */ jsxRuntime.jsx("time", { className: "text-xs text-muted-foreground", dateTime: iso, children: format(item.occurredAt) })
|
|
3672
|
+
] }),
|
|
3673
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm", children: item.summary }),
|
|
3674
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "mt-2 text-xs text-muted-foreground", children: [
|
|
3675
|
+
copy.actor,
|
|
3676
|
+
": ",
|
|
3677
|
+
actorLine(item.actor)
|
|
3678
|
+
] }),
|
|
3679
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
3680
|
+
copy.subject,
|
|
3681
|
+
": ",
|
|
3682
|
+
item.subject.type,
|
|
3683
|
+
" ",
|
|
3684
|
+
item.subject.id
|
|
3685
|
+
] })
|
|
3686
|
+
]
|
|
3687
|
+
},
|
|
3688
|
+
item.id
|
|
3689
|
+
);
|
|
3690
|
+
}) });
|
|
3691
|
+
}
|
|
3692
|
+
var DEFAULT_RISK_BADGE_LABELS = {
|
|
3693
|
+
low: "Low",
|
|
3694
|
+
medium: "Medium",
|
|
3695
|
+
high: "High",
|
|
3696
|
+
unverifiedRule: "Unverified rule"
|
|
3697
|
+
};
|
|
3698
|
+
var tone = {
|
|
3699
|
+
low: "bg-muted text-muted-foreground",
|
|
3700
|
+
medium: "bg-accent text-accent-foreground",
|
|
3701
|
+
high: "bg-destructive/15 text-destructive"
|
|
3702
|
+
};
|
|
3703
|
+
function severityLabel(severity, labels) {
|
|
3704
|
+
switch (severity) {
|
|
3705
|
+
case "low":
|
|
3706
|
+
return labels.low;
|
|
3707
|
+
case "medium":
|
|
3708
|
+
return labels.medium;
|
|
3709
|
+
case "high":
|
|
3710
|
+
return labels.high;
|
|
3711
|
+
default: {
|
|
3712
|
+
const exhaustive = severity;
|
|
3713
|
+
return exhaustive;
|
|
3714
|
+
}
|
|
3715
|
+
}
|
|
3716
|
+
}
|
|
3717
|
+
function RiskBadge({
|
|
3718
|
+
severity,
|
|
3719
|
+
smeVerified,
|
|
3720
|
+
labels,
|
|
3721
|
+
className
|
|
3722
|
+
}) {
|
|
3723
|
+
const copy = { ...DEFAULT_RISK_BADGE_LABELS, ...labels };
|
|
3724
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cn("inline-flex flex-wrap items-center gap-2", className), children: [
|
|
3725
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3726
|
+
"span",
|
|
3727
|
+
{
|
|
3728
|
+
className: cn(
|
|
3729
|
+
"inline-flex items-center rounded-md px-2 py-0.5 text-xs font-medium",
|
|
3730
|
+
tone[severity]
|
|
3731
|
+
),
|
|
3732
|
+
children: severityLabel(severity, copy)
|
|
3733
|
+
}
|
|
3734
|
+
),
|
|
3735
|
+
smeVerified === false ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: copy.unverifiedRule }) : null
|
|
3736
|
+
] });
|
|
3737
|
+
}
|
|
3738
|
+
var DEFAULT_ENTITY_GRAPH_LABELS = {
|
|
3739
|
+
emptyTitle: "No beneficial owners",
|
|
3740
|
+
emptyDescription: "Ownership and control relationships will appear here.",
|
|
3741
|
+
ownership: "Ownership",
|
|
3742
|
+
otherMeans: "Other means",
|
|
3743
|
+
seniorManager: "Senior manager"
|
|
3744
|
+
};
|
|
3745
|
+
function controlLabel(controlKind, labels) {
|
|
3746
|
+
switch (controlKind) {
|
|
3747
|
+
case "ownership":
|
|
3748
|
+
return labels.ownership;
|
|
3749
|
+
case "other_means":
|
|
3750
|
+
return labels.otherMeans;
|
|
3751
|
+
case "senior_manager":
|
|
3752
|
+
return labels.seniorManager;
|
|
3753
|
+
default: {
|
|
3754
|
+
const exhaustive = controlKind;
|
|
3755
|
+
return exhaustive;
|
|
3756
|
+
}
|
|
3757
|
+
}
|
|
3758
|
+
}
|
|
3759
|
+
function formatOwnership(bps) {
|
|
3760
|
+
const percent = bps / 100;
|
|
3761
|
+
const text = Number.isInteger(percent) ? String(percent) : percent.toFixed(2);
|
|
3762
|
+
return `${text}% (${bps} bps)`;
|
|
3763
|
+
}
|
|
3764
|
+
function nodeLabel(nodes, id) {
|
|
3765
|
+
return nodes.find((node) => node.id === id)?.label ?? id;
|
|
3766
|
+
}
|
|
3767
|
+
function EntityGraph({
|
|
3768
|
+
nodes,
|
|
3769
|
+
edges,
|
|
3770
|
+
labels,
|
|
3771
|
+
emptyAction,
|
|
3772
|
+
className
|
|
3773
|
+
}) {
|
|
3774
|
+
const copy = { ...DEFAULT_ENTITY_GRAPH_LABELS, ...labels };
|
|
3775
|
+
if (nodes.length === 0 && edges.length === 0) {
|
|
3776
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3777
|
+
EmptyState,
|
|
3778
|
+
{
|
|
3779
|
+
className,
|
|
3780
|
+
title: copy.emptyTitle,
|
|
3781
|
+
description: copy.emptyDescription,
|
|
3782
|
+
action: emptyAction
|
|
3783
|
+
}
|
|
3784
|
+
);
|
|
3785
|
+
}
|
|
3786
|
+
const nodeIds = new Set(nodes.map((node) => node.id));
|
|
3787
|
+
const unattached = edges.filter((edge) => !nodeIds.has(edge.from));
|
|
3788
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex flex-col gap-3", className), children: [
|
|
3789
|
+
nodes.map((node) => {
|
|
3790
|
+
const outgoing = edges.filter((edge) => edge.from === node.id);
|
|
3791
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3792
|
+
"section",
|
|
3793
|
+
{
|
|
3794
|
+
className: "rounded-xl border border-border bg-card p-4 text-card-foreground",
|
|
3795
|
+
children: [
|
|
3796
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-medium", children: node.label }),
|
|
3797
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: node.kind }),
|
|
3798
|
+
outgoing.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "mt-3 flex flex-col gap-1 text-sm", children: outgoing.map((edge) => /* @__PURE__ */ jsxRuntime.jsxs("li", { children: [
|
|
3799
|
+
node.label,
|
|
3800
|
+
" \u2192 ",
|
|
3801
|
+
nodeLabel(nodes, edge.to),
|
|
3802
|
+
":",
|
|
3803
|
+
" ",
|
|
3804
|
+
controlLabel(edge.controlKind, copy),
|
|
3805
|
+
typeof edge.ownershipBps === "number" ? ` ${formatOwnership(edge.ownershipBps)}` : null
|
|
3806
|
+
] }, `${edge.from}-${edge.to}-${edge.controlKind}`)) }) : null
|
|
3807
|
+
]
|
|
3808
|
+
},
|
|
3809
|
+
node.id
|
|
3810
|
+
);
|
|
3811
|
+
}),
|
|
3812
|
+
unattached.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "flex flex-col gap-1 text-sm", children: unattached.map((edge) => /* @__PURE__ */ jsxRuntime.jsxs("li", { children: [
|
|
3813
|
+
nodeLabel(nodes, edge.from),
|
|
3814
|
+
" \u2192 ",
|
|
3815
|
+
nodeLabel(nodes, edge.to),
|
|
3816
|
+
":",
|
|
3817
|
+
" ",
|
|
3818
|
+
controlLabel(edge.controlKind, copy),
|
|
3819
|
+
typeof edge.ownershipBps === "number" ? ` ${formatOwnership(edge.ownershipBps)}` : null
|
|
3820
|
+
] }, `${edge.from}-${edge.to}-${edge.controlKind}`)) }) : null
|
|
3821
|
+
] });
|
|
3822
|
+
}
|
|
3823
|
+
var DEFAULT_MATCH_REVIEW_LABELS = {
|
|
3824
|
+
emptyTitle: "No match",
|
|
3825
|
+
emptyDescription: "There is no hit to review.",
|
|
3826
|
+
source: "Source",
|
|
3827
|
+
score: "Score",
|
|
3828
|
+
matchedFields: "Matched fields",
|
|
3829
|
+
noMatchedFields: "None",
|
|
3830
|
+
status: "Status",
|
|
3831
|
+
unverified: "Unverified",
|
|
3832
|
+
reason: "Reason",
|
|
3833
|
+
reasonPlaceholder: "Why this decision",
|
|
3834
|
+
confirm: "Confirm",
|
|
3835
|
+
dismiss: "Dismiss"
|
|
3836
|
+
};
|
|
3837
|
+
function sourceUrlIsHttps(url) {
|
|
3838
|
+
try {
|
|
3839
|
+
return new URL(url).protocol === "https:";
|
|
3840
|
+
} catch {
|
|
3841
|
+
return false;
|
|
3842
|
+
}
|
|
3843
|
+
}
|
|
3844
|
+
function matchClaimIsUnverified(hit) {
|
|
3845
|
+
if (hit.unverified === true) return true;
|
|
3846
|
+
return (hit.sources ?? []).some((source) => !sourceUrlIsHttps(source.url));
|
|
3847
|
+
}
|
|
3848
|
+
function appearanceOf(hit) {
|
|
3849
|
+
if (matchClaimIsUnverified(hit)) return "unverified";
|
|
3850
|
+
return hit.status;
|
|
3851
|
+
}
|
|
3852
|
+
var appearanceClass = {
|
|
3853
|
+
unverified: "border-dashed border-border bg-muted/40",
|
|
3854
|
+
pending: "border-border bg-card",
|
|
3855
|
+
confirmed: "border-primary bg-primary/5",
|
|
3856
|
+
dismissed: "border-border bg-muted/30"
|
|
3857
|
+
};
|
|
3858
|
+
function MatchReview({
|
|
3859
|
+
hit,
|
|
3860
|
+
onResolve,
|
|
3861
|
+
labels,
|
|
3862
|
+
sourceLabels,
|
|
3863
|
+
emptyAction,
|
|
3864
|
+
className
|
|
3865
|
+
}) {
|
|
3866
|
+
const copy = { ...DEFAULT_MATCH_REVIEW_LABELS, ...labels };
|
|
3867
|
+
const reasonId = React.useId();
|
|
3868
|
+
const [reason, setReason] = React.useState("");
|
|
3869
|
+
const trimmed = reason.trim();
|
|
3870
|
+
if (!hit) {
|
|
3871
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3872
|
+
EmptyState,
|
|
3873
|
+
{
|
|
3874
|
+
className,
|
|
3875
|
+
title: copy.emptyTitle,
|
|
3876
|
+
description: copy.emptyDescription,
|
|
3877
|
+
action: emptyAction
|
|
3878
|
+
}
|
|
3879
|
+
);
|
|
3880
|
+
}
|
|
3881
|
+
const appearance = appearanceOf(hit);
|
|
3882
|
+
const sourceLabel = sourceLabels?.[hit.source] ?? hit.source;
|
|
3883
|
+
function resolve(status) {
|
|
3884
|
+
if (trimmed.length === 0) return;
|
|
3885
|
+
onResolve(status, trimmed);
|
|
3886
|
+
}
|
|
3887
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3888
|
+
"article",
|
|
3889
|
+
{
|
|
3890
|
+
"data-appearance": appearance,
|
|
3891
|
+
className: cn(
|
|
3892
|
+
"flex flex-col gap-3 rounded-xl border p-4 text-card-foreground motion-reduce:transition-none",
|
|
3893
|
+
appearanceClass[appearance],
|
|
3894
|
+
className
|
|
3895
|
+
),
|
|
3896
|
+
children: [
|
|
3897
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
3898
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm font-medium", children: [
|
|
3899
|
+
copy.source,
|
|
3900
|
+
": ",
|
|
3901
|
+
sourceLabel
|
|
3902
|
+
] }),
|
|
3903
|
+
appearance === "unverified" ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rounded-md bg-muted px-2 py-0.5 text-xs text-muted-foreground", children: copy.unverified }) : null
|
|
3904
|
+
] }),
|
|
3905
|
+
/* @__PURE__ */ jsxRuntime.jsxs("dl", { className: "grid grid-cols-[max-content_1fr] gap-x-3 gap-y-1 text-sm", children: [
|
|
3906
|
+
typeof hit.score === "number" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
3907
|
+
/* @__PURE__ */ jsxRuntime.jsx("dt", { className: "text-muted-foreground", children: copy.score }),
|
|
3908
|
+
/* @__PURE__ */ jsxRuntime.jsx("dd", { children: hit.score })
|
|
3909
|
+
] }) : null,
|
|
3910
|
+
/* @__PURE__ */ jsxRuntime.jsx("dt", { className: "text-muted-foreground", children: copy.matchedFields }),
|
|
3911
|
+
/* @__PURE__ */ jsxRuntime.jsx("dd", { children: hit.matchedFields.length > 0 ? hit.matchedFields.join(", ") : copy.noMatchedFields }),
|
|
3912
|
+
/* @__PURE__ */ jsxRuntime.jsx("dt", { className: "text-muted-foreground", children: copy.status }),
|
|
3913
|
+
/* @__PURE__ */ jsxRuntime.jsx("dd", { children: hit.status })
|
|
3914
|
+
] }),
|
|
3915
|
+
hit.sources && hit.sources.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "flex flex-col gap-1 text-sm", children: hit.sources.map(
|
|
3916
|
+
(source) => sourceUrlIsHttps(source.url) ? /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3917
|
+
"a",
|
|
3918
|
+
{
|
|
3919
|
+
className: "underline underline-offset-2",
|
|
3920
|
+
href: source.url,
|
|
3921
|
+
rel: "noreferrer",
|
|
3922
|
+
target: "_blank",
|
|
3923
|
+
children: source.title
|
|
3924
|
+
}
|
|
3925
|
+
) }, `${source.title}-${source.url}`) : /* @__PURE__ */ jsxRuntime.jsxs("li", { children: [
|
|
3926
|
+
source.title,
|
|
3927
|
+
" (",
|
|
3928
|
+
copy.unverified,
|
|
3929
|
+
")"
|
|
3930
|
+
] }, `${source.title}-${source.url}`)
|
|
3931
|
+
) }) : null,
|
|
3932
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
3933
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-sm font-medium", htmlFor: reasonId, children: copy.reason }),
|
|
3934
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3935
|
+
"textarea",
|
|
3936
|
+
{
|
|
3937
|
+
id: reasonId,
|
|
3938
|
+
className: "min-h-20 rounded-md border border-input bg-background px-3 py-2 text-sm",
|
|
3939
|
+
value: reason,
|
|
3940
|
+
placeholder: copy.reasonPlaceholder,
|
|
3941
|
+
onChange: (event) => setReason(event.target.value)
|
|
3942
|
+
}
|
|
3943
|
+
)
|
|
3944
|
+
] }),
|
|
3945
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap gap-2", children: [
|
|
3946
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3947
|
+
Button,
|
|
3948
|
+
{
|
|
3949
|
+
type: "button",
|
|
3950
|
+
disabled: trimmed.length === 0,
|
|
3951
|
+
onClick: () => resolve("confirmed"),
|
|
3952
|
+
children: copy.confirm
|
|
3953
|
+
}
|
|
3954
|
+
),
|
|
3955
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3956
|
+
Button,
|
|
3957
|
+
{
|
|
3958
|
+
type: "button",
|
|
3959
|
+
variant: "outline",
|
|
3960
|
+
disabled: trimmed.length === 0,
|
|
3961
|
+
onClick: () => resolve("dismissed"),
|
|
3962
|
+
children: copy.dismiss
|
|
3963
|
+
}
|
|
3964
|
+
)
|
|
3965
|
+
] })
|
|
3966
|
+
]
|
|
3967
|
+
}
|
|
3968
|
+
);
|
|
3969
|
+
}
|
|
3970
|
+
|
|
3971
|
+
exports.AuditTimeline = AuditTimeline;
|
|
3972
|
+
exports.AvatarEditor = AvatarEditor;
|
|
3973
|
+
exports.AvatarEditorDialog = AvatarEditorDialog;
|
|
3974
|
+
exports.COOKIE_CONSENT_MAX_AGE = COOKIE_CONSENT_MAX_AGE;
|
|
3975
|
+
exports.COOKIE_CONSENT_NAME = COOKIE_CONSENT_NAME;
|
|
3976
|
+
exports.COOKIE_CONSENT_VALUES = COOKIE_CONSENT_VALUES;
|
|
3977
|
+
exports.ConfirmDestructiveDialog = ConfirmDestructiveDialog;
|
|
3978
|
+
exports.CookieConsentBanner = CookieConsentBanner;
|
|
3979
|
+
exports.CostBreakdownCard = CostBreakdownCard;
|
|
3980
|
+
exports.DEFAULT_AUDIT_TIMELINE_LABELS = DEFAULT_AUDIT_TIMELINE_LABELS;
|
|
3981
|
+
exports.DEFAULT_COOKIE_CONSENT_BANNER_LABELS = DEFAULT_COOKIE_CONSENT_BANNER_LABELS;
|
|
3982
|
+
exports.DEFAULT_EDITABLE_SECTION_LABELS = DEFAULT_EDITABLE_SECTION_LABELS;
|
|
3983
|
+
exports.DEFAULT_ENTITY_GRAPH_LABELS = DEFAULT_ENTITY_GRAPH_LABELS;
|
|
3984
|
+
exports.DEFAULT_FILE_DROPZONE_LABELS = DEFAULT_FILE_DROPZONE_LABELS;
|
|
3985
|
+
exports.DEFAULT_FILE_UPLOAD_LABELS = DEFAULT_FILE_UPLOAD_LABELS;
|
|
3986
|
+
exports.DEFAULT_FILE_UPLOAD_TRAY_LABELS = DEFAULT_FILE_UPLOAD_TRAY_LABELS;
|
|
3987
|
+
exports.DEFAULT_LEGAL_ACCEPT_CHECKBOX_LABELS = DEFAULT_LEGAL_ACCEPT_CHECKBOX_LABELS;
|
|
3988
|
+
exports.DEFAULT_LEGAL_ACCEPT_DIALOG_LABELS = DEFAULT_LEGAL_ACCEPT_DIALOG_LABELS;
|
|
3989
|
+
exports.DEFAULT_MATCH_REVIEW_LABELS = DEFAULT_MATCH_REVIEW_LABELS;
|
|
3990
|
+
exports.DEFAULT_NOTIFICATION_BELL_LABELS = DEFAULT_NOTIFICATION_BELL_LABELS;
|
|
3991
|
+
exports.DEFAULT_RISK_BADGE_LABELS = DEFAULT_RISK_BADGE_LABELS;
|
|
3992
|
+
exports.DataList = DataList;
|
|
3993
|
+
exports.DataListItem = DataListItem;
|
|
3994
|
+
exports.DimensionBreakdownTable = DimensionBreakdownTable;
|
|
3995
|
+
exports.EditableSection = EditableSection;
|
|
3996
|
+
exports.Empty = Empty;
|
|
3997
|
+
exports.EmptyContent = EmptyContent;
|
|
3998
|
+
exports.EmptyDescription = EmptyDescription;
|
|
3999
|
+
exports.EmptyHeader = EmptyHeader;
|
|
4000
|
+
exports.EmptyMedia = EmptyMedia;
|
|
4001
|
+
exports.EmptyState = EmptyState;
|
|
4002
|
+
exports.EmptyTitle = EmptyTitle;
|
|
4003
|
+
exports.EntityGraph = EntityGraph;
|
|
4004
|
+
exports.ErrorState = ErrorState;
|
|
4005
|
+
exports.FieldMessage = FieldMessage;
|
|
4006
|
+
exports.FileDropzone = FileDropzone;
|
|
4007
|
+
exports.FileUploadMenu = FileUploadMenu;
|
|
4008
|
+
exports.FileUploadTray = FileUploadTray;
|
|
4009
|
+
exports.LanguageContext = LanguageContext;
|
|
4010
|
+
exports.LanguageSwitcher = LanguageSwitcher;
|
|
4011
|
+
exports.LegalAcceptCheckbox = LegalAcceptCheckbox;
|
|
4012
|
+
exports.LegalAcceptDialog = LegalAcceptDialog;
|
|
4013
|
+
exports.LegalMarkdown = LegalMarkdown;
|
|
4014
|
+
exports.LoadingState = LoadingState;
|
|
4015
|
+
exports.MatchReview = MatchReview;
|
|
4016
|
+
exports.MetricChartCard = MetricChartCard;
|
|
4017
|
+
exports.MetricEmptyState = MetricEmptyState;
|
|
4018
|
+
exports.MetricSkeleton = MetricSkeleton;
|
|
4019
|
+
exports.NotificationBell = NotificationBell;
|
|
4020
|
+
exports.RiskBadge = RiskBadge;
|
|
4021
|
+
exports.SafeImage = SafeImage;
|
|
4022
|
+
exports.SparklineStat = SparklineStat;
|
|
4023
|
+
exports.StatHighlightCard = StatHighlightCard;
|
|
4024
|
+
exports.THEME_INIT_SCRIPT = THEME_INIT_SCRIPT;
|
|
4025
|
+
exports.THEME_INIT_SCRIPT_ID = THEME_INIT_SCRIPT_ID;
|
|
4026
|
+
exports.THEME_STORAGE_KEY = THEME_STORAGE_KEY;
|
|
4027
|
+
exports.TIME_RANGE_OPTIONS = TIME_RANGE_OPTIONS;
|
|
4028
|
+
exports.ThemeSwitcher = ThemeSwitcher;
|
|
4029
|
+
exports.TimeRangePicker = TimeRangePicker;
|
|
4030
|
+
exports.avatarFallbackStyle = avatarFallbackStyle;
|
|
4031
|
+
exports.cn = cn;
|
|
4032
|
+
exports.cookieConsentAllowsReplay = cookieConsentAllowsReplay;
|
|
4033
|
+
exports.cookieDomainFromHostname = cookieDomainFromHostname;
|
|
4034
|
+
exports.createMultipartUploadController = createMultipartUploadController;
|
|
4035
|
+
exports.defaultLanguages = defaultLanguages;
|
|
4036
|
+
exports.fileMatchesAccept = fileMatchesAccept;
|
|
4037
|
+
exports.getAvatarColors = getAvatarColors;
|
|
4038
|
+
exports.getInitials = getInitials;
|
|
4039
|
+
exports.isCookieConsentValue = isCookieConsentValue;
|
|
4040
|
+
exports.isEmbeddedWindow = isEmbeddedWindow;
|
|
4041
|
+
exports.matchClaimIsUnverified = matchClaimIsUnverified;
|
|
4042
|
+
exports.parseCookieConsent = parseCookieConsent;
|
|
4043
|
+
exports.partRanges = partRanges;
|
|
4044
|
+
exports.progressFromBytes = progressFromBytes;
|
|
4045
|
+
exports.readCookieConsent = readCookieConsent;
|
|
4046
|
+
exports.serializeCookieConsent = serializeCookieConsent;
|
|
4047
|
+
exports.sourceUrlIsHttps = sourceUrlIsHttps;
|
|
4048
|
+
exports.useEditableSection = useEditableSection;
|
|
4049
|
+
exports.useLanguageContext = useLanguageContext;
|
|
4050
|
+
exports.useMediaQuery = useMediaQuery2;
|
|
4051
|
+
exports.writeCookieConsent = writeCookieConsent;
|
|
4052
|
+
//# sourceMappingURL=index.cjs.map
|
|
4053
|
+
//# sourceMappingURL=index.cjs.map
|