@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.
Files changed (103) hide show
  1. package/README.md +100 -2
  2. package/dist/avatar.cjs +88 -0
  3. package/dist/avatar.cjs.map +1 -0
  4. package/dist/avatar.d.cts +18 -0
  5. package/dist/avatar.d.ts +18 -0
  6. package/dist/avatar.js +84 -0
  7. package/dist/avatar.js.map +1 -0
  8. package/dist/consent.cjs +85 -0
  9. package/dist/consent.cjs.map +1 -0
  10. package/dist/consent.d.cts +20 -0
  11. package/dist/consent.d.ts +20 -0
  12. package/dist/consent.js +73 -0
  13. package/dist/consent.js.map +1 -0
  14. package/dist/index.cjs +4053 -0
  15. package/dist/index.cjs.map +1 -0
  16. package/dist/index.d.cts +859 -0
  17. package/dist/index.d.ts +859 -0
  18. package/dist/index.js +3947 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/theme-init.cjs +12 -0
  21. package/dist/theme-init.cjs.map +1 -0
  22. package/dist/theme-init.d.cts +11 -0
  23. package/dist/theme-init.d.ts +11 -0
  24. package/dist/theme-init.js +8 -0
  25. package/dist/theme-init.js.map +1 -0
  26. package/dist/ui/alert-dialog.d.ts +15 -0
  27. package/dist/ui/alert-dialog.js +137 -0
  28. package/dist/ui/alert-dialog.js.map +1 -0
  29. package/dist/ui/alert.d.ts +11 -0
  30. package/dist/ui/alert.js +57 -0
  31. package/dist/ui/alert.js.map +1 -0
  32. package/dist/ui/avatar.d.ts +13 -0
  33. package/dist/ui/avatar.js +105 -0
  34. package/dist/ui/avatar.js.map +1 -0
  35. package/dist/ui/badge.d.ts +13 -0
  36. package/dist/ui/badge.js +33 -0
  37. package/dist/ui/badge.js.map +1 -0
  38. package/dist/ui/breadcrumb.d.ts +12 -0
  39. package/dist/ui/breadcrumb.js +121 -0
  40. package/dist/ui/breadcrumb.js.map +1 -0
  41. package/dist/ui/bubble.d.ts +19 -0
  42. package/dist/ui/bubble.js +120 -0
  43. package/dist/ui/bubble.js.map +1 -0
  44. package/dist/ui/button.d.ts +12 -0
  45. package/dist/ui/button.js +59 -0
  46. package/dist/ui/button.js.map +1 -0
  47. package/dist/ui/card.d.ts +10 -0
  48. package/dist/ui/card.js +55 -0
  49. package/dist/ui/card.js.map +1 -0
  50. package/dist/ui/collapsible.d.ts +8 -0
  51. package/dist/ui/collapsible.js +17 -0
  52. package/dist/ui/collapsible.js.map +1 -0
  53. package/dist/ui/dialog.d.ts +16 -0
  54. package/dist/ui/dialog.js +125 -0
  55. package/dist/ui/dialog.js.map +1 -0
  56. package/dist/ui/dropdown-menu.d.ts +31 -0
  57. package/dist/ui/dropdown-menu.js +244 -0
  58. package/dist/ui/dropdown-menu.js.map +1 -0
  59. package/dist/ui/input-otp.d.ts +39 -0
  60. package/dist/ui/input-otp.js +53 -0
  61. package/dist/ui/input-otp.js.map +1 -0
  62. package/dist/ui/input.d.ts +5 -0
  63. package/dist/ui/input.js +28 -0
  64. package/dist/ui/input.js.map +1 -0
  65. package/dist/ui/label.d.ts +7 -0
  66. package/dist/ui/label.js +29 -0
  67. package/dist/ui/label.js.map +1 -0
  68. package/dist/ui/message.d.ts +12 -0
  69. package/dist/ui/message.js +93 -0
  70. package/dist/ui/message.js.map +1 -0
  71. package/dist/ui/progress.d.ts +9 -0
  72. package/dist/ui/progress.js +41 -0
  73. package/dist/ui/progress.js.map +1 -0
  74. package/dist/ui/separator.d.ts +6 -0
  75. package/dist/ui/separator.js +31 -0
  76. package/dist/ui/separator.js.map +1 -0
  77. package/dist/ui/sheet.d.ts +16 -0
  78. package/dist/ui/sheet.js +127 -0
  79. package/dist/ui/sheet.js.map +1 -0
  80. package/dist/ui/sidebar.d.ts +69 -0
  81. package/dist/ui/sidebar.js +624 -0
  82. package/dist/ui/sidebar.js.map +1 -0
  83. package/dist/ui/skeleton.d.ts +5 -0
  84. package/dist/ui/skeleton.js +26 -0
  85. package/dist/ui/skeleton.js.map +1 -0
  86. package/dist/ui/sonner.d.ts +6 -0
  87. package/dist/ui/sonner.js +25 -0
  88. package/dist/ui/sonner.js.map +1 -0
  89. package/dist/ui/switch.d.ts +8 -0
  90. package/dist/ui/switch.js +38 -0
  91. package/dist/ui/switch.js.map +1 -0
  92. package/dist/ui/table.d.ts +11 -0
  93. package/dist/ui/table.js +85 -0
  94. package/dist/ui/table.js.map +1 -0
  95. package/dist/ui/tooltip.d.ts +9 -0
  96. package/dist/ui/tooltip.js +67 -0
  97. package/dist/ui/tooltip.js.map +1 -0
  98. package/dist/ui/use-mobile.d.ts +3 -0
  99. package/dist/ui/use-mobile.js +24 -0
  100. package/dist/ui/use-mobile.js.map +1 -0
  101. package/package.json +281 -5
  102. package/scripts/check-theme.mjs +74 -0
  103. 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