@underverse-ui/underverse 0.1.38 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -5173,24 +5173,29 @@ var DateRangePicker = ({ startDate, endDate, onChange, placeholder = "Select dat
5173
5173
 
5174
5174
  // ../../components/ui/TimePicker.tsx
5175
5175
  import * as React22 from "react";
5176
- import { Clock as Clock2, X as X8 } from "lucide-react";
5177
- import { jsx as jsx29, jsxs as jsxs24 } from "react/jsx-runtime";
5176
+ import { Clock as Clock2, X as X8, Check as Check4 } from "lucide-react";
5177
+ import { Fragment as Fragment7, jsx as jsx29, jsxs as jsxs24 } from "react/jsx-runtime";
5178
5178
  var pad = (n) => n.toString().padStart(2, "0");
5179
5179
  function parseTime(input, fmt = "24", includeSeconds) {
5180
5180
  if (!input) return null;
5181
- const s = input.trim().toUpperCase();
5182
- const ampm = s.endsWith("AM") || s.endsWith("PM");
5183
- const clean = s.replace(/\s*(AM|PM)\s*$/, "");
5184
- const segs = clean.split(":");
5185
- const h = Number(segs[0]);
5186
- const m = Number(segs[1] ?? 0);
5187
- const sec = Number(segs[2] ?? 0);
5188
- if (Number.isNaN(h) || Number.isNaN(m) || Number.isNaN(sec)) return null;
5189
- if (fmt === "12" || ampm) {
5190
- const p = s.endsWith("PM") ? "PM" : "AM";
5191
- return { h: Math.max(1, Math.min(12, h)), m: Math.max(0, Math.min(59, m)), s: Math.max(0, Math.min(59, sec)), p };
5192
- }
5193
- return { h: Math.max(0, Math.min(23, h)), m: Math.max(0, Math.min(59, m)), s: Math.max(0, Math.min(59, sec)) };
5181
+ try {
5182
+ const s = input.trim().toUpperCase();
5183
+ const ampm = s.endsWith("AM") || s.endsWith("PM");
5184
+ const clean = s.replace(/\s*(AM|PM)\s*$/, "");
5185
+ const segs = clean.split(":");
5186
+ const h = Number(segs[0]);
5187
+ const m = Number(segs[1] ?? 0);
5188
+ const sec = Number(segs[2] ?? 0);
5189
+ if (Number.isNaN(h) || Number.isNaN(m) || Number.isNaN(sec)) return null;
5190
+ if (fmt === "12" || ampm) {
5191
+ const p = s.endsWith("PM") ? "PM" : "AM";
5192
+ return { h: Math.max(1, Math.min(12, h)), m: Math.max(0, Math.min(59, m)), s: Math.max(0, Math.min(59, sec)), p };
5193
+ }
5194
+ return { h: Math.max(0, Math.min(23, h)), m: Math.max(0, Math.min(59, m)), s: Math.max(0, Math.min(59, sec)) };
5195
+ } catch (error) {
5196
+ console.error("Error parsing time:", error);
5197
+ return null;
5198
+ }
5194
5199
  }
5195
5200
  function formatTime2({ h, m, s, p }, fmt, includeSeconds) {
5196
5201
  if (fmt === "12") {
@@ -5226,6 +5231,16 @@ function TimePicker({
5226
5231
  showNow = false,
5227
5232
  showPresets = false,
5228
5233
  allowManualInput = false,
5234
+ customPresets = [],
5235
+ minTime,
5236
+ maxTime,
5237
+ disabledTimes,
5238
+ error,
5239
+ success,
5240
+ helperText,
5241
+ animate = true,
5242
+ onOpen,
5243
+ onClose,
5229
5244
  className,
5230
5245
  ...rest
5231
5246
  }) {
@@ -5235,14 +5250,118 @@ function TimePicker({
5235
5250
  const [open, setOpen] = React22.useState(false);
5236
5251
  const [parts, setParts] = React22.useState(initial);
5237
5252
  const [manualInput, setManualInput] = React22.useState("");
5253
+ const [focusedColumn, setFocusedColumn] = React22.useState(null);
5254
+ const hourScrollRef = React22.useRef(null);
5255
+ const minuteScrollRef = React22.useRef(null);
5256
+ const secondScrollRef = React22.useRef(null);
5238
5257
  React22.useEffect(() => {
5239
5258
  if (isControlled) {
5240
5259
  const parsed = parseTime(value, format, includeSeconds);
5241
5260
  if (parsed) setParts(parsed);
5242
5261
  }
5243
5262
  }, [value, isControlled, format, includeSeconds]);
5263
+ React22.useEffect(() => {
5264
+ if (!open) return;
5265
+ const scrollToSelected = (ref, targetValue, step) => {
5266
+ if (!ref.current) return;
5267
+ const buttons = ref.current.querySelectorAll("button");
5268
+ const targetIndex = Math.floor(targetValue / step);
5269
+ const targetButton = buttons[targetIndex];
5270
+ if (targetButton) {
5271
+ targetButton.scrollIntoView({ behavior: animate ? "smooth" : "auto", block: "center" });
5272
+ }
5273
+ };
5274
+ setTimeout(() => {
5275
+ scrollToSelected(hourScrollRef, parts.h, 1);
5276
+ scrollToSelected(minuteScrollRef, parts.m, minuteStep);
5277
+ if (includeSeconds) scrollToSelected(secondScrollRef, parts.s, secondStep);
5278
+ }, 50);
5279
+ }, [open, parts.h, parts.m, parts.s, minuteStep, secondStep, includeSeconds, animate]);
5280
+ const isTimeDisabled = React22.useCallback((timeStr) => {
5281
+ if (!disabledTimes) return false;
5282
+ if (typeof disabledTimes === "function") return disabledTimes(timeStr);
5283
+ return disabledTimes.includes(timeStr);
5284
+ }, [disabledTimes]);
5285
+ const isTimeInRange = React22.useCallback((timeStr) => {
5286
+ if (!minTime && !maxTime) return true;
5287
+ const parsed = parseTime(timeStr, format, includeSeconds);
5288
+ if (!parsed) return true;
5289
+ if (minTime) {
5290
+ const min = parseTime(minTime, format, includeSeconds);
5291
+ if (min) {
5292
+ const currentMinutes = parsed.h * 60 + parsed.m;
5293
+ const minMinutes = min.h * 60 + min.m;
5294
+ if (currentMinutes < minMinutes) return false;
5295
+ }
5296
+ }
5297
+ if (maxTime) {
5298
+ const max = parseTime(maxTime, format, includeSeconds);
5299
+ if (max) {
5300
+ const currentMinutes = parsed.h * 60 + parsed.m;
5301
+ const maxMinutes = max.h * 60 + max.m;
5302
+ if (currentMinutes > maxMinutes) return false;
5303
+ }
5304
+ }
5305
+ return true;
5306
+ }, [minTime, maxTime, format, includeSeconds]);
5244
5307
  const emit = (next) => {
5245
- onChange?.(next ? formatTime2(next, format, includeSeconds) : void 0);
5308
+ const timeStr = next ? formatTime2(next, format, includeSeconds) : void 0;
5309
+ if (timeStr && !isTimeInRange(timeStr)) return;
5310
+ if (timeStr && isTimeDisabled(timeStr)) return;
5311
+ onChange?.(timeStr);
5312
+ };
5313
+ const handleOpenChange = (newOpen) => {
5314
+ setOpen(newOpen);
5315
+ if (newOpen) {
5316
+ onOpen?.();
5317
+ } else {
5318
+ onClose?.();
5319
+ setFocusedColumn(null);
5320
+ }
5321
+ };
5322
+ const handleKeyDown = (e, column) => {
5323
+ if (!["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Home", "End", "PageUp", "PageDown"].includes(e.key)) return;
5324
+ e.preventDefault();
5325
+ let newParts = { ...parts };
5326
+ switch (column) {
5327
+ case "hour":
5328
+ if (e.key === "ArrowUp") newParts.h = format === "24" ? (parts.h + 1) % 24 : parts.h % 12 + 1;
5329
+ if (e.key === "ArrowDown") newParts.h = format === "24" ? (parts.h - 1 + 24) % 24 : (parts.h - 2 + 12) % 12 + 1;
5330
+ if (e.key === "Home") newParts.h = format === "24" ? 0 : 1;
5331
+ if (e.key === "End") newParts.h = format === "24" ? 23 : 12;
5332
+ if (e.key === "PageUp") newParts.h = format === "24" ? (parts.h + 6) % 24 : parts.h % 12 + 3;
5333
+ if (e.key === "PageDown") newParts.h = format === "24" ? (parts.h - 6 + 24) % 24 : (parts.h - 4 + 12) % 12 + 1;
5334
+ if (e.key === "ArrowRight") setFocusedColumn("minute");
5335
+ break;
5336
+ case "minute":
5337
+ if (e.key === "ArrowUp") newParts.m = (parts.m + minuteStep) % 60;
5338
+ if (e.key === "ArrowDown") newParts.m = (parts.m - minuteStep + 60) % 60;
5339
+ if (e.key === "Home") newParts.m = 0;
5340
+ if (e.key === "End") newParts.m = 59 - 59 % minuteStep;
5341
+ if (e.key === "PageUp") newParts.m = (parts.m + minuteStep * 3) % 60;
5342
+ if (e.key === "PageDown") newParts.m = (parts.m - minuteStep * 3 + 60) % 60;
5343
+ if (e.key === "ArrowLeft") setFocusedColumn("hour");
5344
+ if (e.key === "ArrowRight") setFocusedColumn(includeSeconds ? "second" : format === "12" ? "period" : null);
5345
+ break;
5346
+ case "second":
5347
+ if (e.key === "ArrowUp") newParts.s = (parts.s + secondStep) % 60;
5348
+ if (e.key === "ArrowDown") newParts.s = (parts.s - secondStep + 60) % 60;
5349
+ if (e.key === "Home") newParts.s = 0;
5350
+ if (e.key === "End") newParts.s = 59 - 59 % secondStep;
5351
+ if (e.key === "PageUp") newParts.s = (parts.s + secondStep * 3) % 60;
5352
+ if (e.key === "PageDown") newParts.s = (parts.s - secondStep * 3 + 60) % 60;
5353
+ if (e.key === "ArrowLeft") setFocusedColumn("minute");
5354
+ if (e.key === "ArrowRight" && format === "12") setFocusedColumn("period");
5355
+ break;
5356
+ case "period":
5357
+ if (e.key === "ArrowUp" || e.key === "ArrowDown" || e.key === "Home" || e.key === "End") {
5358
+ newParts.p = newParts.p === "AM" ? "PM" : "AM";
5359
+ }
5360
+ if (e.key === "ArrowLeft") setFocusedColumn(includeSeconds ? "second" : "minute");
5361
+ break;
5362
+ }
5363
+ setParts(newParts);
5364
+ emit(newParts);
5246
5365
  };
5247
5366
  const setNow = () => {
5248
5367
  const now2 = /* @__PURE__ */ new Date();
@@ -5272,6 +5391,16 @@ function TimePicker({
5272
5391
  const handleManualInput = (input) => {
5273
5392
  setManualInput(input);
5274
5393
  const parsed = parseTime(input, format, includeSeconds);
5394
+ if (parsed) {
5395
+ const timeStr = formatTime2(parsed, format, includeSeconds);
5396
+ if (isTimeInRange(timeStr) && !isTimeDisabled(timeStr)) {
5397
+ setParts(parsed);
5398
+ emit(parsed);
5399
+ }
5400
+ }
5401
+ };
5402
+ const handleCustomPreset = (time) => {
5403
+ const parsed = parseTime(time, format, includeSeconds);
5275
5404
  if (parsed) {
5276
5405
  setParts(parsed);
5277
5406
  emit(parsed);
@@ -5293,23 +5422,30 @@ function TimePicker({
5293
5422
  {
5294
5423
  type: "button",
5295
5424
  disabled,
5425
+ "aria-label": "Select time",
5426
+ "aria-haspopup": "dialog",
5427
+ "aria-expanded": open,
5296
5428
  className: cn(
5297
- "flex w-full items-center justify-between border border-input bg-background",
5429
+ "flex w-full items-center justify-between border bg-background",
5298
5430
  sz.height,
5299
5431
  sz.padding,
5300
5432
  sz.text,
5301
5433
  radiusClass,
5302
5434
  "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
5303
5435
  "disabled:opacity-50 disabled:cursor-not-allowed",
5304
- "hover:bg-accent/5 transition-colors",
5436
+ "transition-all duration-200",
5437
+ error && "border-destructive focus-visible:ring-destructive",
5438
+ success && "border-green-500 focus-visible:ring-green-500",
5439
+ !error && !success && "border-input hover:bg-accent/5",
5440
+ animate && !disabled && "hover:shadow-md",
5305
5441
  className
5306
5442
  ),
5307
5443
  children: [
5308
5444
  /* @__PURE__ */ jsxs24("div", { className: "flex items-center gap-2", children: [
5309
- /* @__PURE__ */ jsx29(Clock2, { className: cn(sz.icon, "text-muted-foreground") }),
5445
+ /* @__PURE__ */ jsx29(Clock2, { className: cn(sz.icon, error ? "text-destructive" : success ? "text-green-500" : "text-muted-foreground") }),
5310
5446
  /* @__PURE__ */ jsx29("span", { className: cn("truncate", !value && !defaultValue && "text-muted-foreground"), children: value || defaultValue ? display : placeholder })
5311
5447
  ] }),
5312
- /* @__PURE__ */ jsx29("span", { className: cn("ml-2 transition-transform", open && "rotate-180"), children: /* @__PURE__ */ jsx29("svg", { className: sz.icon, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx29("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 9l-7 7-7-7" }) }) })
5448
+ /* @__PURE__ */ jsx29("span", { className: cn("ml-2 transition-transform duration-200", open && "rotate-180"), children: /* @__PURE__ */ jsx29("svg", { className: sz.icon, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx29("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 9l-7 7-7-7" }) }) })
5313
5449
  ]
5314
5450
  }
5315
5451
  );
@@ -5329,111 +5465,217 @@ function TimePicker({
5329
5465
  "button",
5330
5466
  {
5331
5467
  type: "button",
5332
- className: "px-2 py-1.5 text-xs rounded-md border border-border hover:bg-accent/10 capitalize transition-colors",
5468
+ className: cn(
5469
+ "px-2 py-1.5 text-xs rounded-md border border-border hover:bg-accent/10 capitalize transition-all",
5470
+ animate && "hover:scale-105 active:scale-95"
5471
+ ),
5333
5472
  onClick: () => setPreset(preset),
5473
+ "aria-label": `Set time to ${preset}`,
5334
5474
  children: preset
5335
5475
  },
5336
5476
  preset
5337
5477
  )) }),
5338
- /* @__PURE__ */ jsxs24("div", { className: "flex gap-2", children: [
5339
- /* @__PURE__ */ jsxs24("div", { className: "flex-1 min-w-[60px]", children: [
5340
- /* @__PURE__ */ jsx29("div", { className: "text-xs font-medium text-muted-foreground mb-1.5", children: "Hour" }),
5341
- /* @__PURE__ */ jsx29("div", { className: "max-h-48 overflow-y-auto pr-1 space-y-1 scrollbar-thin", children: hours.map((h) => /* @__PURE__ */ jsx29(
5342
- "button",
5478
+ customPresets && customPresets.length > 0 && /* @__PURE__ */ jsx29("div", { className: "grid grid-cols-2 gap-2", children: customPresets.map((preset, idx) => /* @__PURE__ */ jsx29(
5479
+ "button",
5480
+ {
5481
+ type: "button",
5482
+ className: cn(
5483
+ "px-2 py-1.5 text-xs rounded-md border border-border hover:bg-accent/10 transition-all",
5484
+ animate && "hover:scale-105 active:scale-95"
5485
+ ),
5486
+ onClick: () => handleCustomPreset(preset.time),
5487
+ "aria-label": `Set time to ${preset.label}`,
5488
+ children: preset.label
5489
+ },
5490
+ idx
5491
+ )) }),
5492
+ /* @__PURE__ */ jsxs24("div", { className: "flex gap-3", children: [
5493
+ /* @__PURE__ */ jsxs24("div", { className: "flex-1 min-w-[70px]", children: [
5494
+ /* @__PURE__ */ jsx29("div", { className: "text-xs font-semibold text-muted-foreground mb-2 text-center", children: "Hour" }),
5495
+ /* @__PURE__ */ jsx29(
5496
+ "div",
5343
5497
  {
5344
- type: "button",
5345
- className: cn(
5346
- "w-full text-center px-2 py-1.5 rounded-md hover:bg-accent transition-colors text-sm",
5347
- (format === "24" && parts.h === h || format === "12" && (parts.h % 12 || 12) === (h % 12 || 12)) && "bg-primary text-primary-foreground font-semibold"
5348
- ),
5349
- onClick: () => {
5350
- const nextH = format === "24" ? h : (parts.p === "PM" ? h % 12 + 12 : h % 12) % 24;
5351
- const next = { ...parts, h: format === "24" ? h : nextH === 0 && parts.p === "AM" ? 0 : nextH || (parts.p === "PM" ? 12 : 0) };
5352
- setParts(next);
5353
- emit(next);
5354
- },
5355
- children: pad(h)
5356
- },
5357
- h
5358
- )) })
5498
+ ref: hourScrollRef,
5499
+ className: "max-h-48 overflow-y-auto pr-1 space-y-1 scrollbar-thin scroll-smooth",
5500
+ role: "listbox",
5501
+ "aria-label": "Select hour",
5502
+ tabIndex: focusedColumn === "hour" ? 0 : -1,
5503
+ onKeyDown: (e) => handleKeyDown(e, "hour"),
5504
+ onFocus: () => setFocusedColumn("hour"),
5505
+ children: hours.map((h) => {
5506
+ const isSelected = format === "24" && parts.h === h || format === "12" && (parts.h % 12 || 12) === (h % 12 || 12);
5507
+ return /* @__PURE__ */ jsx29(
5508
+ "button",
5509
+ {
5510
+ type: "button",
5511
+ role: "option",
5512
+ "aria-selected": isSelected,
5513
+ className: cn(
5514
+ "w-full text-center px-3 py-2 rounded-md transition-all text-sm font-medium",
5515
+ "hover:bg-accent hover:scale-105 active:scale-95 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1",
5516
+ isSelected && "bg-primary text-primary-foreground shadow-md",
5517
+ !isSelected && "text-foreground/80",
5518
+ animate && "transition-transform duration-150"
5519
+ ),
5520
+ onClick: () => {
5521
+ const nextH = format === "24" ? h : (parts.p === "PM" ? h % 12 + 12 : h % 12) % 24;
5522
+ const next = { ...parts, h: format === "24" ? h : nextH === 0 && parts.p === "AM" ? 0 : nextH || (parts.p === "PM" ? 12 : 0) };
5523
+ setParts(next);
5524
+ emit(next);
5525
+ },
5526
+ children: pad(h)
5527
+ },
5528
+ h
5529
+ );
5530
+ })
5531
+ }
5532
+ )
5359
5533
  ] }),
5360
- /* @__PURE__ */ jsxs24("div", { className: "flex-1 min-w-[60px]", children: [
5361
- /* @__PURE__ */ jsx29("div", { className: "text-xs font-medium text-muted-foreground mb-1.5", children: "Min" }),
5362
- /* @__PURE__ */ jsx29("div", { className: "max-h-48 overflow-y-auto pr-1 space-y-1 scrollbar-thin", children: minutes.map((m) => /* @__PURE__ */ jsx29(
5363
- "button",
5534
+ /* @__PURE__ */ jsx29("div", { className: "w-px bg-border/50 self-stretch my-8" }),
5535
+ /* @__PURE__ */ jsxs24("div", { className: "flex-1 min-w-[70px]", children: [
5536
+ /* @__PURE__ */ jsx29("div", { className: "text-xs font-semibold text-muted-foreground mb-2 text-center", children: "Min" }),
5537
+ /* @__PURE__ */ jsx29(
5538
+ "div",
5364
5539
  {
5365
- type: "button",
5366
- className: cn(
5367
- "w-full text-center px-2 py-1.5 rounded-md hover:bg-accent transition-colors text-sm",
5368
- parts.m === m && "bg-primary text-primary-foreground font-semibold"
5369
- ),
5370
- onClick: () => {
5371
- const next = { ...parts, m };
5372
- setParts(next);
5373
- emit(next);
5374
- },
5375
- children: pad(m)
5376
- },
5377
- m
5378
- )) })
5540
+ ref: minuteScrollRef,
5541
+ className: "max-h-48 overflow-y-auto pr-1 space-y-1 scrollbar-thin scroll-smooth",
5542
+ role: "listbox",
5543
+ "aria-label": "Select minute",
5544
+ tabIndex: focusedColumn === "minute" ? 0 : -1,
5545
+ onKeyDown: (e) => handleKeyDown(e, "minute"),
5546
+ onFocus: () => setFocusedColumn("minute"),
5547
+ children: minutes.map((m) => {
5548
+ const isSelected = parts.m === m;
5549
+ return /* @__PURE__ */ jsx29(
5550
+ "button",
5551
+ {
5552
+ type: "button",
5553
+ role: "option",
5554
+ "aria-selected": isSelected,
5555
+ className: cn(
5556
+ "w-full text-center px-3 py-2 rounded-md transition-all text-sm font-medium",
5557
+ "hover:bg-accent hover:scale-105 active:scale-95 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1",
5558
+ isSelected && "bg-primary text-primary-foreground shadow-md",
5559
+ !isSelected && "text-foreground/80",
5560
+ animate && "transition-transform duration-150"
5561
+ ),
5562
+ onClick: () => {
5563
+ const next = { ...parts, m };
5564
+ setParts(next);
5565
+ emit(next);
5566
+ },
5567
+ children: pad(m)
5568
+ },
5569
+ m
5570
+ );
5571
+ })
5572
+ }
5573
+ )
5379
5574
  ] }),
5380
- includeSeconds && /* @__PURE__ */ jsxs24("div", { className: "flex-1 min-w-[60px]", children: [
5381
- /* @__PURE__ */ jsx29("div", { className: "text-xs font-medium text-muted-foreground mb-1.5", children: "Sec" }),
5382
- /* @__PURE__ */ jsx29("div", { className: "max-h-48 overflow-y-auto pr-1 space-y-1 scrollbar-thin", children: seconds.map((s) => /* @__PURE__ */ jsx29(
5383
- "button",
5384
- {
5385
- type: "button",
5386
- className: cn(
5387
- "w-full text-center px-2 py-1.5 rounded-md hover:bg-accent transition-colors text-sm",
5388
- parts.s === s && "bg-primary text-primary-foreground font-semibold"
5389
- ),
5390
- onClick: () => {
5391
- const next = { ...parts, s };
5392
- setParts(next);
5393
- emit(next);
5394
- },
5395
- children: pad(s)
5396
- },
5397
- s
5398
- )) })
5575
+ /* @__PURE__ */ jsx29("div", { className: "w-px bg-border/50 self-stretch my-8" }),
5576
+ includeSeconds && /* @__PURE__ */ jsxs24(Fragment7, { children: [
5577
+ /* @__PURE__ */ jsxs24("div", { className: "flex-1 min-w-[70px]", children: [
5578
+ /* @__PURE__ */ jsx29("div", { className: "text-xs font-semibold text-muted-foreground mb-2 text-center", children: "Sec" }),
5579
+ /* @__PURE__ */ jsx29(
5580
+ "div",
5581
+ {
5582
+ ref: secondScrollRef,
5583
+ className: "max-h-48 overflow-y-auto pr-1 space-y-1 scrollbar-thin scroll-smooth",
5584
+ role: "listbox",
5585
+ "aria-label": "Select second",
5586
+ tabIndex: focusedColumn === "second" ? 0 : -1,
5587
+ onKeyDown: (e) => handleKeyDown(e, "second"),
5588
+ onFocus: () => setFocusedColumn("second"),
5589
+ children: seconds.map((s) => {
5590
+ const isSelected = parts.s === s;
5591
+ return /* @__PURE__ */ jsx29(
5592
+ "button",
5593
+ {
5594
+ type: "button",
5595
+ role: "option",
5596
+ "aria-selected": isSelected,
5597
+ className: cn(
5598
+ "w-full text-center px-3 py-2 rounded-md transition-all text-sm font-medium",
5599
+ "hover:bg-accent hover:scale-105 active:scale-95 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1",
5600
+ isSelected && "bg-primary text-primary-foreground shadow-md",
5601
+ !isSelected && "text-foreground/80",
5602
+ animate && "transition-transform duration-150"
5603
+ ),
5604
+ onClick: () => {
5605
+ const next = { ...parts, s };
5606
+ setParts(next);
5607
+ emit(next);
5608
+ },
5609
+ children: pad(s)
5610
+ },
5611
+ s
5612
+ );
5613
+ })
5614
+ }
5615
+ )
5616
+ ] }),
5617
+ /* @__PURE__ */ jsx29("div", { className: "w-px bg-border/50 self-stretch my-8" })
5399
5618
  ] }),
5400
5619
  format === "12" && /* @__PURE__ */ jsxs24("div", { className: "w-20", children: [
5401
- /* @__PURE__ */ jsx29("div", { className: "text-xs font-medium text-muted-foreground mb-1.5", children: "Period" }),
5402
- /* @__PURE__ */ jsx29("div", { className: "flex flex-col gap-1.5", children: ["AM", "PM"].map((p) => /* @__PURE__ */ jsx29(
5403
- "button",
5620
+ /* @__PURE__ */ jsx29("div", { className: "text-xs font-semibold text-muted-foreground mb-2 text-center", children: "Period" }),
5621
+ /* @__PURE__ */ jsx29(
5622
+ "div",
5404
5623
  {
5405
- type: "button",
5406
- className: cn(
5407
- "px-3 py-2 rounded-md hover:bg-accent transition-colors text-sm font-medium",
5408
- parts.p === p && "bg-primary text-primary-foreground"
5409
- ),
5410
- onClick: () => {
5411
- const pVal = p;
5412
- let hour = parts.h;
5413
- if (pVal === "AM" && hour >= 12) hour -= 12;
5414
- if (pVal === "PM" && hour < 12) hour += 12;
5415
- const next = { ...parts, p: pVal, h: hour };
5416
- setParts(next);
5417
- emit(next);
5418
- },
5419
- children: p
5420
- },
5421
- p
5422
- )) })
5624
+ className: "flex flex-col gap-2",
5625
+ role: "radiogroup",
5626
+ "aria-label": "Select AM or PM",
5627
+ tabIndex: focusedColumn === "period" ? 0 : -1,
5628
+ onKeyDown: (e) => handleKeyDown(e, "period"),
5629
+ onFocus: () => setFocusedColumn("period"),
5630
+ children: ["AM", "PM"].map((p) => {
5631
+ const isSelected = parts.p === p;
5632
+ return /* @__PURE__ */ jsx29(
5633
+ "button",
5634
+ {
5635
+ type: "button",
5636
+ role: "radio",
5637
+ "aria-checked": isSelected,
5638
+ className: cn(
5639
+ "px-4 py-3 rounded-md transition-all text-sm font-semibold",
5640
+ "hover:bg-accent hover:scale-105 active:scale-95 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1",
5641
+ isSelected && "bg-primary text-primary-foreground shadow-md",
5642
+ !isSelected && "text-foreground/80 border border-border",
5643
+ animate && "transition-transform duration-150"
5644
+ ),
5645
+ onClick: () => {
5646
+ const pVal = p;
5647
+ let hour = parts.h;
5648
+ if (pVal === "AM" && hour >= 12) hour -= 12;
5649
+ if (pVal === "PM" && hour < 12) hour += 12;
5650
+ const next = { ...parts, p: pVal, h: hour };
5651
+ setParts(next);
5652
+ emit(next);
5653
+ },
5654
+ children: p
5655
+ },
5656
+ p
5657
+ );
5658
+ })
5659
+ }
5660
+ )
5423
5661
  ] })
5424
5662
  ] }),
5425
- (showNow || clearable) && /* @__PURE__ */ jsxs24("div", { className: "flex items-center justify-between gap-2 pt-2 border-t border-border", children: [
5663
+ (showNow || clearable) && /* @__PURE__ */ jsxs24("div", { className: "flex items-center justify-between gap-2 pt-3 border-t border-border", children: [
5426
5664
  showNow && /* @__PURE__ */ jsxs24(
5427
5665
  "button",
5428
5666
  {
5429
5667
  type: "button",
5430
- className: "px-3 py-1.5 text-xs rounded-md border border-border hover:bg-accent/10 transition-colors flex items-center gap-1.5",
5668
+ className: cn(
5669
+ "px-3 py-2 text-xs rounded-md border border-border hover:bg-accent/10 transition-all flex items-center gap-2 font-medium",
5670
+ animate && "hover:scale-105 active:scale-95"
5671
+ ),
5431
5672
  onClick: () => {
5432
5673
  setNow();
5433
- if (variant === "compact") setOpen(false);
5674
+ if (variant === "compact") handleOpenChange(false);
5434
5675
  },
5676
+ "aria-label": "Set current time",
5435
5677
  children: [
5436
- /* @__PURE__ */ jsx29(Clock2, { className: "w-3 h-3" }),
5678
+ /* @__PURE__ */ jsx29(Clock2, { className: "w-3.5 h-3.5" }),
5437
5679
  "Now"
5438
5680
  ]
5439
5681
  }
@@ -5443,14 +5685,18 @@ function TimePicker({
5443
5685
  "button",
5444
5686
  {
5445
5687
  type: "button",
5446
- className: "px-3 py-1.5 text-xs rounded-md border border-border hover:bg-destructive/10 transition-colors flex items-center gap-1.5",
5688
+ className: cn(
5689
+ "px-3 py-2 text-xs rounded-md border border-border hover:bg-destructive/10 hover:text-destructive transition-all flex items-center gap-2 font-medium",
5690
+ animate && "hover:scale-105 active:scale-95"
5691
+ ),
5447
5692
  onClick: () => {
5448
5693
  setParts(initial);
5449
5694
  emit(void 0);
5450
- setOpen(false);
5695
+ handleOpenChange(false);
5451
5696
  },
5697
+ "aria-label": "Clear selected time",
5452
5698
  children: [
5453
- /* @__PURE__ */ jsx29(X8, { className: "w-3 h-3" }),
5699
+ /* @__PURE__ */ jsx29(X8, { className: "w-3.5 h-3.5" }),
5454
5700
  "Clear"
5455
5701
  ]
5456
5702
  }
@@ -5467,30 +5713,54 @@ function TimePicker({
5467
5713
  ] });
5468
5714
  }
5469
5715
  return /* @__PURE__ */ jsxs24("div", { className: "w-full", ...rest, children: [
5470
- label && /* @__PURE__ */ jsx29("div", { className: "flex items-center justify-between mb-1.5", children: /* @__PURE__ */ jsxs24("label", { className: cn(sz.label, "font-medium", disabled ? "text-muted-foreground" : "text-foreground"), onClick: () => !disabled && setOpen(true), children: [
5471
- label,
5472
- required && /* @__PURE__ */ jsx29("span", { className: "text-destructive ml-1", children: "*" })
5473
- ] }) }),
5716
+ label && /* @__PURE__ */ jsx29("div", { className: "flex items-center justify-between mb-1.5", children: /* @__PURE__ */ jsxs24(
5717
+ "label",
5718
+ {
5719
+ className: cn(sz.label, "font-medium", disabled ? "text-muted-foreground" : "text-foreground", "cursor-pointer"),
5720
+ onClick: () => !disabled && handleOpenChange(true),
5721
+ children: [
5722
+ label,
5723
+ required && /* @__PURE__ */ jsx29("span", { className: "text-destructive ml-1", children: "*" })
5724
+ ]
5725
+ }
5726
+ ) }),
5474
5727
  /* @__PURE__ */ jsx29(
5475
5728
  Popover,
5476
5729
  {
5477
5730
  trigger,
5478
5731
  open,
5479
- onOpenChange: (o) => setOpen(o),
5732
+ onOpenChange: handleOpenChange,
5480
5733
  placement: "bottom-start",
5481
5734
  matchTriggerWidth: variant === "compact",
5482
5735
  contentWidth,
5483
- contentClassName: cn("p-3 rounded-lg border border-border bg-popover shadow-lg backdrop-blur-sm bg-popover/95"),
5736
+ contentClassName: cn(
5737
+ "p-4 rounded-lg border bg-popover shadow-xl backdrop-blur-md",
5738
+ error && "border-destructive",
5739
+ success && "border-green-500",
5740
+ !error && !success && "border-border",
5741
+ animate && "animate-in fade-in-0 zoom-in-95 duration-200"
5742
+ ),
5484
5743
  children: timePickerContent
5485
5744
  }
5486
- )
5745
+ ),
5746
+ (error || success || helperText) && /* @__PURE__ */ jsxs24("div", { className: cn("mt-1.5 flex items-start gap-1.5", sz.label), children: [
5747
+ error && /* @__PURE__ */ jsxs24("div", { className: "flex items-center gap-1.5 text-destructive", children: [
5748
+ /* @__PURE__ */ jsx29(X8, { className: "w-3.5 h-3.5 flex-shrink-0" }),
5749
+ /* @__PURE__ */ jsx29("span", { children: error })
5750
+ ] }),
5751
+ success && !error && /* @__PURE__ */ jsxs24("div", { className: "flex items-center gap-1.5 text-green-600", children: [
5752
+ /* @__PURE__ */ jsx29(Check4, { className: "w-3.5 h-3.5 flex-shrink-0" }),
5753
+ /* @__PURE__ */ jsx29("span", { children: "Valid time selected" })
5754
+ ] }),
5755
+ helperText && !error && !success && /* @__PURE__ */ jsx29("span", { className: "text-muted-foreground", children: helperText })
5756
+ ] })
5487
5757
  ] });
5488
5758
  }
5489
5759
 
5490
5760
  // ../../components/ui/Calendar.tsx
5491
5761
  import * as React23 from "react";
5492
5762
  import { ChevronLeft as ChevronLeft3, ChevronRight as ChevronRight4 } from "lucide-react";
5493
- import { Fragment as Fragment8, jsx as jsx30, jsxs as jsxs25 } from "react/jsx-runtime";
5763
+ import { Fragment as Fragment9, jsx as jsx30, jsxs as jsxs25 } from "react/jsx-runtime";
5494
5764
  function startOfMonth(d) {
5495
5765
  return new Date(d.getFullYear(), d.getMonth(), 1);
5496
5766
  }
@@ -5734,7 +6004,7 @@ function Calendar2({
5734
6004
  /* @__PURE__ */ jsx30("div", { className: "text-sm font-semibold", children: display === "week" ? `${labels?.month ? labels.month(weekDays[0]) : weekDays[0].toLocaleDateString("en-US", { month: "short" })} ${weekDays[0].getDate()} \u2013 ${labels?.month ? labels.month(weekDays[6]) : weekDays[6].toLocaleDateString("en-US", { month: "short" })} ${weekDays[6].getDate()}` : labels?.month ? labels.month(view) : view.toLocaleDateString("en-US", { month: "long", year: "numeric" }) }),
5735
6005
  /* @__PURE__ */ jsx30("button", { onClick: () => goByView(1), disabled: nextDisabled, className: cn("p-1 rounded-md hover:bg-accent", nextDisabled && "opacity-40 cursor-not-allowed hover:bg-transparent"), "aria-label": labels?.next || (display === "week" ? "Next week" : "Next month"), children: /* @__PURE__ */ jsx30(ChevronRight4, { className: "h-4 w-4" }) })
5736
6006
  ] }),
5737
- display === "week" ? /* @__PURE__ */ jsxs25(Fragment8, { children: [
6007
+ display === "week" ? /* @__PURE__ */ jsxs25(Fragment9, { children: [
5738
6008
  showWeekdays && /* @__PURE__ */ jsx30("div", { className: cn("grid grid-cols-7", sz.grid, "mb-1 text-center text-muted-foreground font-medium"), children: weekdays.map((w) => /* @__PURE__ */ jsx30("div", { className: cn(sz.head), children: w }, `w-${w}`)) }),
5739
6009
  /* @__PURE__ */ jsx30("div", { className: cn("grid grid-cols-7", sz.grid), children: weekDays.map((d, idx) => {
5740
6010
  const inMonth = true;
@@ -6547,7 +6817,7 @@ Slider.displayName = "Slider";
6547
6817
  // ../../components/ui/OverlayControls.tsx
6548
6818
  import { Dot, Maximize2, Pause, Play, RotateCcw, RotateCw, Volume2, VolumeX } from "lucide-react";
6549
6819
  import React27 from "react";
6550
- import { Fragment as Fragment9, jsx as jsx34, jsxs as jsxs29 } from "react/jsx-runtime";
6820
+ import { Fragment as Fragment10, jsx as jsx34, jsxs as jsxs29 } from "react/jsx-runtime";
6551
6821
  function OverlayControls({
6552
6822
  mode,
6553
6823
  value,
@@ -6770,7 +7040,7 @@ function OverlayControls({
6770
7040
  const handleSliderMouseLeave = () => {
6771
7041
  setPreviewData(null);
6772
7042
  };
6773
- return /* @__PURE__ */ jsxs29(Fragment9, { children: [
7043
+ return /* @__PURE__ */ jsxs29(Fragment10, { children: [
6774
7044
  keyboardFeedback && /* @__PURE__ */ jsx34(
6775
7045
  "div",
6776
7046
  {
@@ -6980,7 +7250,7 @@ function OverlayControls({
6980
7250
  // ../../components/ui/CategoryTreeSelect.tsx
6981
7251
  import { useState as useState24 } from "react";
6982
7252
  import { ChevronRight as ChevronRight5, ChevronDown as ChevronDown3, Check as Check6 } from "lucide-react";
6983
- import { Fragment as Fragment10, jsx as jsx35, jsxs as jsxs30 } from "react/jsx-runtime";
7253
+ import { Fragment as Fragment11, jsx as jsx35, jsxs as jsxs30 } from "react/jsx-runtime";
6984
7254
  function CategoryTreeSelect({ categories, value, onChange, placeholder = "Ch\u1ECDn danh m\u1EE5c", disabled }) {
6985
7255
  const [isOpen, setIsOpen] = useState24(false);
6986
7256
  const [expandedNodes, setExpandedNodes] = useState24(/* @__PURE__ */ new Set());
@@ -7103,7 +7373,7 @@ function CategoryTreeSelect({ categories, value, onChange, placeholder = "Ch\u1E
7103
7373
  ]
7104
7374
  }
7105
7375
  ),
7106
- isOpen && !disabled && /* @__PURE__ */ jsxs30(Fragment10, { children: [
7376
+ isOpen && !disabled && /* @__PURE__ */ jsxs30(Fragment11, { children: [
7107
7377
  /* @__PURE__ */ jsx35("div", { className: "fixed inset-0 z-10", onClick: () => setIsOpen(false) }),
7108
7378
  /* @__PURE__ */ jsx35(
7109
7379
  "div",
@@ -7121,7 +7391,7 @@ function CategoryTreeSelect({ categories, value, onChange, placeholder = "Ch\u1E
7121
7391
  }
7122
7392
 
7123
7393
  // ../../components/ui/ImageUpload.tsx
7124
- import { useState as useState25, useRef as useRef9, useCallback as useCallback8 } from "react";
7394
+ import { useState as useState25, useRef as useRef10, useCallback as useCallback9 } from "react";
7125
7395
  import { Upload, X as X9, Image as ImageIcon, Loader2 as Loader22, Check as Check7 } from "lucide-react";
7126
7396
  import { useTranslations as useTranslations6 } from "next-intl";
7127
7397
  import { jsx as jsx36, jsxs as jsxs31 } from "react/jsx-runtime";
@@ -7143,7 +7413,7 @@ function ImageUpload({
7143
7413
  const [isDragging, setIsDragging] = useState25(false);
7144
7414
  const [uploading, setUploading] = useState25(false);
7145
7415
  const [uploadedImages, setUploadedImages] = useState25([]);
7146
- const fileInputRef = useRef9(null);
7416
+ const fileInputRef = useRef10(null);
7147
7417
  const { addToast } = useToast();
7148
7418
  const t = useTranslations6("OCR.imageUpload");
7149
7419
  const previewSizes = {
@@ -7151,17 +7421,17 @@ function ImageUpload({
7151
7421
  md: "w-24 h-24",
7152
7422
  lg: "w-32 h-32"
7153
7423
  };
7154
- const handleDragOver = useCallback8((e) => {
7424
+ const handleDragOver = useCallback9((e) => {
7155
7425
  e.preventDefault();
7156
7426
  if (!disabled) {
7157
7427
  setIsDragging(true);
7158
7428
  }
7159
7429
  }, [disabled]);
7160
- const handleDragLeave = useCallback8((e) => {
7430
+ const handleDragLeave = useCallback9((e) => {
7161
7431
  e.preventDefault();
7162
7432
  setIsDragging(false);
7163
7433
  }, []);
7164
- const handleFiles = useCallback8(async (files) => {
7434
+ const handleFiles = useCallback9(async (files) => {
7165
7435
  if (files.length === 0) return;
7166
7436
  const validFiles = files.filter((file) => {
7167
7437
  if (file.size > maxSize * 1024 * 1024) {
@@ -7225,14 +7495,14 @@ function ImageUpload({
7225
7495
  setUploading(false);
7226
7496
  }
7227
7497
  }, [maxSize, addToast, onUpload]);
7228
- const handleDrop = useCallback8((e) => {
7498
+ const handleDrop = useCallback9((e) => {
7229
7499
  e.preventDefault();
7230
7500
  setIsDragging(false);
7231
7501
  if (disabled) return;
7232
7502
  const files = Array.from(e.dataTransfer.files);
7233
7503
  handleFiles(files);
7234
7504
  }, [disabled, handleFiles]);
7235
- const handleFileSelect = useCallback8((e) => {
7505
+ const handleFileSelect = useCallback9((e) => {
7236
7506
  const files = Array.from(e.target.files || []);
7237
7507
  handleFiles(files);
7238
7508
  if (fileInputRef.current) {
@@ -7351,7 +7621,7 @@ function ImageUpload({
7351
7621
  // ../../components/ui/Carousel.tsx
7352
7622
  import * as React29 from "react";
7353
7623
  import { ChevronLeft as ChevronLeft4, ChevronRight as ChevronRight6 } from "lucide-react";
7354
- import { Fragment as Fragment11, jsx as jsx37, jsxs as jsxs32 } from "react/jsx-runtime";
7624
+ import { Fragment as Fragment12, jsx as jsx37, jsxs as jsxs32 } from "react/jsx-runtime";
7355
7625
  function Carousel({
7356
7626
  children,
7357
7627
  autoScroll = true,
@@ -7560,7 +7830,7 @@ function Carousel({
7560
7830
  ))
7561
7831
  }
7562
7832
  ),
7563
- showArrows && totalSlides > slidesToShow && /* @__PURE__ */ jsxs32(Fragment11, { children: [
7833
+ showArrows && totalSlides > slidesToShow && /* @__PURE__ */ jsxs32(Fragment12, { children: [
7564
7834
  /* @__PURE__ */ jsx37(
7565
7835
  Button_default,
7566
7836
  {
@@ -7940,7 +8210,7 @@ function FallingIcons({
7940
8210
  // ../../components/ui/List.tsx
7941
8211
  import * as React31 from "react";
7942
8212
  import { ChevronRight as ChevronRight7 } from "lucide-react";
7943
- import { Fragment as Fragment12, jsx as jsx39, jsxs as jsxs34 } from "react/jsx-runtime";
8213
+ import { Fragment as Fragment13, jsx as jsx39, jsxs as jsxs34 } from "react/jsx-runtime";
7944
8214
  var SIZE_STYLES2 = {
7945
8215
  xs: { itemPad: "px-2 py-1.5", densePad: "px-2 py-1", label: "text-xs", desc: "text-[11px]", icon: "h-3.5 w-3.5", avatar: "h-6 w-6" },
7946
8216
  sm: { itemPad: "px-3 py-2", densePad: "px-3 py-1.5", label: "text-[13px]", desc: "text-[12px]", icon: "h-4 w-4", avatar: "h-8 w-8" },
@@ -8070,41 +8340,50 @@ var ListItem = React31.forwardRef(
8070
8340
  setInternalExpanded(newExpanded);
8071
8341
  }
8072
8342
  };
8073
- const inner = /* @__PURE__ */ jsxs34(Fragment12, { children: [
8074
- /* @__PURE__ */ jsxs34("div", { className: cn("flex items-center gap-3", padding, "group/item relative"), children: [
8075
- avatar && /* @__PURE__ */ jsx39("div", { className: cn("shrink-0", sz.avatar), children: typeof avatar === "string" ? /* @__PURE__ */ jsx39("img", { src: avatar, alt: "", className: cn("rounded-full object-cover", sz.avatar) }) : avatar }),
8076
- Left && !avatar && /* @__PURE__ */ jsx39("span", { className: cn("text-muted-foreground shrink-0", sz.icon), children: /* @__PURE__ */ jsx39(Left, { className: cn(sz.icon) }) }),
8077
- /* @__PURE__ */ jsxs34("div", { className: "min-w-0 flex-1", children: [
8078
- /* @__PURE__ */ jsxs34("div", { className: "flex items-center gap-2", children: [
8079
- label && /* @__PURE__ */ jsx39("div", { className: cn(sz.label, "text-foreground font-medium truncate"), children: label }),
8080
- badge && /* @__PURE__ */ jsx39("span", { className: cn("px-2 py-0.5 rounded-full text-[11px] font-medium shrink-0", BADGE_VARIANTS[badgeVariant]), children: badge })
8081
- ] }),
8082
- description && /* @__PURE__ */ jsx39("div", { className: cn(sz.desc, "text-muted-foreground truncate mt-0.5"), children: description }),
8083
- children && /* @__PURE__ */ jsx39("div", { className: "mt-1", children })
8084
- ] }),
8085
- action && /* @__PURE__ */ jsx39("div", { className: "opacity-0 group-hover/item:opacity-100 transition-opacity shrink-0", children: action }),
8086
- collapsible ? /* @__PURE__ */ jsx39(
8087
- "span",
8088
- {
8089
- role: "button",
8090
- "aria-label": "Toggle",
8091
- tabIndex: 0,
8092
- onClick: (e) => {
8093
- e.stopPropagation();
8094
- toggleExpanded();
8095
- },
8096
- onKeyDown: (e) => {
8097
- if (e.key === "Enter" || e.key === " ") {
8098
- e.preventDefault();
8099
- e.stopPropagation();
8100
- toggleExpanded();
8343
+ const headerProps = collapsible ? {
8344
+ role: "button",
8345
+ tabIndex: disabled ? -1 : 0,
8346
+ onClick: disabled ? void 0 : () => toggleExpanded(),
8347
+ onKeyDown: (e) => {
8348
+ if (disabled) return;
8349
+ if (e.key === "Enter" || e.key === " ") {
8350
+ e.preventDefault();
8351
+ toggleExpanded();
8352
+ }
8353
+ }
8354
+ } : {};
8355
+ const inner = /* @__PURE__ */ jsxs34(Fragment13, { children: [
8356
+ /* @__PURE__ */ jsxs34(
8357
+ "div",
8358
+ {
8359
+ className: cn("flex items-center gap-3", padding, "group/item relative"),
8360
+ ...headerProps,
8361
+ children: [
8362
+ avatar && /* @__PURE__ */ jsx39("div", { className: cn("shrink-0", sz.avatar), children: typeof avatar === "string" ? /* @__PURE__ */ jsx39("img", { src: avatar, alt: "", className: cn("rounded-full object-cover", sz.avatar) }) : avatar }),
8363
+ Left && !avatar && /* @__PURE__ */ jsx39("span", { className: cn("text-muted-foreground shrink-0", sz.icon), children: /* @__PURE__ */ jsx39(Left, { className: cn(sz.icon) }) }),
8364
+ /* @__PURE__ */ jsxs34("div", { className: "min-w-0 flex-1", children: [
8365
+ /* @__PURE__ */ jsxs34("div", { className: "flex items-center gap-2", children: [
8366
+ label && /* @__PURE__ */ jsx39("div", { className: cn(sz.label, "text-foreground font-medium truncate"), children: label }),
8367
+ badge && /* @__PURE__ */ jsx39("span", { className: cn("px-2 py-0.5 rounded-full text-[11px] font-medium shrink-0", BADGE_VARIANTS[badgeVariant]), children: badge })
8368
+ ] }),
8369
+ description && /* @__PURE__ */ jsx39("div", { className: cn(sz.desc, "text-muted-foreground truncate mt-0.5"), children: description }),
8370
+ children && /* @__PURE__ */ jsx39("div", { className: "mt-1", children })
8371
+ ] }),
8372
+ action && /* @__PURE__ */ jsx39("div", { className: "opacity-0 group-hover/item:opacity-100 transition-opacity shrink-0", children: action }),
8373
+ collapsible ? /* @__PURE__ */ jsx39(
8374
+ "span",
8375
+ {
8376
+ className: cn(
8377
+ "text-muted-foreground shrink-0 transition-transform cursor-pointer select-none",
8378
+ sz.icon,
8379
+ isExpanded && "rotate-90"
8380
+ ),
8381
+ children: /* @__PURE__ */ jsx39(ChevronRight7, { className: cn(sz.icon) })
8101
8382
  }
8102
- },
8103
- className: cn("text-muted-foreground shrink-0 transition-transform cursor-pointer select-none", sz.icon, isExpanded && "rotate-90"),
8104
- children: /* @__PURE__ */ jsx39(ChevronRight7, { className: cn(sz.icon) })
8105
- }
8106
- ) : Right && /* @__PURE__ */ jsx39("span", { className: cn("text-muted-foreground shrink-0", sz.icon), children: /* @__PURE__ */ jsx39(Right, { className: cn(sz.icon) }) })
8107
- ] }),
8383
+ ) : Right && /* @__PURE__ */ jsx39("span", { className: cn("text-muted-foreground shrink-0", sz.icon), children: /* @__PURE__ */ jsx39(Right, { className: cn(sz.icon) }) })
8384
+ ]
8385
+ }
8386
+ ),
8108
8387
  collapsible && isExpanded && expandContent && /* @__PURE__ */ jsx39("div", { className: cn("border-t border-border/50 bg-muted/20", padding, "pt-3"), children: expandContent })
8109
8388
  ] });
8110
8389
  const baseCls = cn(
@@ -8117,14 +8396,24 @@ var ListItem = React31.forwardRef(
8117
8396
  const A = as === "a" ? "a" : "a";
8118
8397
  return /* @__PURE__ */ jsx39(A, { ref, href, className: cn(baseCls, "block"), ...rest, children: inner });
8119
8398
  }
8120
- if (as === "button" || collapsible) {
8399
+ if (as === "button" && !collapsible) {
8121
8400
  return /* @__PURE__ */ jsx39(
8122
8401
  "button",
8123
8402
  {
8124
8403
  ref,
8125
8404
  type: "button",
8126
8405
  className: cn(baseCls, "text-left block w-full"),
8127
- onClick: collapsible ? toggleExpanded : void 0,
8406
+ ...rest,
8407
+ children: inner
8408
+ }
8409
+ );
8410
+ }
8411
+ if (collapsible) {
8412
+ return /* @__PURE__ */ jsx39(
8413
+ "div",
8414
+ {
8415
+ ref,
8416
+ className: cn(baseCls, "text-left block w-full"),
8128
8417
  ...rest,
8129
8418
  children: inner
8130
8419
  }
@@ -8141,7 +8430,7 @@ var List_default = List;
8141
8430
  // ../../components/ui/Watermark.tsx
8142
8431
  import * as React32 from "react";
8143
8432
  import { createPortal as createPortal9 } from "react-dom";
8144
- import { Fragment as Fragment13, jsx as jsx40, jsxs as jsxs35 } from "react/jsx-runtime";
8433
+ import { Fragment as Fragment14, jsx as jsx40, jsxs as jsxs35 } from "react/jsx-runtime";
8145
8434
  var PRESETS2 = {
8146
8435
  confidential: { text: "CONFIDENTIAL", color: "rgba(220, 38, 38, 0.15)", rotate: -22, fontSize: 16, fontWeight: "bold" },
8147
8436
  draft: { text: "DRAFT", color: "rgba(59, 130, 246, 0.15)", rotate: -22, fontSize: 18, fontWeight: "bold" },
@@ -8178,16 +8467,27 @@ function useWatermarkDataURL(opts) {
8178
8467
  const drawText = () => {
8179
8468
  ctx.clearRect(0, 0, tileW, tileH);
8180
8469
  ctx.save();
8181
- ctx.translate(tileW / 2, tileH / 2);
8470
+ if (pattern === "grid") {
8471
+ ctx.translate(width / 2, height / 2);
8472
+ } else {
8473
+ ctx.translate(tileW / 2, tileH / 2);
8474
+ }
8182
8475
  if (pattern === "diagonal") {
8183
8476
  ctx.rotate(rotate * Math.PI / 180);
8184
- } else if (pattern === "straight") {
8477
+ } else if (pattern === "straight" || pattern === "grid") {
8185
8478
  }
8186
8479
  if (text) {
8187
8480
  const textLines = Array.isArray(text) ? text : [text];
8188
8481
  ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily}`;
8189
8482
  ctx.textAlign = "center";
8190
8483
  ctx.textBaseline = "middle";
8484
+ if (opts.textShadow) {
8485
+ const shadowCol = opts.shadowColor || "rgba(255, 255, 255, 0.5)";
8486
+ ctx.shadowColor = shadowCol;
8487
+ ctx.shadowBlur = 8;
8488
+ ctx.shadowOffsetX = 0;
8489
+ ctx.shadowOffsetY = 0;
8490
+ }
8191
8491
  if (gradient) {
8192
8492
  const splitStops = (input) => {
8193
8493
  const s = input.trim();
@@ -8269,9 +8569,19 @@ function useWatermarkDataURL(opts) {
8269
8569
  return () => {
8270
8570
  cancelled = true;
8271
8571
  };
8272
- }, [opts.text, opts.image, opts.width, opts.height, opts.gapX, opts.gapY, opts.rotate, opts.fontSize, opts.fontFamily, opts.fontWeight, opts.fontStyle, opts.color, opts.gradient, opts.pattern]);
8572
+ }, [opts.text, opts.image, opts.width, opts.height, opts.gapX, opts.gapY, opts.rotate, opts.fontSize, opts.fontFamily, opts.fontWeight, opts.fontStyle, opts.color, opts.gradient, opts.pattern, opts.textShadow, opts.shadowColor]);
8273
8573
  return url;
8274
8574
  }
8575
+ var getAnimationClass = (variant, visible) => {
8576
+ if (variant === "none") return "";
8577
+ const animations = {
8578
+ fade: visible ? "animate-in fade-in duration-300" : "animate-out fade-out duration-300",
8579
+ slide: visible ? "animate-in slide-in-from-top duration-500" : "animate-out slide-out-to-top duration-500",
8580
+ scale: visible ? "animate-in zoom-in duration-300" : "animate-out zoom-out duration-300",
8581
+ pulse: visible ? "animate-pulse" : "opacity-0"
8582
+ };
8583
+ return animations[variant] || "";
8584
+ };
8275
8585
  var Watermark = ({
8276
8586
  text: textProp,
8277
8587
  image,
@@ -8295,13 +8605,40 @@ var Watermark = ({
8295
8605
  pattern = "diagonal",
8296
8606
  interactive = false,
8297
8607
  animate = false,
8608
+ animationVariant = "fade",
8609
+ blur = false,
8610
+ blurAmount = 4,
8611
+ textShadow = false,
8612
+ shadowColor,
8613
+ darkMode = false,
8298
8614
  overlayClassName,
8615
+ ariaLabel,
8299
8616
  className,
8300
8617
  style,
8301
8618
  children,
8302
8619
  ...rest
8303
8620
  }) => {
8304
8621
  const [visible, setVisible] = React32.useState(true);
8622
+ const [isDark, setIsDark] = React32.useState(false);
8623
+ React32.useEffect(() => {
8624
+ if (!darkMode) return;
8625
+ const checkDarkMode = () => {
8626
+ const isDarkMode = document.documentElement.classList.contains("dark") || window.matchMedia("(prefers-color-scheme: dark)").matches;
8627
+ setIsDark(isDarkMode);
8628
+ };
8629
+ checkDarkMode();
8630
+ const observer = new MutationObserver(checkDarkMode);
8631
+ observer.observe(document.documentElement, {
8632
+ attributes: true,
8633
+ attributeFilter: ["class"]
8634
+ });
8635
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
8636
+ mediaQuery.addEventListener("change", checkDarkMode);
8637
+ return () => {
8638
+ observer.disconnect();
8639
+ mediaQuery.removeEventListener("change", checkDarkMode);
8640
+ };
8641
+ }, [darkMode]);
8305
8642
  const presetConfig = preset ? PRESETS2[preset] : null;
8306
8643
  const text = textProp ?? presetConfig?.text;
8307
8644
  const color = colorProp ?? presetConfig?.color ?? "rgba(0,0,0,0.15)";
@@ -8309,6 +8646,7 @@ var Watermark = ({
8309
8646
  const fontSize = fontSizeProp ?? presetConfig?.fontSize ?? 14;
8310
8647
  const fontWeight = fontWeightProp ?? presetConfig?.fontWeight ?? "normal";
8311
8648
  const fontStyle = fontStyleProp ?? "normal";
8649
+ const finalOpacity = darkMode && isDark ? opacity * 1.3 : opacity;
8312
8650
  const dataURL = useWatermarkDataURL({
8313
8651
  text,
8314
8652
  image,
@@ -8323,17 +8661,19 @@ var Watermark = ({
8323
8661
  fontStyle,
8324
8662
  color,
8325
8663
  gradient,
8326
- pattern
8664
+ pattern,
8665
+ textShadow,
8666
+ shadowColor
8327
8667
  });
8328
8668
  const overlayStyle = {
8329
8669
  position: fullPage ? "fixed" : "absolute",
8330
8670
  top: 0,
8331
8671
  left: 0,
8332
8672
  zIndex,
8333
- opacity: visible ? opacity : 0,
8673
+ opacity: visible ? finalOpacity : 0,
8334
8674
  backgroundRepeat: "repeat",
8335
8675
  backgroundPosition: `${offsetLeft}px ${offsetTop}px`,
8336
- transition: animate ? "opacity 0.3s ease" : void 0
8676
+ transition: animate && animationVariant === "fade" ? "opacity 0.3s ease" : void 0
8337
8677
  };
8338
8678
  if (fullPage) {
8339
8679
  overlayStyle.right = 0;
@@ -8343,17 +8683,33 @@ var Watermark = ({
8343
8683
  overlayStyle.height = "100%";
8344
8684
  }
8345
8685
  if (dataURL) overlayStyle.backgroundImage = `url(${dataURL})`;
8686
+ const animationClass = animate ? getAnimationClass(animationVariant, visible) : "";
8687
+ const blurClass = blur ? `backdrop-blur-[${blurAmount}px]` : "";
8346
8688
  const overlay = /* @__PURE__ */ jsx40(
8347
8689
  "div",
8348
8690
  {
8349
- "aria-hidden": true,
8350
- className: cn("pointer-events-none", overlayClassName),
8691
+ role: interactive ? "button" : void 0,
8692
+ "aria-label": ariaLabel || (interactive ? "Toggle watermark visibility" : "Watermark overlay"),
8693
+ "aria-hidden": !interactive,
8694
+ tabIndex: interactive ? 0 : void 0,
8695
+ className: cn(
8696
+ interactive ? "cursor-pointer" : "pointer-events-none",
8697
+ blurClass,
8698
+ animationClass,
8699
+ overlayClassName
8700
+ ),
8351
8701
  style: overlayStyle,
8352
- onClick: interactive ? () => setVisible(!visible) : void 0
8702
+ onClick: interactive ? () => setVisible(!visible) : void 0,
8703
+ onKeyDown: interactive ? (e) => {
8704
+ if (e.key === "Enter" || e.key === " ") {
8705
+ e.preventDefault();
8706
+ setVisible(!visible);
8707
+ }
8708
+ } : void 0
8353
8709
  }
8354
8710
  );
8355
8711
  if (fullPage) {
8356
- return /* @__PURE__ */ jsxs35(Fragment13, { children: [
8712
+ return /* @__PURE__ */ jsxs35(Fragment14, { children: [
8357
8713
  children,
8358
8714
  typeof window !== "undefined" ? createPortal9(overlay, document.body) : null
8359
8715
  ] });
@@ -9064,7 +9420,7 @@ function ColorPicker({
9064
9420
 
9065
9421
  // ../../components/ui/Grid.tsx
9066
9422
  import React35, { useId as useId6 } from "react";
9067
- import { Fragment as Fragment14, jsx as jsx43, jsxs as jsxs38 } from "react/jsx-runtime";
9423
+ import { Fragment as Fragment15, jsx as jsx43, jsxs as jsxs38 } from "react/jsx-runtime";
9068
9424
  var BP_MIN = {
9069
9425
  sm: 640,
9070
9426
  md: 768,
@@ -9226,7 +9582,7 @@ var GridItem = React35.forwardRef(
9226
9582
  st.opacity = 0;
9227
9583
  st.animation = `uvGridItemFadeIn 0.5s ease-out forwards`;
9228
9584
  }
9229
- return /* @__PURE__ */ jsxs38(Fragment14, { children: [
9585
+ return /* @__PURE__ */ jsxs38(Fragment15, { children: [
9230
9586
  animationDelay != null && /* @__PURE__ */ jsx43(
9231
9587
  "style",
9232
9588
  {
@@ -9256,16 +9612,16 @@ var Grid_default = Grid;
9256
9612
 
9257
9613
  // ../../components/ui/ClientOnly.tsx
9258
9614
  import { useEffect as useEffect18, useState as useState31 } from "react";
9259
- import { Fragment as Fragment15, jsx as jsx44 } from "react/jsx-runtime";
9615
+ import { Fragment as Fragment16, jsx as jsx44 } from "react/jsx-runtime";
9260
9616
  function ClientOnly({ children, fallback = null }) {
9261
9617
  const [hasMounted, setHasMounted] = useState31(false);
9262
9618
  useEffect18(() => {
9263
9619
  setHasMounted(true);
9264
9620
  }, []);
9265
9621
  if (!hasMounted) {
9266
- return /* @__PURE__ */ jsx44(Fragment15, { children: fallback });
9622
+ return /* @__PURE__ */ jsx44(Fragment16, { children: fallback });
9267
9623
  }
9268
- return /* @__PURE__ */ jsx44(Fragment15, { children });
9624
+ return /* @__PURE__ */ jsx44(Fragment16, { children });
9269
9625
  }
9270
9626
 
9271
9627
  // ../../components/ui/Loading.tsx
@@ -12042,9 +12398,9 @@ function AccessDenied({
12042
12398
 
12043
12399
  // ../../components/ui/ThemeToggleHeadless.tsx
12044
12400
  import { Moon, Sun, Monitor } from "lucide-react";
12045
- import { useEffect as useEffect20, useRef as useRef11, useState as useState32 } from "react";
12401
+ import { useEffect as useEffect20, useRef as useRef12, useState as useState32 } from "react";
12046
12402
  import { createPortal as createPortal10 } from "react-dom";
12047
- import { Fragment as Fragment16, jsx as jsx52, jsxs as jsxs46 } from "react/jsx-runtime";
12403
+ import { Fragment as Fragment17, jsx as jsx52, jsxs as jsxs46 } from "react/jsx-runtime";
12048
12404
  function ThemeToggleHeadless({
12049
12405
  theme,
12050
12406
  onChange,
@@ -12053,7 +12409,7 @@ function ThemeToggleHeadless({
12053
12409
  }) {
12054
12410
  const [isOpen, setIsOpen] = useState32(false);
12055
12411
  const [mounted, setMounted] = useState32(false);
12056
- const triggerRef = useRef11(null);
12412
+ const triggerRef = useRef12(null);
12057
12413
  const [dropdownPosition, setDropdownPosition] = useState32(null);
12058
12414
  useEffect20(() => setMounted(true), []);
12059
12415
  const themes = [
@@ -12095,7 +12451,7 @@ function ThemeToggleHeadless({
12095
12451
  children: /* @__PURE__ */ jsx52(CurrentIcon, { className: "h-5 w-5" })
12096
12452
  }
12097
12453
  ),
12098
- isOpen && /* @__PURE__ */ jsxs46(Fragment16, { children: [
12454
+ isOpen && /* @__PURE__ */ jsxs46(Fragment17, { children: [
12099
12455
  typeof window !== "undefined" && createPortal10(/* @__PURE__ */ jsx52("div", { className: "fixed inset-0 z-[9998]", onClick: () => setIsOpen(false) }), document.body),
12100
12456
  typeof window !== "undefined" && dropdownPosition && createPortal10(
12101
12457
  /* @__PURE__ */ jsx52(
@@ -12144,10 +12500,10 @@ function ThemeToggleHeadless({
12144
12500
  }
12145
12501
 
12146
12502
  // ../../components/ui/LanguageSwitcherHeadless.tsx
12147
- import { useRef as useRef12, useState as useState33 } from "react";
12503
+ import { useRef as useRef13, useState as useState33 } from "react";
12148
12504
  import { createPortal as createPortal11 } from "react-dom";
12149
12505
  import { Globe } from "lucide-react";
12150
- import { Fragment as Fragment17, jsx as jsx53, jsxs as jsxs47 } from "react/jsx-runtime";
12506
+ import { Fragment as Fragment18, jsx as jsx53, jsxs as jsxs47 } from "react/jsx-runtime";
12151
12507
  function LanguageSwitcherHeadless({
12152
12508
  locales,
12153
12509
  currentLocale,
@@ -12157,7 +12513,7 @@ function LanguageSwitcherHeadless({
12157
12513
  }) {
12158
12514
  const [isOpen, setIsOpen] = useState33(false);
12159
12515
  const [dropdownPosition, setDropdownPosition] = useState33(null);
12160
- const triggerButtonRef = useRef12(null);
12516
+ const triggerButtonRef = useRef13(null);
12161
12517
  const currentLanguage = locales.find((l) => l.code === currentLocale) || locales[0];
12162
12518
  const calculatePosition = () => {
12163
12519
  const rect = triggerButtonRef.current?.getBoundingClientRect();
@@ -12192,7 +12548,7 @@ function LanguageSwitcherHeadless({
12192
12548
  children: /* @__PURE__ */ jsx53(Globe, { className: "h-5 w-5" })
12193
12549
  }
12194
12550
  ),
12195
- isOpen && /* @__PURE__ */ jsxs47(Fragment17, { children: [
12551
+ isOpen && /* @__PURE__ */ jsxs47(Fragment18, { children: [
12196
12552
  typeof window !== "undefined" && createPortal11(/* @__PURE__ */ jsx53("div", { className: "fixed inset-0 z-[9998]", onClick: () => setIsOpen(false) }), document.body),
12197
12553
  typeof window !== "undefined" && dropdownPosition && createPortal11(
12198
12554
  /* @__PURE__ */ jsx53(