@underverse-ui/underverse 0.2.29 → 0.2.31

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
@@ -2382,6 +2382,8 @@ var Modal = ({
2382
2382
  const [isMounted, setIsMounted] = React10.useState(false);
2383
2383
  const [isVisible, setIsVisible] = React10.useState(false);
2384
2384
  const [isAnimating, setIsAnimating] = React10.useState(true);
2385
+ const mouseDownTarget = React10.useRef(null);
2386
+ const modalContentRef = React10.useRef(null);
2385
2387
  React10.useEffect(() => {
2386
2388
  setIsMounted(true);
2387
2389
  return () => setIsMounted(false);
@@ -2421,74 +2423,90 @@ var Modal = ({
2421
2423
  document.body.style.overflow = "unset";
2422
2424
  };
2423
2425
  }, [isOpen]);
2424
- const handleOverlayClick = (event) => {
2425
- if (closeOnOverlayClick) {
2426
+ const handleOverlayMouseDown = (event) => {
2427
+ mouseDownTarget.current = event.target;
2428
+ };
2429
+ const handleOverlayMouseUp = (event) => {
2430
+ const modalContent2 = modalContentRef.current;
2431
+ const mouseDownOutside = modalContent2 && !modalContent2.contains(mouseDownTarget.current);
2432
+ const mouseUpOutside = modalContent2 && !modalContent2.contains(event.target);
2433
+ if (closeOnOverlayClick && mouseDownOutside && mouseUpOutside) {
2426
2434
  onClose();
2427
2435
  }
2436
+ mouseDownTarget.current = null;
2428
2437
  };
2429
2438
  if (!isMounted || !isOpen && !isVisible) {
2430
2439
  return null;
2431
2440
  }
2432
2441
  const maxWidthClass = width ? "max-w-none" : fullWidth ? "max-w-full" : sizeStyles3[size];
2433
- const modalContent = /* @__PURE__ */ jsxs12("div", { className: cn("fixed inset-0 z-9999 flex items-center justify-center", overlayClassName), onClick: handleOverlayClick, children: [
2434
- /* @__PURE__ */ jsx14(
2435
- "div",
2436
- {
2437
- className: "absolute inset-0 bg-background/80 backdrop-blur-sm transition-opacity duration-200 ease-out",
2438
- style: {
2439
- opacity: isOpen && !isAnimating ? 1 : 0
2440
- }
2441
- }
2442
- ),
2443
- /* @__PURE__ */ jsxs12(
2444
- "div",
2445
- {
2446
- className: cn(
2447
- "relative w-full rounded-lg bg-card text-card-foreground shadow-xl",
2448
- "transition-all duration-200 ease-out",
2449
- maxWidthClass,
2450
- fullWidth && "mx-0",
2451
- className
2442
+ const modalContent = /* @__PURE__ */ jsxs12(
2443
+ "div",
2444
+ {
2445
+ className: cn("fixed inset-0 z-9999 flex items-center justify-center", overlayClassName),
2446
+ onMouseDown: handleOverlayMouseDown,
2447
+ onMouseUp: handleOverlayMouseUp,
2448
+ children: [
2449
+ /* @__PURE__ */ jsx14(
2450
+ "div",
2451
+ {
2452
+ className: "absolute inset-0 bg-background/80 backdrop-blur-sm transition-opacity duration-200 ease-out",
2453
+ style: {
2454
+ opacity: isOpen && !isAnimating ? 1 : 0
2455
+ }
2456
+ }
2452
2457
  ),
2453
- style: {
2454
- opacity: isOpen && !isAnimating ? 1 : 0,
2455
- transform: isOpen && !isAnimating ? "scale(1)" : "scale(0.9)",
2456
- // Thêm dòng này để tạo hiệu ứng nảy
2457
- transition: "all 300ms cubic-bezier(0.34, 1.76, 0.64, 1)",
2458
- width,
2459
- height
2460
- },
2461
- onClick: (e) => e.stopPropagation(),
2462
- children: [
2463
- (title || description || showCloseButton) && /* @__PURE__ */ jsxs12("div", { className: "flex items-start justify-between p-6 pb-0", children: [
2464
- /* @__PURE__ */ jsxs12("div", { className: "space-y-1.5", children: [
2465
- title && /* @__PURE__ */ jsx14("h2", { className: "text-lg font-semibold leading-none tracking-tight", children: title }),
2466
- description && /* @__PURE__ */ jsx14("p", { className: "text-sm text-muted-foreground", children: description })
2467
- ] }),
2468
- showCloseButton && /* @__PURE__ */ jsx14(
2469
- "button",
2470
- {
2471
- onClick: onClose,
2472
- className: cn(
2473
- "rounded-sm opacity-70 ring-offset-background transition-opacity",
2474
- "hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
2475
- "disabled:pointer-events-none "
2476
- ),
2477
- children: /* @__PURE__ */ jsx14(X4, { className: "h-4 w-4 cursor-pointer" })
2478
- }
2479
- )
2480
- ] }),
2481
- /* @__PURE__ */ jsx14("div", { className: cn("p-6", noPadding && "p-0", contentClassName), children })
2482
- ]
2483
- }
2484
- )
2485
- ] });
2458
+ /* @__PURE__ */ jsxs12(
2459
+ "div",
2460
+ {
2461
+ ref: modalContentRef,
2462
+ className: cn(
2463
+ "relative w-full rounded-lg bg-card text-card-foreground shadow-xl",
2464
+ "transition-all duration-200 ease-out",
2465
+ maxWidthClass,
2466
+ fullWidth && "mx-0",
2467
+ className
2468
+ ),
2469
+ style: {
2470
+ opacity: isOpen && !isAnimating ? 1 : 0,
2471
+ transform: isOpen && !isAnimating ? "scale(1)" : "scale(0.9)",
2472
+ // Thêm dòng này để tạo hiệu ứng nảy
2473
+ transition: "all 300ms cubic-bezier(0.34, 1.76, 0.64, 1)",
2474
+ width,
2475
+ height
2476
+ },
2477
+ onClick: (e) => e.stopPropagation(),
2478
+ children: [
2479
+ (title || description || showCloseButton) && /* @__PURE__ */ jsxs12("div", { className: "flex items-start justify-between p-6 pb-0", children: [
2480
+ /* @__PURE__ */ jsxs12("div", { className: "space-y-1.5", children: [
2481
+ title && /* @__PURE__ */ jsx14("h2", { className: "text-lg font-semibold leading-none tracking-tight", children: title }),
2482
+ description && /* @__PURE__ */ jsx14("p", { className: "text-sm text-muted-foreground", children: description })
2483
+ ] }),
2484
+ showCloseButton && /* @__PURE__ */ jsx14(
2485
+ "button",
2486
+ {
2487
+ onClick: onClose,
2488
+ className: cn(
2489
+ "rounded-sm opacity-70 ring-offset-background transition-opacity",
2490
+ "hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
2491
+ "disabled:pointer-events-none "
2492
+ ),
2493
+ children: /* @__PURE__ */ jsx14(X4, { className: "h-4 w-4 cursor-pointer" })
2494
+ }
2495
+ )
2496
+ ] }),
2497
+ /* @__PURE__ */ jsx14("div", { className: cn("p-6", noPadding && "p-0", contentClassName), children })
2498
+ ]
2499
+ }
2500
+ )
2501
+ ]
2502
+ }
2503
+ );
2486
2504
  return typeof window !== "undefined" ? createPortal(modalContent, document.body) : null;
2487
2505
  };
2488
2506
  var Modal_default = Modal;
2489
2507
 
2490
2508
  // ../../components/ui/Toast.tsx
2491
- import { createContext, useContext, useState as useState9, useCallback as useCallback2, useEffect as useEffect2, useRef as useRef3 } from "react";
2509
+ import { createContext, useContext, useState as useState9, useCallback as useCallback2, useEffect as useEffect2, useRef as useRef4 } from "react";
2492
2510
  import { X as X5, CheckCircle as CheckCircle2, AlertCircle as AlertCircle2, Info, AlertTriangle as AlertTriangle2 } from "lucide-react";
2493
2511
  import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
2494
2512
  var ToastContext = createContext(void 0);
@@ -2501,7 +2519,7 @@ var useToast = () => {
2501
2519
  };
2502
2520
  var ToastProvider = ({ children, position = "top-right", maxToasts = 5 }) => {
2503
2521
  const [toasts, setToasts] = useState9([]);
2504
- const idRef = useRef3(0);
2522
+ const idRef = useRef4(0);
2505
2523
  const removeToast = useCallback2((id) => {
2506
2524
  setToasts((prev) => prev.filter((toast) => toast.id !== id));
2507
2525
  }, []);
@@ -2513,11 +2531,6 @@ var ToastProvider = ({ children, position = "top-right", maxToasts = 5 }) => {
2513
2531
  const updated = [newToast, ...prev];
2514
2532
  return updated.slice(0, maxToasts);
2515
2533
  });
2516
- if (toast.duration !== 0) {
2517
- setTimeout(() => {
2518
- removeToast(id);
2519
- }, toast.duration || 5e3);
2520
- }
2521
2534
  },
2522
2535
  [maxToasts, removeToast]
2523
2536
  );
@@ -2538,33 +2551,31 @@ var ToastComponent = ({ toast, onRemove }) => {
2538
2551
  const [isVisible, setIsVisible] = useState9(false);
2539
2552
  const [progress, setProgress] = useState9(100);
2540
2553
  const [paused, setPaused] = useState9(false);
2541
- const [startTs] = useState9(() => Date.now());
2542
2554
  const total = toast.duration && toast.duration > 0 ? toast.duration : 5e3;
2543
- const [remaining, setRemaining] = useState9(total);
2555
+ const endTsRef = useRef4(Date.now() + total);
2556
+ const remainingRef = useRef4(total);
2557
+ const pausedRef = useRef4(false);
2558
+ const handleRemove = useCallback2(() => {
2559
+ setIsVisible(false);
2560
+ setTimeout(() => onRemove(toast.id), 150);
2561
+ }, [onRemove, toast.id]);
2544
2562
  useEffect2(() => {
2545
2563
  setIsVisible(true);
2546
2564
  if (toast.duration === 0) return;
2547
- let raf;
2548
- const tick = () => {
2549
- if (!paused) {
2550
- const elapsed = Date.now() - startTs;
2551
- const remain = Math.max(total - elapsed, 0);
2552
- setRemaining(remain);
2565
+ remainingRef.current = total;
2566
+ endTsRef.current = Date.now() + total;
2567
+ const intervalId = window.setInterval(() => {
2568
+ if (!pausedRef.current) {
2569
+ const remain = Math.max(endTsRef.current - Date.now(), 0);
2570
+ remainingRef.current = remain;
2553
2571
  setProgress(remain / total * 100);
2554
2572
  if (remain === 0) {
2555
2573
  handleRemove();
2556
- return;
2557
2574
  }
2558
2575
  }
2559
- raf = requestAnimationFrame(tick);
2560
- };
2561
- raf = requestAnimationFrame(tick);
2562
- return () => cancelAnimationFrame(raf);
2563
- }, []);
2564
- const handleRemove = () => {
2565
- setIsVisible(false);
2566
- setTimeout(() => onRemove(toast.id), 150);
2567
- };
2576
+ }, 50);
2577
+ return () => window.clearInterval(intervalId);
2578
+ }, [handleRemove, toast.duration, total]);
2568
2579
  const typeConfig = {
2569
2580
  success: {
2570
2581
  icon: CheckCircle2,
@@ -2600,8 +2611,18 @@ var ToastComponent = ({ toast, onRemove }) => {
2600
2611
  ),
2601
2612
  role: "status",
2602
2613
  "aria-live": toast.type === "error" ? "assertive" : "polite",
2603
- onMouseEnter: () => setPaused(true),
2604
- onMouseLeave: () => setPaused(false),
2614
+ onMouseEnter: () => {
2615
+ if (toast.duration === 0) return;
2616
+ pausedRef.current = true;
2617
+ remainingRef.current = Math.max(endTsRef.current - Date.now(), 0);
2618
+ setPaused(true);
2619
+ },
2620
+ onMouseLeave: () => {
2621
+ if (toast.duration === 0) return;
2622
+ pausedRef.current = false;
2623
+ endTsRef.current = Date.now() + remainingRef.current;
2624
+ setPaused(false);
2625
+ },
2605
2626
  children: [
2606
2627
  /* @__PURE__ */ jsxs13("div", { className: "flex items-start gap-3 p-4", children: [
2607
2628
  /* @__PURE__ */ jsx15(Icon, { className: cn("h-5 w-5 mt-0.5 shrink-0", config.iconClassName) }),
@@ -7762,7 +7783,7 @@ function CategoryTreeSelect({ categories, value, onChange, placeholder = "Ch\u1E
7762
7783
  }
7763
7784
 
7764
7785
  // ../../components/ui/ImageUpload.tsx
7765
- import { useState as useState26, useRef as useRef11, useCallback as useCallback9 } from "react";
7786
+ import { useState as useState26, useRef as useRef12, useCallback as useCallback9 } from "react";
7766
7787
  import { Upload, X as X10, Image as ImageIcon, Loader2 as Loader25, Check as Check7 } from "lucide-react";
7767
7788
  import { useTranslations as useTranslations6 } from "next-intl";
7768
7789
  import { jsx as jsx36, jsxs as jsxs32 } from "react/jsx-runtime";
@@ -7784,7 +7805,7 @@ function ImageUpload({
7784
7805
  const [isDragging, setIsDragging] = useState26(false);
7785
7806
  const [uploading, setUploading] = useState26(false);
7786
7807
  const [uploadedImages, setUploadedImages] = useState26([]);
7787
- const fileInputRef = useRef11(null);
7808
+ const fileInputRef = useRef12(null);
7788
7809
  const { addToast } = useToast();
7789
7810
  const t = useTranslations6("OCR.imageUpload");
7790
7811
  const previewSizes = {
@@ -10923,7 +10944,7 @@ function AccessDenied({
10923
10944
 
10924
10945
  // ../../components/ui/ThemeToggleHeadless.tsx
10925
10946
  import { Moon, Sun, Monitor } from "lucide-react";
10926
- import { useEffect as useEffect20, useRef as useRef13, useState as useState33 } from "react";
10947
+ import { useEffect as useEffect20, useRef as useRef14, useState as useState33 } from "react";
10927
10948
  import { createPortal as createPortal10 } from "react-dom";
10928
10949
  import { Fragment as Fragment18, jsx as jsx52, jsxs as jsxs47 } from "react/jsx-runtime";
10929
10950
  function ThemeToggleHeadless({
@@ -10934,7 +10955,7 @@ function ThemeToggleHeadless({
10934
10955
  }) {
10935
10956
  const [isOpen, setIsOpen] = useState33(false);
10936
10957
  const [mounted, setMounted] = useState33(false);
10937
- const triggerRef = useRef13(null);
10958
+ const triggerRef = useRef14(null);
10938
10959
  const [dropdownPosition, setDropdownPosition] = useState33(null);
10939
10960
  useEffect20(() => setMounted(true), []);
10940
10961
  const themes = [
@@ -11025,7 +11046,7 @@ function ThemeToggleHeadless({
11025
11046
  }
11026
11047
 
11027
11048
  // ../../components/ui/LanguageSwitcherHeadless.tsx
11028
- import { useRef as useRef14, useState as useState34 } from "react";
11049
+ import { useRef as useRef15, useState as useState34 } from "react";
11029
11050
  import { createPortal as createPortal11 } from "react-dom";
11030
11051
  import { Globe } from "lucide-react";
11031
11052
  import { Fragment as Fragment19, jsx as jsx53, jsxs as jsxs48 } from "react/jsx-runtime";
@@ -11038,7 +11059,7 @@ function LanguageSwitcherHeadless({
11038
11059
  }) {
11039
11060
  const [isOpen, setIsOpen] = useState34(false);
11040
11061
  const [dropdownPosition, setDropdownPosition] = useState34(null);
11041
- const triggerButtonRef = useRef14(null);
11062
+ const triggerButtonRef = useRef15(null);
11042
11063
  const currentLanguage = locales.find((l) => l.code === currentLocale) || locales[0];
11043
11064
  const calculatePosition = () => {
11044
11065
  const rect = triggerButtonRef.current?.getBoundingClientRect();