asterui 0.12.54 → 0.12.55

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.
@@ -1 +1 @@
1
- {"version":3,"file":"Drawer.js","sources":["../../src/components/Drawer.tsx"],"sourcesContent":["import React, {\n useEffect,\n useRef,\n useId,\n useCallback,\n useState,\n forwardRef,\n useImperativeHandle,\n createContext,\n useContext,\n} from 'react'\nimport { createPortal } from 'react-dom'\nimport { Skeleton } from './Skeleton'\n\n// DaisyUI classes\nconst dBtn = 'btn'\nconst dBtnGhost = 'btn-ghost'\nconst dBtnSm = 'btn-sm'\nconst dBtnSquare = 'btn-square'\n\nexport type DrawerPlacement = 'top' | 'right' | 'bottom' | 'left'\nexport type DrawerSize = 'default' | 'large' | number\n\nexport interface DrawerPushConfig {\n /** Distance to push parent drawer (default: 180) */\n distance?: number\n}\n\nexport interface DrawerProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'title'> {\n /** Drawer content */\n children: React.ReactNode\n /** Whether the drawer is visible */\n open?: boolean\n /** Callback when drawer is closed */\n onClose?: (e?: React.MouseEvent | React.KeyboardEvent) => void\n /** Callback after open/close animation completes */\n afterOpenChange?: (open: boolean) => void\n /** Drawer title */\n title?: React.ReactNode\n /** Direction drawer slides from */\n placement?: DrawerPlacement\n /** Preset size or custom width/height in pixels */\n size?: DrawerSize\n /** Custom width (overrides size for left/right placement) */\n width?: number | string\n /** Custom height (overrides size for top/bottom placement) */\n height?: number | string\n /** Whether to show close button */\n closable?: boolean\n /** Whether to show mask/backdrop */\n mask?: boolean\n /** Whether clicking mask closes drawer */\n maskClosable?: boolean\n /** Whether ESC closes drawer */\n keyboard?: boolean\n /** Footer content */\n footer?: React.ReactNode\n /** Extra content in header (right side) */\n extra?: React.ReactNode\n /** CSS class for drawer panel */\n className?: string\n /** CSS class for drawer wrapper */\n rootClassName?: string\n /** Style for drawer panel */\n style?: React.CSSProperties\n /** Style for drawer header */\n headerStyle?: React.CSSProperties\n /** Style for drawer body/content area */\n bodyStyle?: React.CSSProperties\n /** Style for drawer footer */\n footerStyle?: React.CSSProperties\n /** Style for drawer wrapper (includes mask) */\n rootStyle?: React.CSSProperties\n /** Style for mask/backdrop */\n maskStyle?: React.CSSProperties\n /** z-index of drawer */\n zIndex?: number\n /** Destroy content when closed */\n destroyOnClose?: boolean\n /** Pre-render drawer content (keep in DOM even when closed) */\n forceRender?: boolean\n /** Where to place initial focus */\n initialFocus?: 'close' | 'content'\n /** Show loading skeleton */\n loading?: boolean\n /** Custom container for portal (false to disable portal) */\n getContainer?: HTMLElement | (() => HTMLElement) | false\n /** Nested drawer push behavior */\n push?: boolean | DrawerPushConfig\n /** Test ID for testing */\n 'data-testid'?: string\n}\n\nexport interface DrawerRef {\n /** The drawer panel element */\n nativeElement: HTMLDivElement | null\n}\n\n// Context for nested drawer push behavior\ninterface DrawerContextValue {\n push: boolean | DrawerPushConfig\n pushDistance: number\n}\n\nconst DrawerContext = createContext<DrawerContextValue | null>(null)\n\nfunction useDrawerContext() {\n return useContext(DrawerContext)\n}\n\n/**\n * Drawer - A panel that slides in from the edge of the screen.\n * Use for forms, details, or task panels.\n * For responsive sidebar navigation, use ResponsiveDrawer instead.\n */\nexport const Drawer = forwardRef<DrawerRef, DrawerProps>(\n (\n {\n children,\n open = false,\n onClose,\n afterOpenChange,\n title,\n placement = 'right',\n size = 'default',\n width,\n height,\n closable = true,\n mask = true,\n maskClosable = true,\n keyboard = true,\n footer,\n extra,\n className = '',\n rootClassName = '',\n style,\n headerStyle,\n bodyStyle,\n footerStyle,\n rootStyle,\n maskStyle,\n zIndex = 1000,\n destroyOnClose = false,\n forceRender = false,\n initialFocus = 'close',\n loading = false,\n getContainer,\n push = { distance: 180 },\n 'data-testid': testId,\n ...rest\n },\n ref\n ) => {\n const drawerRef = useRef<HTMLDivElement>(null)\n const closeButtonRef = useRef<HTMLButtonElement>(null)\n const contentRef = useRef<HTMLDivElement>(null)\n const previousActiveElement = useRef<HTMLElement | null>(null)\n const titleId = useId()\n const contentId = useId()\n const [mounted, setMounted] = useState(false)\n const [shouldRender, setShouldRender] = useState(open || forceRender)\n const [isAnimating, setIsAnimating] = useState(false)\n\n // Get parent drawer context for nested push behavior\n const parentDrawer = useDrawerContext()\n\n // Expose ref\n useImperativeHandle(ref, () => ({\n nativeElement: drawerRef.current,\n }))\n\n // Handle SSR - only render portal after mounting in browser\n useEffect(() => {\n setMounted(true)\n }, [])\n\n // Calculate dimensions\n const getSizeValue = (): number => {\n if (typeof size === 'number') return size\n return size === 'large' ? 736 : 378\n }\n\n const getDimension = (): { width?: string; height?: string } => {\n const isHorizontal = placement === 'left' || placement === 'right'\n const sizeValue = getSizeValue()\n\n if (isHorizontal) {\n const w = width ?? sizeValue\n return { width: typeof w === 'number' ? `${w}px` : w }\n } else {\n const h = height ?? sizeValue\n return { height: typeof h === 'number' ? `${h}px` : h }\n }\n }\n\n // Calculate push distance for nested drawers\n const getPushDistance = (): number => {\n if (!push) return 0\n if (typeof push === 'boolean') return push ? 180 : 0\n return push.distance ?? 180\n }\n\n // Focus trap\n const trapFocus = useCallback((e: KeyboardEvent) => {\n if (!drawerRef.current || e.key !== 'Tab' || typeof document === 'undefined') return\n\n const focusableElements = drawerRef.current.querySelectorAll<HTMLElement>(\n 'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])'\n )\n const firstElement = focusableElements[0]\n const lastElement = focusableElements[focusableElements.length - 1]\n\n if (e.shiftKey && document.activeElement === firstElement) {\n e.preventDefault()\n lastElement?.focus()\n } else if (!e.shiftKey && document.activeElement === lastElement) {\n e.preventDefault()\n firstElement?.focus()\n }\n }, [])\n\n // Handle ESC key\n const handleKeyDown = useCallback(\n (e: KeyboardEvent) => {\n if (keyboard && e.key === 'Escape' && onClose) {\n e.preventDefault()\n onClose()\n }\n },\n [keyboard, onClose]\n )\n\n // Handle animation end\n const handleTransitionEnd = useCallback(() => {\n setIsAnimating(false)\n afterOpenChange?.(open)\n\n if (!open && destroyOnClose) {\n setShouldRender(false)\n }\n }, [open, afterOpenChange, destroyOnClose])\n\n // Open/close effects\n useEffect(() => {\n if (typeof document === 'undefined') return\n\n if (open) {\n setShouldRender(true)\n setIsAnimating(true)\n previousActiveElement.current = document.activeElement as HTMLElement\n document.body.style.overflow = 'hidden'\n\n // Set initial focus\n const focusTimeout = setTimeout(() => {\n if (initialFocus === 'close' && closeButtonRef.current) {\n closeButtonRef.current.focus()\n } else if (contentRef.current) {\n const firstFocusable = contentRef.current.querySelector<HTMLElement>(\n 'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])'\n )\n firstFocusable?.focus()\n }\n }, 0)\n\n // Add event listeners\n document.addEventListener('keydown', handleKeyDown)\n document.addEventListener('keydown', trapFocus)\n\n return () => {\n clearTimeout(focusTimeout)\n document.body.style.overflow = ''\n document.removeEventListener('keydown', handleKeyDown)\n document.removeEventListener('keydown', trapFocus)\n }\n } else {\n setIsAnimating(true)\n // Restore focus to previously focused element if it's still in the DOM\n const prevElement = previousActiveElement.current\n if (prevElement && document.body.contains(prevElement)) {\n prevElement.focus()\n }\n }\n }, [open, handleKeyDown, trapFocus, initialFocus])\n\n const handleMaskClick = (e: React.MouseEvent) => {\n if (maskClosable && onClose) {\n onClose(e)\n }\n }\n\n // Position classes\n const placementClasses: Record<DrawerPlacement, string> = {\n top: 'inset-x-0 top-0',\n right: 'inset-y-0 right-0',\n bottom: 'inset-x-0 bottom-0',\n left: 'inset-y-0 left-0',\n }\n\n // Transform for animation\n const getTransform = (isOpen: boolean): string => {\n // Apply push offset from child drawer\n const pushOffset = parentDrawer && open ? parentDrawer.pushDistance : 0\n\n if (isOpen) {\n if (pushOffset === 0) return 'translate(0, 0)'\n switch (placement) {\n case 'right':\n return `translateX(-${pushOffset}px)`\n case 'left':\n return `translateX(${pushOffset}px)`\n case 'top':\n return `translateY(${pushOffset}px)`\n case 'bottom':\n return `translateY(-${pushOffset}px)`\n }\n }\n\n switch (placement) {\n case 'top':\n return 'translateY(-100%)'\n case 'right':\n return 'translateX(100%)'\n case 'bottom':\n return 'translateY(100%)'\n case 'left':\n return 'translateX(-100%)'\n }\n }\n\n const dimension = getDimension()\n\n // Get container element\n const getContainerElement = (): HTMLElement | null => {\n if (getContainer === false) return null\n if (typeof getContainer === 'function') return getContainer()\n if (getContainer) return getContainer\n return typeof document !== 'undefined' ? document.body : null\n }\n\n // Generate test IDs\n const getTestId = (suffix: string) => (testId ? `${testId}-${suffix}` : undefined)\n\n const drawerContent = (\n <DrawerContext.Provider value={{ push, pushDistance: getPushDistance() }}>\n <div\n className={`fixed inset-0 ${open ? '' : 'pointer-events-none'} ${rootClassName}`}\n style={{ zIndex, ...rootStyle }}\n role=\"presentation\"\n data-state={open ? 'open' : 'closed'}\n data-testid={testId}\n {...rest}\n >\n {/* Backdrop/Mask */}\n {mask && (\n <div\n className={`absolute inset-0 bg-black transition-opacity duration-300 ${\n open ? 'opacity-50' : 'opacity-0'\n }`}\n style={maskStyle}\n onClick={handleMaskClick}\n aria-hidden=\"true\"\n data-testid={getTestId('mask')}\n />\n )}\n\n {/* Drawer Panel */}\n <div\n ref={drawerRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={title ? titleId : undefined}\n aria-describedby={contentId}\n className={`fixed flex flex-col bg-base-100 shadow-xl transition-transform duration-300 ease-in-out ${placementClasses[placement]} ${className}`}\n style={{\n ...dimension,\n transform: getTransform(open),\n ...style,\n }}\n onTransitionEnd={handleTransitionEnd}\n data-testid={getTestId('panel')}\n >\n {/* Header */}\n {(title || closable || extra) && (\n <div\n className=\"flex items-center justify-between px-6 py-4 border-b border-base-300\"\n style={headerStyle}\n data-testid={getTestId('header')}\n >\n {title && (\n <h2 id={titleId} className=\"text-lg font-semibold\">\n {title}\n </h2>\n )}\n <div className=\"flex items-center gap-2 ml-auto\">\n {extra}\n {closable && (\n <button\n ref={closeButtonRef}\n type=\"button\"\n className={`${dBtn} ${dBtnGhost} ${dBtnSm} ${dBtnSquare}`}\n onClick={onClose}\n aria-label=\"Close drawer\"\n data-testid={getTestId('close')}\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n className=\"h-5 w-5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M6 18L18 6M6 6l12 12\"\n />\n </svg>\n </button>\n )}\n </div>\n </div>\n )}\n\n {/* Content */}\n <div\n ref={contentRef}\n id={contentId}\n className=\"flex-1 overflow-auto p-6\"\n style={bodyStyle}\n data-testid={getTestId('body')}\n >\n {loading ? (\n <div className=\"space-y-4\" data-testid={getTestId('skeleton')}>\n <Skeleton className=\"h-4 w-3/4\" />\n <Skeleton className=\"h-4 w-full\" />\n <Skeleton className=\"h-4 w-5/6\" />\n <Skeleton className=\"h-4 w-2/3\" />\n <Skeleton className=\"h-32 w-full\" />\n </div>\n ) : (\n children\n )}\n </div>\n\n {/* Footer */}\n {footer && (\n <div\n className=\"px-6 py-4 border-t border-base-300\"\n style={footerStyle}\n data-testid={getTestId('footer')}\n >\n {footer}\n </div>\n )}\n </div>\n </div>\n </DrawerContext.Provider>\n )\n\n // Don't render during SSR or when not needed\n if (!mounted) return null\n if (!shouldRender && !open && !forceRender) return null\n\n // Render without portal if getContainer is false\n const container = getContainerElement()\n if (container === null) return drawerContent\n\n return createPortal(drawerContent, container)\n }\n)\n\nDrawer.displayName = 'Drawer'\n"],"names":["dBtn","dBtnGhost","dBtnSm","dBtnSquare","DrawerContext","createContext","useDrawerContext","useContext","Drawer","forwardRef","children","open","onClose","afterOpenChange","title","placement","size","width","height","closable","mask","maskClosable","keyboard","footer","extra","className","rootClassName","style","headerStyle","bodyStyle","footerStyle","rootStyle","maskStyle","zIndex","destroyOnClose","forceRender","initialFocus","loading","getContainer","push","testId","rest","ref","drawerRef","useRef","closeButtonRef","contentRef","previousActiveElement","titleId","useId","contentId","mounted","setMounted","useState","shouldRender","setShouldRender","isAnimating","setIsAnimating","parentDrawer","useImperativeHandle","useEffect","getSizeValue","getDimension","isHorizontal","sizeValue","w","h","getPushDistance","trapFocus","useCallback","focusableElements","firstElement","lastElement","handleKeyDown","handleTransitionEnd","focusTimeout","prevElement","handleMaskClick","placementClasses","getTransform","isOpen","pushOffset","dimension","getContainerElement","getTestId","suffix","drawerContent","jsx","jsxs","Skeleton","container","createPortal"],"mappings":"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"}
1
+ {"version":3,"file":"Drawer.js","sources":["../../src/components/Drawer.tsx"],"sourcesContent":["import React, {\n useEffect,\n useRef,\n useId,\n useCallback,\n useState,\n forwardRef,\n useImperativeHandle,\n createContext,\n useContext,\n} from 'react'\nimport { createPortal } from 'react-dom'\nimport { Skeleton } from './Skeleton'\n\n// DaisyUI classes\nconst dBtn = 'btn'\nconst dBtnGhost = 'btn-ghost'\nconst dBtnSm = 'btn-sm'\nconst dBtnSquare = 'btn-square'\n\nexport type DrawerPlacement = 'top' | 'right' | 'bottom' | 'left'\nexport type DrawerSize = 'default' | 'large' | number\n\nexport interface DrawerPushConfig {\n /** Distance to push parent drawer (default: 180) */\n distance?: number\n}\n\nexport interface DrawerProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'title'> {\n /** Drawer content */\n children: React.ReactNode\n /** Whether the drawer is visible */\n open?: boolean\n /** Callback when drawer is closed */\n onClose?: (e?: React.MouseEvent | React.KeyboardEvent) => void\n /** Callback after open/close animation completes */\n afterOpenChange?: (open: boolean) => void\n /** Drawer title */\n title?: React.ReactNode\n /** Direction drawer slides from */\n placement?: DrawerPlacement\n /** Preset size or custom width/height in pixels */\n size?: DrawerSize\n /** Custom width (overrides size for left/right placement) */\n width?: number | string\n /** Custom height (overrides size for top/bottom placement) */\n height?: number | string\n /** Whether to show close button */\n closable?: boolean\n /** Whether to show mask/backdrop */\n mask?: boolean\n /** Whether clicking mask closes drawer */\n maskClosable?: boolean\n /** Whether ESC closes drawer */\n keyboard?: boolean\n /** Footer content */\n footer?: React.ReactNode\n /** Extra content in header (right side) */\n extra?: React.ReactNode\n /** CSS class for drawer panel */\n className?: string\n /** CSS class for drawer wrapper */\n rootClassName?: string\n /** Style for drawer panel */\n style?: React.CSSProperties\n /** Style for drawer header */\n headerStyle?: React.CSSProperties\n /** Style for drawer body/content area */\n bodyStyle?: React.CSSProperties\n /** Style for drawer footer */\n footerStyle?: React.CSSProperties\n /** Style for drawer wrapper (includes mask) */\n rootStyle?: React.CSSProperties\n /** Style for mask/backdrop */\n maskStyle?: React.CSSProperties\n /** z-index of drawer */\n zIndex?: number\n /** Destroy content when closed */\n destroyOnClose?: boolean\n /** Pre-render drawer content (keep in DOM even when closed) */\n forceRender?: boolean\n /** Where to place initial focus */\n initialFocus?: 'close' | 'content'\n /** Show loading skeleton */\n loading?: boolean\n /** Custom container for portal (false to disable portal) */\n getContainer?: HTMLElement | (() => HTMLElement) | false\n /** Nested drawer push behavior */\n push?: boolean | DrawerPushConfig\n /** Test ID for testing */\n 'data-testid'?: string\n}\n\nexport interface DrawerRef {\n /** The drawer panel element */\n nativeElement: HTMLDivElement | null\n}\n\n// Context for nested drawer push behavior\ninterface DrawerContextValue {\n push: boolean | DrawerPushConfig\n pushDistance: number\n}\n\nconst DrawerContext = createContext<DrawerContextValue | null>(null)\n\nfunction useDrawerContext() {\n return useContext(DrawerContext)\n}\n\n/**\n * Drawer - A panel that slides in from the edge of the screen.\n * Use for forms, details, or task panels.\n * For responsive sidebar navigation, use ResponsiveDrawer instead.\n */\nexport const Drawer = forwardRef<DrawerRef, DrawerProps>(\n (\n {\n children,\n open = false,\n onClose,\n afterOpenChange,\n title,\n placement = 'right',\n size = 'default',\n width,\n height,\n closable = true,\n mask = true,\n maskClosable = true,\n keyboard = true,\n footer,\n extra,\n className = '',\n rootClassName = '',\n style,\n headerStyle,\n bodyStyle,\n footerStyle,\n rootStyle,\n maskStyle,\n zIndex = 1000,\n destroyOnClose = false,\n forceRender = false,\n initialFocus = 'close',\n loading = false,\n getContainer,\n push = { distance: 180 },\n 'data-testid': testId,\n ...rest\n },\n ref\n ) => {\n const drawerRef = useRef<HTMLDivElement>(null)\n const closeButtonRef = useRef<HTMLButtonElement>(null)\n const contentRef = useRef<HTMLDivElement>(null)\n const previousActiveElement = useRef<HTMLElement | null>(null)\n const titleId = useId()\n const contentId = useId()\n const [mounted, setMounted] = useState(false)\n const [shouldRender, setShouldRender] = useState(open || forceRender)\n const [, setIsAnimating] = useState(false)\n\n // Get parent drawer context for nested push behavior\n const parentDrawer = useDrawerContext()\n\n // Expose ref\n useImperativeHandle(ref, () => ({\n nativeElement: drawerRef.current,\n }))\n\n // Handle SSR - only render portal after mounting in browser\n useEffect(() => {\n setMounted(true)\n }, [])\n\n // Calculate dimensions\n const getSizeValue = (): number => {\n if (typeof size === 'number') return size\n return size === 'large' ? 736 : 378\n }\n\n const getDimension = (): { width?: string; height?: string } => {\n const isHorizontal = placement === 'left' || placement === 'right'\n const sizeValue = getSizeValue()\n\n if (isHorizontal) {\n const w = width ?? sizeValue\n return { width: typeof w === 'number' ? `${w}px` : w }\n } else {\n const h = height ?? sizeValue\n return { height: typeof h === 'number' ? `${h}px` : h }\n }\n }\n\n // Calculate push distance for nested drawers\n const getPushDistance = (): number => {\n if (!push) return 0\n if (typeof push === 'boolean') return push ? 180 : 0\n return push.distance ?? 180\n }\n\n // Focus trap\n const trapFocus = useCallback((e: KeyboardEvent) => {\n if (!drawerRef.current || e.key !== 'Tab' || typeof document === 'undefined') return\n\n const focusableElements = drawerRef.current.querySelectorAll<HTMLElement>(\n 'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])'\n )\n const firstElement = focusableElements[0]\n const lastElement = focusableElements[focusableElements.length - 1]\n\n if (e.shiftKey && document.activeElement === firstElement) {\n e.preventDefault()\n lastElement?.focus()\n } else if (!e.shiftKey && document.activeElement === lastElement) {\n e.preventDefault()\n firstElement?.focus()\n }\n }, [])\n\n // Handle ESC key\n const handleKeyDown = useCallback(\n (e: KeyboardEvent) => {\n if (keyboard && e.key === 'Escape' && onClose) {\n e.preventDefault()\n onClose()\n }\n },\n [keyboard, onClose]\n )\n\n // Handle animation end\n const handleTransitionEnd = useCallback(() => {\n setIsAnimating(false)\n afterOpenChange?.(open)\n\n if (!open && destroyOnClose) {\n setShouldRender(false)\n }\n }, [open, afterOpenChange, destroyOnClose])\n\n // Open/close effects\n useEffect(() => {\n if (typeof document === 'undefined') return\n\n if (open) {\n setShouldRender(true)\n setIsAnimating(true)\n previousActiveElement.current = document.activeElement as HTMLElement\n document.body.style.overflow = 'hidden'\n\n // Set initial focus\n const focusTimeout = setTimeout(() => {\n if (initialFocus === 'close' && closeButtonRef.current) {\n closeButtonRef.current.focus()\n } else if (contentRef.current) {\n const firstFocusable = contentRef.current.querySelector<HTMLElement>(\n 'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])'\n )\n firstFocusable?.focus()\n }\n }, 0)\n\n // Add event listeners\n document.addEventListener('keydown', handleKeyDown)\n document.addEventListener('keydown', trapFocus)\n\n return () => {\n clearTimeout(focusTimeout)\n document.body.style.overflow = ''\n document.removeEventListener('keydown', handleKeyDown)\n document.removeEventListener('keydown', trapFocus)\n }\n } else {\n setIsAnimating(true)\n // Restore focus to previously focused element if it's still in the DOM\n const prevElement = previousActiveElement.current\n if (prevElement && document.body.contains(prevElement)) {\n prevElement.focus()\n }\n }\n }, [open, handleKeyDown, trapFocus, initialFocus])\n\n const handleMaskClick = (e: React.MouseEvent) => {\n if (maskClosable && onClose) {\n onClose(e)\n }\n }\n\n // Position classes\n const placementClasses: Record<DrawerPlacement, string> = {\n top: 'inset-x-0 top-0',\n right: 'inset-y-0 right-0',\n bottom: 'inset-x-0 bottom-0',\n left: 'inset-y-0 left-0',\n }\n\n // Transform for animation\n const getTransform = (isOpen: boolean): string => {\n // Apply push offset from child drawer\n const pushOffset = parentDrawer && open ? parentDrawer.pushDistance : 0\n\n if (isOpen) {\n if (pushOffset === 0) return 'translate(0, 0)'\n switch (placement) {\n case 'right':\n return `translateX(-${pushOffset}px)`\n case 'left':\n return `translateX(${pushOffset}px)`\n case 'top':\n return `translateY(${pushOffset}px)`\n case 'bottom':\n return `translateY(-${pushOffset}px)`\n }\n }\n\n switch (placement) {\n case 'top':\n return 'translateY(-100%)'\n case 'right':\n return 'translateX(100%)'\n case 'bottom':\n return 'translateY(100%)'\n case 'left':\n return 'translateX(-100%)'\n }\n }\n\n const dimension = getDimension()\n\n // Get container element\n const getContainerElement = (): HTMLElement | null => {\n if (getContainer === false) return null\n if (typeof getContainer === 'function') return getContainer()\n if (getContainer) return getContainer\n return typeof document !== 'undefined' ? document.body : null\n }\n\n // Generate test IDs\n const getTestId = (suffix: string) => (testId ? `${testId}-${suffix}` : undefined)\n\n const drawerContent = (\n <DrawerContext.Provider value={{ push, pushDistance: getPushDistance() }}>\n <div\n className={`fixed inset-0 ${open ? '' : 'pointer-events-none'} ${rootClassName}`}\n style={{ zIndex, ...rootStyle }}\n role=\"presentation\"\n data-state={open ? 'open' : 'closed'}\n data-testid={testId}\n {...rest}\n >\n {/* Backdrop/Mask */}\n {mask && (\n <div\n className={`absolute inset-0 bg-black transition-opacity duration-300 ${\n open ? 'opacity-50' : 'opacity-0'\n }`}\n style={maskStyle}\n onClick={handleMaskClick}\n aria-hidden=\"true\"\n data-testid={getTestId('mask')}\n />\n )}\n\n {/* Drawer Panel */}\n <div\n ref={drawerRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={title ? titleId : undefined}\n aria-describedby={contentId}\n className={`fixed flex flex-col bg-base-100 shadow-xl transition-transform duration-300 ease-in-out ${placementClasses[placement]} ${className}`}\n style={{\n ...dimension,\n transform: getTransform(open),\n ...style,\n }}\n onTransitionEnd={handleTransitionEnd}\n data-testid={getTestId('panel')}\n >\n {/* Header */}\n {(title || closable || extra) && (\n <div\n className=\"flex items-center justify-between px-6 py-4 border-b border-base-300\"\n style={headerStyle}\n data-testid={getTestId('header')}\n >\n {title && (\n <h2 id={titleId} className=\"text-lg font-semibold\">\n {title}\n </h2>\n )}\n <div className=\"flex items-center gap-2 ml-auto\">\n {extra}\n {closable && (\n <button\n ref={closeButtonRef}\n type=\"button\"\n className={`${dBtn} ${dBtnGhost} ${dBtnSm} ${dBtnSquare}`}\n onClick={onClose}\n aria-label=\"Close drawer\"\n data-testid={getTestId('close')}\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n className=\"h-5 w-5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M6 18L18 6M6 6l12 12\"\n />\n </svg>\n </button>\n )}\n </div>\n </div>\n )}\n\n {/* Content */}\n <div\n ref={contentRef}\n id={contentId}\n className=\"flex-1 overflow-auto p-6\"\n style={bodyStyle}\n data-testid={getTestId('body')}\n >\n {loading ? (\n <div className=\"space-y-4\" data-testid={getTestId('skeleton')}>\n <Skeleton className=\"h-4 w-3/4\" />\n <Skeleton className=\"h-4 w-full\" />\n <Skeleton className=\"h-4 w-5/6\" />\n <Skeleton className=\"h-4 w-2/3\" />\n <Skeleton className=\"h-32 w-full\" />\n </div>\n ) : (\n children\n )}\n </div>\n\n {/* Footer */}\n {footer && (\n <div\n className=\"px-6 py-4 border-t border-base-300\"\n style={footerStyle}\n data-testid={getTestId('footer')}\n >\n {footer}\n </div>\n )}\n </div>\n </div>\n </DrawerContext.Provider>\n )\n\n // Don't render during SSR or when not needed\n if (!mounted) return null\n if (!shouldRender && !open && !forceRender) return null\n\n // Render without portal if getContainer is false\n const container = getContainerElement()\n if (container === null) return drawerContent\n\n return createPortal(drawerContent, container)\n }\n)\n\nDrawer.displayName = 'Drawer'\n"],"names":["dBtn","dBtnGhost","dBtnSm","dBtnSquare","DrawerContext","createContext","useDrawerContext","useContext","Drawer","forwardRef","children","open","onClose","afterOpenChange","title","placement","size","width","height","closable","mask","maskClosable","keyboard","footer","extra","className","rootClassName","style","headerStyle","bodyStyle","footerStyle","rootStyle","maskStyle","zIndex","destroyOnClose","forceRender","initialFocus","loading","getContainer","push","testId","rest","ref","drawerRef","useRef","closeButtonRef","contentRef","previousActiveElement","titleId","useId","contentId","mounted","setMounted","useState","shouldRender","setShouldRender","setIsAnimating","parentDrawer","useImperativeHandle","useEffect","getSizeValue","getDimension","isHorizontal","sizeValue","w","h","getPushDistance","trapFocus","useCallback","focusableElements","firstElement","lastElement","handleKeyDown","handleTransitionEnd","focusTimeout","prevElement","handleMaskClick","placementClasses","getTransform","isOpen","pushOffset","dimension","getContainerElement","getTestId","suffix","drawerContent","jsx","jsxs","Skeleton","container","createPortal"],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ import { default as React } from 'react';
2
+ export interface ThemeProviderProps {
3
+ children: React.ReactNode;
4
+ /** Default theme. Use "system" to follow browser preference. */
5
+ defaultTheme?: string;
6
+ /** localStorage key for persisting theme. Set to false to disable persistence. */
7
+ storageKey?: string | false;
8
+ /** Light theme to use when system preference is light */
9
+ lightTheme?: string;
10
+ /** Dark theme to use when system preference is dark */
11
+ darkTheme?: string;
12
+ /** Custom function to determine if a theme is dark */
13
+ isDarkTheme?: (theme: string) => boolean;
14
+ }
15
+ export interface ThemeColors {
16
+ background: string;
17
+ foreground: string;
18
+ primary: string;
19
+ primaryContent: string;
20
+ secondary: string;
21
+ accent: string;
22
+ info: string;
23
+ success: string;
24
+ warning: string;
25
+ error: string;
26
+ }
27
+ export interface ThemeContextValue {
28
+ /** The theme setting (what user selected: "system", "light", "dark", etc.) */
29
+ theme: string;
30
+ /** The actual applied theme after resolving "system" */
31
+ resolvedTheme: string;
32
+ /** Whether the resolved theme is dark */
33
+ isDark: boolean;
34
+ /** Set the theme */
35
+ setTheme: (theme: string) => void;
36
+ /** Computed theme colors as hex values (for canvas/non-CSS contexts) */
37
+ colors: ThemeColors;
38
+ /** The system preference ("light" or "dark") */
39
+ systemTheme: 'light' | 'dark';
40
+ }
41
+ export declare function ThemeProvider({ children, defaultTheme, storageKey, lightTheme, darkTheme, isDarkTheme, }: ThemeProviderProps): import("react/jsx-runtime").JSX.Element;
42
+ /**
43
+ * Hook to access theme context.
44
+ * Must be used within a ThemeProvider.
45
+ */
46
+ export declare function useThemeContext(): ThemeContextValue;
47
+ /**
48
+ * Check if ThemeProvider is present in the tree.
49
+ */
50
+ export declare function useHasThemeProvider(): boolean;
@@ -0,0 +1,128 @@
1
+ import { jsx as E } from "react/jsx-runtime";
2
+ import { useState as a, useMemo as m, useCallback as k, useEffect as l, createContext as P, useContext as v } from "react";
3
+ const L = /* @__PURE__ */ new Set([
4
+ "dark",
5
+ "synthwave",
6
+ "halloween",
7
+ "forest",
8
+ "black",
9
+ "luxury",
10
+ "dracula",
11
+ "business",
12
+ "night",
13
+ "coffee",
14
+ "dim",
15
+ "sunset"
16
+ ]), h = P(void 0);
17
+ function M(t) {
18
+ if (typeof document > "u") return "#000000";
19
+ const e = document.createElement("canvas");
20
+ e.width = e.height = 1;
21
+ const n = e.getContext("2d");
22
+ if (!n) return "#000000";
23
+ n.fillStyle = t, n.fillRect(0, 0, 1, 1);
24
+ const [f, o, i] = n.getImageData(0, 0, 1, 1).data;
25
+ return `#${((1 << 24) + (f << 16) + (o << 8) + i).toString(16).slice(1)}`;
26
+ }
27
+ function b() {
28
+ if (typeof document > "u")
29
+ return {
30
+ background: "#ffffff",
31
+ foreground: "#000000",
32
+ primary: "#6366f1",
33
+ primaryContent: "#ffffff",
34
+ secondary: "#f000b8",
35
+ accent: "#37cdbe",
36
+ info: "#3abff8",
37
+ success: "#36d399",
38
+ warning: "#fbbd23",
39
+ error: "#f87272"
40
+ };
41
+ const t = getComputedStyle(document.documentElement), e = (n, f) => {
42
+ const o = t.getPropertyValue(n).trim();
43
+ return o ? M(o) : f;
44
+ };
45
+ return {
46
+ background: e("--color-base-100", "#ffffff"),
47
+ foreground: e("--color-base-content", "#000000"),
48
+ primary: e("--color-primary", "#6366f1"),
49
+ primaryContent: e("--color-primary-content", "#ffffff"),
50
+ secondary: e("--color-secondary", "#f000b8"),
51
+ accent: e("--color-accent", "#37cdbe"),
52
+ info: e("--color-info", "#3abff8"),
53
+ success: e("--color-success", "#36d399"),
54
+ warning: e("--color-warning", "#fbbd23"),
55
+ error: e("--color-error", "#f87272")
56
+ };
57
+ }
58
+ function A() {
59
+ return typeof window > "u" ? "light" : window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
60
+ }
61
+ function H(t) {
62
+ if (!t || typeof window > "u") return null;
63
+ try {
64
+ return localStorage.getItem(t);
65
+ } catch {
66
+ return null;
67
+ }
68
+ }
69
+ function I(t, e) {
70
+ if (!(!t || typeof window > "u"))
71
+ try {
72
+ localStorage.setItem(t, e);
73
+ } catch {
74
+ }
75
+ }
76
+ function j({
77
+ children: t,
78
+ defaultTheme: e = "system",
79
+ storageKey: n = "asterui-theme",
80
+ lightTheme: f = "light",
81
+ darkTheme: o = "dark",
82
+ isDarkTheme: i
83
+ }) {
84
+ const [u, w] = a(() => H(n) || e), [d, C] = a(A), c = m(() => u === "system" ? d === "dark" ? o : f : u, [u, d, f, o]), g = m(() => i ? i(c) : L.has(c), [c, i]), [y, S] = a(b), p = k((r) => {
85
+ w(r), I(n, r);
86
+ }, [n]);
87
+ l(() => {
88
+ typeof document > "u" || (document.documentElement.setAttribute("data-theme", c), requestAnimationFrame(() => {
89
+ S(b());
90
+ }));
91
+ }, [c]), l(() => {
92
+ if (typeof window > "u") return;
93
+ const r = window.matchMedia("(prefers-color-scheme: dark)"), s = (T) => {
94
+ C(T.matches ? "dark" : "light");
95
+ };
96
+ return r.addEventListener("change", s), () => r.removeEventListener("change", s);
97
+ }, []), l(() => {
98
+ if (!n || typeof window > "u") return;
99
+ const r = (s) => {
100
+ s.key === n && s.newValue && w(s.newValue);
101
+ };
102
+ return window.addEventListener("storage", r), () => window.removeEventListener("storage", r);
103
+ }, [n]);
104
+ const x = m(() => ({
105
+ theme: u,
106
+ resolvedTheme: c,
107
+ isDark: g,
108
+ setTheme: p,
109
+ colors: y,
110
+ systemTheme: d
111
+ }), [u, c, g, p, y, d]);
112
+ return /* @__PURE__ */ E(h.Provider, { value: x, children: t });
113
+ }
114
+ function q() {
115
+ const t = v(h);
116
+ if (!t)
117
+ throw new Error("useThemeContext must be used within a ThemeProvider");
118
+ return t;
119
+ }
120
+ function D() {
121
+ return v(h) !== void 0;
122
+ }
123
+ export {
124
+ j as ThemeProvider,
125
+ D as useHasThemeProvider,
126
+ q as useThemeContext
127
+ };
128
+ //# sourceMappingURL=ThemeProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ThemeProvider.js","sources":["../../src/components/ThemeProvider.tsx"],"sourcesContent":["import React, { createContext, useContext, useEffect, useState, useCallback, useMemo } from 'react'\n\n// Common dark themes in DaisyUI\nconst DARK_THEMES = new Set([\n 'dark', 'synthwave', 'halloween', 'forest', 'black', 'luxury', 'dracula',\n 'business', 'night', 'coffee', 'dim', 'sunset'\n])\n\nexport interface ThemeProviderProps {\n children: React.ReactNode\n /** Default theme. Use \"system\" to follow browser preference. */\n defaultTheme?: string\n /** localStorage key for persisting theme. Set to false to disable persistence. */\n storageKey?: string | false\n /** Light theme to use when system preference is light */\n lightTheme?: string\n /** Dark theme to use when system preference is dark */\n darkTheme?: string\n /** Custom function to determine if a theme is dark */\n isDarkTheme?: (theme: string) => boolean\n}\n\nexport interface ThemeColors {\n background: string\n foreground: string\n primary: string\n primaryContent: string\n secondary: string\n accent: string\n info: string\n success: string\n warning: string\n error: string\n}\n\nexport interface ThemeContextValue {\n /** The theme setting (what user selected: \"system\", \"light\", \"dark\", etc.) */\n theme: string\n /** The actual applied theme after resolving \"system\" */\n resolvedTheme: string\n /** Whether the resolved theme is dark */\n isDark: boolean\n /** Set the theme */\n setTheme: (theme: string) => void\n /** Computed theme colors as hex values (for canvas/non-CSS contexts) */\n colors: ThemeColors\n /** The system preference (\"light\" or \"dark\") */\n systemTheme: 'light' | 'dark'\n}\n\nconst ThemeContext = createContext<ThemeContextValue | undefined>(undefined)\n\n// Convert any CSS color to hex\nfunction colorToHex(color: string): string {\n if (typeof document === 'undefined') return '#000000'\n const canvas = document.createElement('canvas')\n canvas.width = canvas.height = 1\n const ctx = canvas.getContext('2d')\n if (!ctx) return '#000000'\n ctx.fillStyle = color\n ctx.fillRect(0, 0, 1, 1)\n const [r, g, b] = ctx.getImageData(0, 0, 1, 1).data\n return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`\n}\n\nfunction getThemeColors(): ThemeColors {\n if (typeof document === 'undefined') {\n return {\n background: '#ffffff',\n foreground: '#000000',\n primary: '#6366f1',\n primaryContent: '#ffffff',\n secondary: '#f000b8',\n accent: '#37cdbe',\n info: '#3abff8',\n success: '#36d399',\n warning: '#fbbd23',\n error: '#f87272',\n }\n }\n\n const style = getComputedStyle(document.documentElement)\n const getColor = (varName: string, fallback: string): string => {\n const value = style.getPropertyValue(varName).trim()\n return value ? colorToHex(value) : fallback\n }\n\n return {\n background: getColor('--color-base-100', '#ffffff'),\n foreground: getColor('--color-base-content', '#000000'),\n primary: getColor('--color-primary', '#6366f1'),\n primaryContent: getColor('--color-primary-content', '#ffffff'),\n secondary: getColor('--color-secondary', '#f000b8'),\n accent: getColor('--color-accent', '#37cdbe'),\n info: getColor('--color-info', '#3abff8'),\n success: getColor('--color-success', '#36d399'),\n warning: getColor('--color-warning', '#fbbd23'),\n error: getColor('--color-error', '#f87272'),\n }\n}\n\nfunction getSystemTheme(): 'light' | 'dark' {\n if (typeof window === 'undefined') return 'light'\n return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'\n}\n\nfunction getStoredTheme(key: string | false): string | null {\n if (!key || typeof window === 'undefined') return null\n try {\n return localStorage.getItem(key)\n } catch {\n return null\n }\n}\n\nfunction storeTheme(key: string | false, theme: string): void {\n if (!key || typeof window === 'undefined') return\n try {\n localStorage.setItem(key, theme)\n } catch {\n // Ignore storage errors\n }\n}\n\nexport function ThemeProvider({\n children,\n defaultTheme = 'system',\n storageKey = 'asterui-theme',\n lightTheme = 'light',\n darkTheme = 'dark',\n isDarkTheme,\n}: ThemeProviderProps) {\n // Initialize theme from storage or default\n const [theme, setThemeState] = useState<string>(() => {\n const stored = getStoredTheme(storageKey)\n return stored || defaultTheme\n })\n\n // Track system preference\n const [systemTheme, setSystemTheme] = useState<'light' | 'dark'>(getSystemTheme)\n\n // Resolve the actual theme\n const resolvedTheme = useMemo(() => {\n if (theme === 'system') {\n return systemTheme === 'dark' ? darkTheme : lightTheme\n }\n return theme\n }, [theme, systemTheme, lightTheme, darkTheme])\n\n // Determine if dark\n const isDark = useMemo(() => {\n if (isDarkTheme) return isDarkTheme(resolvedTheme)\n return DARK_THEMES.has(resolvedTheme)\n }, [resolvedTheme, isDarkTheme])\n\n // Track colors (updated after theme applies)\n const [colors, setColors] = useState<ThemeColors>(getThemeColors)\n\n // Set theme function\n const setTheme = useCallback((newTheme: string) => {\n setThemeState(newTheme)\n storeTheme(storageKey, newTheme)\n }, [storageKey])\n\n // Apply theme to document\n useEffect(() => {\n if (typeof document === 'undefined') return\n document.documentElement.setAttribute('data-theme', resolvedTheme)\n\n // Update colors after a frame to ensure CSS has applied\n requestAnimationFrame(() => {\n setColors(getThemeColors())\n })\n }, [resolvedTheme])\n\n // Listen for system preference changes\n useEffect(() => {\n if (typeof window === 'undefined') return\n\n const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')\n const handleChange = (e: MediaQueryListEvent) => {\n setSystemTheme(e.matches ? 'dark' : 'light')\n }\n\n mediaQuery.addEventListener('change', handleChange)\n return () => mediaQuery.removeEventListener('change', handleChange)\n }, [])\n\n // Listen for storage changes (cross-tab sync)\n useEffect(() => {\n if (!storageKey || typeof window === 'undefined') return\n\n const handleStorage = (e: StorageEvent) => {\n if (e.key === storageKey && e.newValue) {\n setThemeState(e.newValue)\n }\n }\n\n window.addEventListener('storage', handleStorage)\n return () => window.removeEventListener('storage', handleStorage)\n }, [storageKey])\n\n const value = useMemo<ThemeContextValue>(() => ({\n theme,\n resolvedTheme,\n isDark,\n setTheme,\n colors,\n systemTheme,\n }), [theme, resolvedTheme, isDark, setTheme, colors, systemTheme])\n\n return (\n <ThemeContext.Provider value={value}>\n {children}\n </ThemeContext.Provider>\n )\n}\n\n/**\n * Hook to access theme context.\n * Must be used within a ThemeProvider.\n */\nexport function useThemeContext(): ThemeContextValue {\n const context = useContext(ThemeContext)\n if (!context) {\n throw new Error('useThemeContext must be used within a ThemeProvider')\n }\n return context\n}\n\n/**\n * Check if ThemeProvider is present in the tree.\n */\nexport function useHasThemeProvider(): boolean {\n return useContext(ThemeContext) !== undefined\n}\n"],"names":["DARK_THEMES","ThemeContext","createContext","colorToHex","color","canvas","ctx","r","g","b","getThemeColors","style","getColor","varName","fallback","value","getSystemTheme","getStoredTheme","key","storeTheme","theme","ThemeProvider","children","defaultTheme","storageKey","lightTheme","darkTheme","isDarkTheme","setThemeState","useState","systemTheme","setSystemTheme","resolvedTheme","useMemo","isDark","colors","setColors","setTheme","useCallback","newTheme","useEffect","mediaQuery","handleChange","e","handleStorage","jsx","useThemeContext","context","useContext","useHasThemeProvider"],"mappings":";;AAGA,MAAMA,wBAAkB,IAAI;AAAA,EAC1B;AAAA,EAAQ;AAAA,EAAa;AAAA,EAAa;AAAA,EAAU;AAAA,EAAS;AAAA,EAAU;AAAA,EAC/D;AAAA,EAAY;AAAA,EAAS;AAAA,EAAU;AAAA,EAAO;AACxC,CAAC,GA4CKC,IAAeC,EAA6C,MAAS;AAG3E,SAASC,EAAWC,GAAuB;AACzC,MAAI,OAAO,WAAa,IAAa,QAAO;AAC5C,QAAMC,IAAS,SAAS,cAAc,QAAQ;AAC9C,EAAAA,EAAO,QAAQA,EAAO,SAAS;AAC/B,QAAMC,IAAMD,EAAO,WAAW,IAAI;AAClC,MAAI,CAACC,EAAK,QAAO;AACjB,EAAAA,EAAI,YAAYF,GAChBE,EAAI,SAAS,GAAG,GAAG,GAAG,CAAC;AACvB,QAAM,CAACC,GAAGC,GAAGC,CAAC,IAAIH,EAAI,aAAa,GAAG,GAAG,GAAG,CAAC,EAAE;AAC/C,SAAO,MAAM,KAAK,OAAOC,KAAK,OAAOC,KAAK,KAAKC,GAAG,SAAS,EAAE,EAAE,MAAM,CAAC,CAAC;AACzE;AAEA,SAASC,IAA8B;AACrC,MAAI,OAAO,WAAa;AACtB,WAAO;AAAA,MACL,YAAY;AAAA,MACZ,YAAY;AAAA,MACZ,SAAS;AAAA,MACT,gBAAgB;AAAA,MAChB,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,SAAS;AAAA,MACT,SAAS;AAAA,MACT,OAAO;AAAA,IAAA;AAIX,QAAMC,IAAQ,iBAAiB,SAAS,eAAe,GACjDC,IAAW,CAACC,GAAiBC,MAA6B;AAC9D,UAAMC,IAAQJ,EAAM,iBAAiBE,CAAO,EAAE,KAAA;AAC9C,WAAOE,IAAQZ,EAAWY,CAAK,IAAID;AAAA,EACrC;AAEA,SAAO;AAAA,IACL,YAAYF,EAAS,oBAAoB,SAAS;AAAA,IAClD,YAAYA,EAAS,wBAAwB,SAAS;AAAA,IACtD,SAASA,EAAS,mBAAmB,SAAS;AAAA,IAC9C,gBAAgBA,EAAS,2BAA2B,SAAS;AAAA,IAC7D,WAAWA,EAAS,qBAAqB,SAAS;AAAA,IAClD,QAAQA,EAAS,kBAAkB,SAAS;AAAA,IAC5C,MAAMA,EAAS,gBAAgB,SAAS;AAAA,IACxC,SAASA,EAAS,mBAAmB,SAAS;AAAA,IAC9C,SAASA,EAAS,mBAAmB,SAAS;AAAA,IAC9C,OAAOA,EAAS,iBAAiB,SAAS;AAAA,EAAA;AAE9C;AAEA,SAASI,IAAmC;AAC1C,SAAI,OAAO,SAAW,MAAoB,UACnC,OAAO,WAAW,8BAA8B,EAAE,UAAU,SAAS;AAC9E;AAEA,SAASC,EAAeC,GAAoC;AAC1D,MAAI,CAACA,KAAO,OAAO,SAAW,IAAa,QAAO;AAClD,MAAI;AACF,WAAO,aAAa,QAAQA,CAAG;AAAA,EACjC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAASC,EAAWD,GAAqBE,GAAqB;AAC5D,MAAI,GAACF,KAAO,OAAO,SAAW;AAC9B,QAAI;AACF,mBAAa,QAAQA,GAAKE,CAAK;AAAA,IACjC,QAAQ;AAAA,IAER;AACF;AAEO,SAASC,EAAc;AAAA,EAC5B,UAAAC;AAAA,EACA,cAAAC,IAAe;AAAA,EACf,YAAAC,IAAa;AAAA,EACb,YAAAC,IAAa;AAAA,EACb,WAAAC,IAAY;AAAA,EACZ,aAAAC;AACF,GAAuB;AAErB,QAAM,CAACP,GAAOQ,CAAa,IAAIC,EAAiB,MAC/BZ,EAAeO,CAAU,KACvBD,CAClB,GAGK,CAACO,GAAaC,CAAc,IAAIF,EAA2Bb,CAAc,GAGzEgB,IAAgBC,EAAQ,MACxBb,MAAU,WACLU,MAAgB,SAASJ,IAAYD,IAEvCL,GACN,CAACA,GAAOU,GAAaL,GAAYC,CAAS,CAAC,GAGxCQ,IAASD,EAAQ,MACjBN,IAAoBA,EAAYK,CAAa,IAC1ChC,EAAY,IAAIgC,CAAa,GACnC,CAACA,GAAeL,CAAW,CAAC,GAGzB,CAACQ,GAAQC,CAAS,IAAIP,EAAsBnB,CAAc,GAG1D2B,IAAWC,EAAY,CAACC,MAAqB;AACjD,IAAAX,EAAcW,CAAQ,GACtBpB,EAAWK,GAAYe,CAAQ;AAAA,EACjC,GAAG,CAACf,CAAU,CAAC;AAGf,EAAAgB,EAAU,MAAM;AACd,IAAI,OAAO,WAAa,QACxB,SAAS,gBAAgB,aAAa,cAAcR,CAAa,GAGjE,sBAAsB,MAAM;AAC1B,MAAAI,EAAU1B,GAAgB;AAAA,IAC5B,CAAC;AAAA,EACH,GAAG,CAACsB,CAAa,CAAC,GAGlBQ,EAAU,MAAM;AACd,QAAI,OAAO,SAAW,IAAa;AAEnC,UAAMC,IAAa,OAAO,WAAW,8BAA8B,GAC7DC,IAAe,CAACC,MAA2B;AAC/C,MAAAZ,EAAeY,EAAE,UAAU,SAAS,OAAO;AAAA,IAC7C;AAEA,WAAAF,EAAW,iBAAiB,UAAUC,CAAY,GAC3C,MAAMD,EAAW,oBAAoB,UAAUC,CAAY;AAAA,EACpE,GAAG,CAAA,CAAE,GAGLF,EAAU,MAAM;AACd,QAAI,CAAChB,KAAc,OAAO,SAAW,IAAa;AAElD,UAAMoB,IAAgB,CAACD,MAAoB;AACzC,MAAIA,EAAE,QAAQnB,KAAcmB,EAAE,YAC5Bf,EAAce,EAAE,QAAQ;AAAA,IAE5B;AAEA,kBAAO,iBAAiB,WAAWC,CAAa,GACzC,MAAM,OAAO,oBAAoB,WAAWA,CAAa;AAAA,EAClE,GAAG,CAACpB,CAAU,CAAC;AAEf,QAAMT,IAAQkB,EAA2B,OAAO;AAAA,IAC9C,OAAAb;AAAA,IACA,eAAAY;AAAA,IACA,QAAAE;AAAA,IACA,UAAAG;AAAA,IACA,QAAAF;AAAA,IACA,aAAAL;AAAA,EAAA,IACE,CAACV,GAAOY,GAAeE,GAAQG,GAAUF,GAAQL,CAAW,CAAC;AAEjE,SACE,gBAAAe,EAAC5C,EAAa,UAAb,EAAsB,OAAAc,GACpB,UAAAO,EAAA,CACH;AAEJ;AAMO,SAASwB,IAAqC;AACnD,QAAMC,IAAUC,EAAW/C,CAAY;AACvC,MAAI,CAAC8C;AACH,UAAM,IAAI,MAAM,qDAAqD;AAEvE,SAAOA;AACT;AAKO,SAASE,IAA+B;AAC7C,SAAOD,EAAW/C,CAAY,MAAM;AACtC;"}
@@ -1,120 +1,112 @@
1
- import { jsxs as z, jsx as R } from "react/jsx-runtime";
2
- import { useState as V, useMemo as P, useEffect as Y } from "react";
3
- const D = "asterui", O = 0.22, U = (t, c = O) => {
4
- const r = `rgba(0,0,0,${c})`;
5
- if (t && t.includes("var(--bc")) {
6
- if (typeof window < "u") {
7
- const a = getComputedStyle(document.documentElement).getPropertyValue("--bc").trim(), g = getComputedStyle(document.body).getPropertyValue("--bc").trim(), m = a || g;
8
- if (m)
9
- return t.replace(/var\(--bc\)/g, m);
10
- }
11
- return r;
12
- }
13
- if (t) return t;
14
- if (typeof window > "u") return r;
15
- const i = getComputedStyle(document.documentElement).getPropertyValue("--bc").trim(), d = getComputedStyle(document.body).getPropertyValue("--bc").trim(), s = i || d;
16
- return s ? `hsl(${s} / ${c})` : r;
17
- }, M = (t, c) => {
18
- const r = t?.fontSize ?? 16;
1
+ import { jsxs as R, jsx as Y } from "react/jsx-runtime";
2
+ import { useState as D, useMemo as A, useEffect as M } from "react";
3
+ import { useTheme as U } from "../hooks/useTheme.js";
4
+ const B = "asterui", X = 0.22;
5
+ function _(r, l) {
6
+ const n = Math.round(l * 255).toString(16).padStart(2, "0");
7
+ return r + n;
8
+ }
9
+ const K = (r, l) => {
10
+ const n = r?.fontSize ?? 16;
19
11
  return {
20
- color: c,
21
- fontSize: r,
22
- fontWeight: t?.fontWeight ?? 600,
23
- fontStyle: t?.fontStyle ?? "normal",
24
- fontFamily: t?.fontFamily ?? "sans-serif",
25
- lineHeight: t?.lineHeight ?? r * 1.2
12
+ color: l,
13
+ fontSize: n,
14
+ fontWeight: r?.fontWeight ?? 600,
15
+ fontStyle: r?.fontStyle ?? "normal",
16
+ fontFamily: r?.fontFamily ?? "sans-serif",
17
+ lineHeight: r?.lineHeight ?? n * 1.2
26
18
  };
27
- }, X = ({
28
- children: t,
29
- className: c = "",
30
- style: r,
31
- content: i,
32
- image: d,
33
- width: s = 120,
34
- height: a = 64,
35
- gap: g,
36
- offset: m,
37
- rotate: T = -22,
38
- zIndex: W = 1e3,
39
- font: S,
40
- ...A
19
+ }, q = ({
20
+ children: r,
21
+ className: l = "",
22
+ style: n,
23
+ content: c,
24
+ image: v,
25
+ width: a = 120,
26
+ height: i = 64,
27
+ gap: E,
28
+ offset: k,
29
+ rotate: F = -22,
30
+ zIndex: I = 1e3,
31
+ font: y,
32
+ ...b
41
33
  }) => {
42
- const [y, $] = V(null), u = g?.[0] ?? 120, f = g?.[1] ?? 120, B = m?.[0] ?? u / 2, F = m?.[1] ?? f / 2, E = P(
43
- () => typeof i == "string" ? [i] : Array.isArray(i) ? i : [D],
44
- [i]
45
- ), k = U(S?.color), l = P(
46
- () => M(S, k),
47
- [S, k]
48
- ), h = Math.PI / 180 * T, I = E.join("|");
49
- Y(() => {
34
+ const [m, L] = D(null), d = E?.[0] ?? 120, f = E?.[1] ?? 120, H = k?.[0] ?? d / 2, N = k?.[1] ?? f / 2, { colors: P } = U(), h = A(
35
+ () => typeof c == "string" ? [c] : Array.isArray(c) ? c : [B],
36
+ [c]
37
+ ), W = y?.color ?? _(P.foreground, X), s = A(
38
+ () => K(y, W),
39
+ [y, W]
40
+ ), S = Math.PI / 180 * F, j = h.join("|");
41
+ M(() => {
50
42
  if (typeof window > "u") return;
51
- let p = !1;
52
- const n = window.devicePixelRatio || 1, w = s + u, C = a + f, v = document.createElement("canvas");
53
- v.width = w * n, v.height = C * n;
54
- const e = v.getContext("2d");
43
+ let u = !1;
44
+ const o = window.devicePixelRatio || 1, $ = a + d, T = i + f, g = document.createElement("canvas");
45
+ g.width = $ * o, g.height = T * o;
46
+ const e = g.getContext("2d");
55
47
  if (!e) return;
56
- const L = () => {
57
- const o = v.toDataURL();
58
- p || $({ url: o, width: w, height: C });
59
- }, N = () => {
60
- e.save(), e.translate((u / 2 + s / 2) * n, (f / 2 + a / 2) * n), e.rotate(h), e.fillStyle = l.color, e.textAlign = "center", e.textBaseline = "middle", e.font = `${l.fontStyle} normal ${l.fontWeight} ${l.fontSize * n}px ${l.fontFamily}`;
61
- const o = l.lineHeight * n, b = -((E.length - 1) * o) / 2;
62
- E.forEach((x, j) => {
63
- e.fillText(x, 0, b + j * o);
48
+ const w = () => {
49
+ const t = g.toDataURL();
50
+ u || L({ url: t, width: $, height: T });
51
+ }, C = () => {
52
+ e.save(), e.translate((d / 2 + a / 2) * o, (f / 2 + i / 2) * o), e.rotate(S), e.fillStyle = s.color, e.textAlign = "center", e.textBaseline = "middle", e.font = `${s.fontStyle} normal ${s.fontWeight} ${s.fontSize * o}px ${s.fontFamily}`;
53
+ const t = s.lineHeight * o, p = -((h.length - 1) * t) / 2;
54
+ h.forEach((x, O) => {
55
+ e.fillText(x, 0, p + O * t);
64
56
  }), e.restore();
65
57
  };
66
- if (d) {
67
- const o = new Image();
68
- o.crossOrigin = "anonymous", o.referrerPolicy = "no-referrer";
69
- const b = () => {
70
- e.save(), e.translate((u / 2 + s / 2) * n, (f / 2 + a / 2) * n), e.rotate(h), e.drawImage(
71
- o,
72
- -s / 2 * n,
73
- -a / 2 * n,
74
- s * n,
75
- a * n
76
- ), e.restore(), L();
58
+ if (v) {
59
+ const t = new Image();
60
+ t.crossOrigin = "anonymous", t.referrerPolicy = "no-referrer";
61
+ const p = () => {
62
+ e.save(), e.translate((d / 2 + a / 2) * o, (f / 2 + i / 2) * o), e.rotate(S), e.drawImage(
63
+ t,
64
+ -a / 2 * o,
65
+ -i / 2 * o,
66
+ a * o,
67
+ i * o
68
+ ), e.restore(), w();
77
69
  }, x = () => {
78
- p || $(null);
70
+ u || L(null);
79
71
  };
80
- return o.addEventListener("load", b), o.addEventListener("error", x), o.src = d, () => {
81
- p = !0, o.removeEventListener("load", b), o.removeEventListener("error", x);
72
+ return t.addEventListener("load", p), t.addEventListener("error", x), t.src = v, () => {
73
+ u = !0, t.removeEventListener("load", p), t.removeEventListener("error", x);
82
74
  };
83
75
  } else
84
- N(), L();
76
+ C(), w();
85
77
  return () => {
86
- p = !0;
78
+ u = !0;
87
79
  };
88
80
  }, [
89
- l,
90
- u,
91
- f,
92
- a,
81
+ s,
93
82
  d,
94
- h,
95
- I,
96
- s
83
+ f,
84
+ i,
85
+ v,
86
+ S,
87
+ j,
88
+ a
97
89
  ]);
98
- const H = ["relative", c].filter(Boolean).join(" ");
99
- return /* @__PURE__ */ z(
90
+ const z = ["relative", l].filter(Boolean).join(" ");
91
+ return /* @__PURE__ */ R(
100
92
  "div",
101
93
  {
102
- className: H,
103
- style: { position: r?.position ?? "relative", ...r },
104
- ...A,
94
+ className: z,
95
+ style: { position: n?.position ?? "relative", ...n },
96
+ ...b,
105
97
  children: [
106
- t,
107
- y && /* @__PURE__ */ R(
98
+ r,
99
+ m && /* @__PURE__ */ Y(
108
100
  "div",
109
101
  {
110
102
  "aria-hidden": !0,
111
103
  className: "pointer-events-none absolute inset-0",
112
104
  style: {
113
- zIndex: W,
114
- backgroundImage: `url(${y.url})`,
105
+ zIndex: I,
106
+ backgroundImage: `url(${m.url})`,
115
107
  backgroundRepeat: "repeat",
116
- backgroundSize: `${y.width}px ${y.height}px`,
117
- backgroundPosition: `${B}px ${F}px`
108
+ backgroundSize: `${m.width}px ${m.height}px`,
109
+ backgroundPosition: `${H}px ${N}px`
118
110
  }
119
111
  }
120
112
  )
@@ -122,8 +114,8 @@ const D = "asterui", O = 0.22, U = (t, c = O) => {
122
114
  }
123
115
  );
124
116
  };
125
- X.displayName = "Watermark";
117
+ q.displayName = "Watermark";
126
118
  export {
127
- X as Watermark
119
+ q as Watermark
128
120
  };
129
121
  //# sourceMappingURL=Watermark.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Watermark.js","sources":["../../src/components/Watermark.tsx"],"sourcesContent":["import React, { useEffect, useMemo, useState } from 'react'\n\nexport type WatermarkGap = [number, number]\nexport type WatermarkOffset = [number, number]\n\nexport interface WatermarkFontOptions {\n /** Text color for watermark content */\n color?: string\n /** Font size in pixels */\n fontSize?: number\n /** Font weight for watermark text */\n fontWeight?: number | 'normal' | 'bold' | 'bolder' | 'lighter'\n /** Font style for watermark text */\n fontStyle?: 'normal' | 'italic' | 'oblique'\n /** Font family for watermark text */\n fontFamily?: string\n /** Line height in pixels for multi-line content */\n lineHeight?: number\n}\n\nexport interface WatermarkProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, 'content'> {\n /** Text to render inside the watermark; falls back to \"asterui\" */\n content?: string | string[]\n /** Image source (URL or base64) to render instead of text */\n image?: string\n /** Width of a single watermark tile in pixels */\n width?: number\n /** Height of a single watermark tile in pixels */\n height?: number\n /** Horizontal/vertical gap between watermarks in pixels */\n gap?: WatermarkGap\n /** Offset for the first watermark tile from the top-left corner */\n offset?: WatermarkOffset\n /** Rotation angle in degrees */\n rotate?: number\n /** z-index for the overlay layer */\n zIndex?: number\n /** Font settings for text watermarks */\n font?: WatermarkFontOptions\n /** Content to protect with the watermark */\n children?: React.ReactNode\n}\n\ntype WatermarkImage = {\n url: string\n width: number\n height: number\n}\n\nconst DEFAULT_CONTENT = 'asterui'\nconst DEFAULT_OPACITY = 0.22\n\nconst resolveThemeColor = (requested?: string, opacity = DEFAULT_OPACITY) => {\n const fallback = `rgba(0,0,0,${opacity})`\n\n if (requested && requested.includes('var(--bc')) {\n if (typeof window !== 'undefined') {\n const docBase = getComputedStyle(document.documentElement).getPropertyValue('--bc').trim()\n const bodyBase = getComputedStyle(document.body).getPropertyValue('--bc').trim()\n const base = docBase || bodyBase\n if (base) {\n return requested.replace(/var\\(--bc\\)/g, base)\n }\n }\n return fallback\n }\n\n if (requested) return requested\n\n if (typeof window === 'undefined') return fallback\n\n const docBase = getComputedStyle(document.documentElement).getPropertyValue('--bc').trim()\n const bodyBase = getComputedStyle(document.body).getPropertyValue('--bc').trim()\n const base = docBase || bodyBase\n\n return base ? `hsl(${base} / ${opacity})` : fallback\n}\n\nconst getFontSettings = (font: WatermarkFontOptions | undefined, resolvedColor: string) => {\n const fontSize = font?.fontSize ?? 16\n\n return {\n color: resolvedColor,\n fontSize,\n fontWeight: font?.fontWeight ?? 600,\n fontStyle: font?.fontStyle ?? 'normal',\n fontFamily: font?.fontFamily ?? 'sans-serif',\n lineHeight: font?.lineHeight ?? fontSize * 1.2,\n }\n}\n\nexport const Watermark: React.FC<WatermarkProps> = ({\n children,\n className = '',\n style,\n content,\n image,\n width = 120,\n height = 64,\n gap,\n offset,\n rotate = -22,\n zIndex = 1000,\n font,\n ...rest\n}) => {\n const [watermark, setWatermark] = useState<WatermarkImage | null>(null)\n\n const gapX = gap?.[0] ?? 120\n const gapY = gap?.[1] ?? 120\n const offsetX = offset?.[0] ?? gapX / 2\n const offsetY = offset?.[1] ?? gapY / 2\n const textLines = useMemo(\n () =>\n typeof content === 'string'\n ? [content]\n : Array.isArray(content)\n ? content\n : [DEFAULT_CONTENT],\n [content]\n )\n const resolvedColor = resolveThemeColor(font?.color)\n const fontSettings = useMemo(\n () => getFontSettings(font, resolvedColor),\n [font, resolvedColor]\n )\n const rotationInRadians = (Math.PI / 180) * rotate\n const textKey = textLines.join('|')\n\n useEffect(() => {\n if (typeof window === 'undefined') return\n\n let cancelled = false\n const ratio = window.devicePixelRatio || 1\n const tileWidth = width + gapX\n const tileHeight = height + gapY\n const canvas = document.createElement('canvas')\n canvas.width = tileWidth * ratio\n canvas.height = tileHeight * ratio\n const ctx = canvas.getContext('2d')\n\n if (!ctx) return\n\n const commitWatermark = () => {\n const url = canvas.toDataURL()\n if (!cancelled) {\n setWatermark({ url, width: tileWidth, height: tileHeight })\n }\n }\n\n const drawText = () => {\n ctx.save()\n ctx.translate((gapX / 2 + width / 2) * ratio, (gapY / 2 + height / 2) * ratio)\n ctx.rotate(rotationInRadians)\n ctx.fillStyle = fontSettings.color\n ctx.textAlign = 'center'\n ctx.textBaseline = 'middle'\n ctx.font = `${fontSettings.fontStyle} normal ${fontSettings.fontWeight} ${fontSettings.fontSize * ratio}px ${fontSettings.fontFamily}`\n\n const lineHeight = fontSettings.lineHeight * ratio\n const startY = -((textLines.length - 1) * lineHeight) / 2\n\n textLines.forEach((line, index) => {\n ctx.fillText(line, 0, startY + index * lineHeight)\n })\n\n ctx.restore()\n }\n\n if (image) {\n const img = new Image()\n img.crossOrigin = 'anonymous'\n img.referrerPolicy = 'no-referrer'\n\n const handleLoad = () => {\n ctx.save()\n ctx.translate((gapX / 2 + width / 2) * ratio, (gapY / 2 + height / 2) * ratio)\n ctx.rotate(rotationInRadians)\n ctx.drawImage(\n img,\n (-width / 2) * ratio,\n (-height / 2) * ratio,\n width * ratio,\n height * ratio\n )\n ctx.restore()\n commitWatermark()\n }\n\n const handleError = () => {\n if (!cancelled) setWatermark(null)\n }\n\n img.addEventListener('load', handleLoad)\n img.addEventListener('error', handleError)\n img.src = image\n\n return () => {\n cancelled = true\n img.removeEventListener('load', handleLoad)\n img.removeEventListener('error', handleError)\n }\n } else {\n drawText()\n commitWatermark()\n }\n\n return () => {\n cancelled = true\n }\n }, [\n fontSettings,\n gapX,\n gapY,\n height,\n image,\n rotationInRadians,\n textKey,\n width,\n ])\n\n const classes = ['relative', className].filter(Boolean).join(' ')\n\n return (\n <div\n className={classes}\n style={{ position: style?.position ?? 'relative', ...style }}\n {...rest}\n >\n {children}\n {watermark && (\n <div\n aria-hidden\n className=\"pointer-events-none absolute inset-0\"\n style={{\n zIndex,\n backgroundImage: `url(${watermark.url})`,\n backgroundRepeat: 'repeat',\n backgroundSize: `${watermark.width}px ${watermark.height}px`,\n backgroundPosition: `${offsetX}px ${offsetY}px`,\n }}\n />\n )}\n </div>\n )\n}\n\nWatermark.displayName = 'Watermark'\n"],"names":["DEFAULT_CONTENT","DEFAULT_OPACITY","resolveThemeColor","requested","opacity","fallback","docBase","bodyBase","base","getFontSettings","font","resolvedColor","fontSize","Watermark","children","className","style","content","image","width","height","gap","offset","rotate","zIndex","rest","watermark","setWatermark","useState","gapX","gapY","offsetX","offsetY","textLines","useMemo","fontSettings","rotationInRadians","textKey","useEffect","cancelled","ratio","tileWidth","tileHeight","canvas","ctx","commitWatermark","url","drawText","lineHeight","startY","line","index","img","handleLoad","handleError","classes","jsxs","jsx"],"mappings":"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"}
1
+ {"version":3,"file":"Watermark.js","sources":["../../src/components/Watermark.tsx"],"sourcesContent":["import React, { useEffect, useMemo, useState } from 'react'\nimport { useTheme } from '../hooks/useTheme'\n\nexport type WatermarkGap = [number, number]\nexport type WatermarkOffset = [number, number]\n\nexport interface WatermarkFontOptions {\n /** Text color for watermark content */\n color?: string\n /** Font size in pixels */\n fontSize?: number\n /** Font weight for watermark text */\n fontWeight?: number | 'normal' | 'bold' | 'bolder' | 'lighter'\n /** Font style for watermark text */\n fontStyle?: 'normal' | 'italic' | 'oblique'\n /** Font family for watermark text */\n fontFamily?: string\n /** Line height in pixels for multi-line content */\n lineHeight?: number\n}\n\nexport interface WatermarkProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, 'content'> {\n /** Text to render inside the watermark; falls back to \"asterui\" */\n content?: string | string[]\n /** Image source (URL or base64) to render instead of text */\n image?: string\n /** Width of a single watermark tile in pixels */\n width?: number\n /** Height of a single watermark tile in pixels */\n height?: number\n /** Horizontal/vertical gap between watermarks in pixels */\n gap?: WatermarkGap\n /** Offset for the first watermark tile from the top-left corner */\n offset?: WatermarkOffset\n /** Rotation angle in degrees */\n rotate?: number\n /** z-index for the overlay layer */\n zIndex?: number\n /** Font settings for text watermarks */\n font?: WatermarkFontOptions\n /** Content to protect with the watermark */\n children?: React.ReactNode\n}\n\ntype WatermarkImage = {\n url: string\n width: number\n height: number\n}\n\nconst DEFAULT_CONTENT = 'asterui'\nconst DEFAULT_OPACITY = 0.22\n\n// Add opacity to a hex color (returns #rrggbbaa format)\nfunction hexWithOpacity(hex: string, opacity: number): string {\n const alpha = Math.round(opacity * 255).toString(16).padStart(2, '0')\n return hex + alpha\n}\n\nconst getFontSettings = (font: WatermarkFontOptions | undefined, resolvedColor: string) => {\n const fontSize = font?.fontSize ?? 16\n\n return {\n color: resolvedColor,\n fontSize,\n fontWeight: font?.fontWeight ?? 600,\n fontStyle: font?.fontStyle ?? 'normal',\n fontFamily: font?.fontFamily ?? 'sans-serif',\n lineHeight: font?.lineHeight ?? fontSize * 1.2,\n }\n}\n\nexport const Watermark: React.FC<WatermarkProps> = ({\n children,\n className = '',\n style,\n content,\n image,\n width = 120,\n height = 64,\n gap,\n offset,\n rotate = -22,\n zIndex = 1000,\n font,\n ...rest\n}) => {\n const [watermark, setWatermark] = useState<WatermarkImage | null>(null)\n\n const gapX = gap?.[0] ?? 120\n const gapY = gap?.[1] ?? 120\n const offsetX = offset?.[0] ?? gapX / 2\n const offsetY = offset?.[1] ?? gapY / 2\n const { colors } = useTheme()\n const textLines = useMemo(\n () =>\n typeof content === 'string'\n ? [content]\n : Array.isArray(content)\n ? content\n : [DEFAULT_CONTENT],\n [content]\n )\n // Use provided color or theme foreground with opacity\n const resolvedColor = font?.color ?? hexWithOpacity(colors.foreground, DEFAULT_OPACITY)\n const fontSettings = useMemo(\n () => getFontSettings(font, resolvedColor),\n [font, resolvedColor]\n )\n const rotationInRadians = (Math.PI / 180) * rotate\n const textKey = textLines.join('|')\n\n useEffect(() => {\n if (typeof window === 'undefined') return\n\n let cancelled = false\n const ratio = window.devicePixelRatio || 1\n const tileWidth = width + gapX\n const tileHeight = height + gapY\n const canvas = document.createElement('canvas')\n canvas.width = tileWidth * ratio\n canvas.height = tileHeight * ratio\n const ctx = canvas.getContext('2d')\n\n if (!ctx) return\n\n const commitWatermark = () => {\n const url = canvas.toDataURL()\n if (!cancelled) {\n setWatermark({ url, width: tileWidth, height: tileHeight })\n }\n }\n\n const drawText = () => {\n ctx.save()\n ctx.translate((gapX / 2 + width / 2) * ratio, (gapY / 2 + height / 2) * ratio)\n ctx.rotate(rotationInRadians)\n ctx.fillStyle = fontSettings.color\n ctx.textAlign = 'center'\n ctx.textBaseline = 'middle'\n ctx.font = `${fontSettings.fontStyle} normal ${fontSettings.fontWeight} ${fontSettings.fontSize * ratio}px ${fontSettings.fontFamily}`\n\n const lineHeight = fontSettings.lineHeight * ratio\n const startY = -((textLines.length - 1) * lineHeight) / 2\n\n textLines.forEach((line, index) => {\n ctx.fillText(line, 0, startY + index * lineHeight)\n })\n\n ctx.restore()\n }\n\n if (image) {\n const img = new Image()\n img.crossOrigin = 'anonymous'\n img.referrerPolicy = 'no-referrer'\n\n const handleLoad = () => {\n ctx.save()\n ctx.translate((gapX / 2 + width / 2) * ratio, (gapY / 2 + height / 2) * ratio)\n ctx.rotate(rotationInRadians)\n ctx.drawImage(\n img,\n (-width / 2) * ratio,\n (-height / 2) * ratio,\n width * ratio,\n height * ratio\n )\n ctx.restore()\n commitWatermark()\n }\n\n const handleError = () => {\n if (!cancelled) setWatermark(null)\n }\n\n img.addEventListener('load', handleLoad)\n img.addEventListener('error', handleError)\n img.src = image\n\n return () => {\n cancelled = true\n img.removeEventListener('load', handleLoad)\n img.removeEventListener('error', handleError)\n }\n } else {\n drawText()\n commitWatermark()\n }\n\n return () => {\n cancelled = true\n }\n }, [\n fontSettings,\n gapX,\n gapY,\n height,\n image,\n rotationInRadians,\n textKey,\n width,\n ])\n\n const classes = ['relative', className].filter(Boolean).join(' ')\n\n return (\n <div\n className={classes}\n style={{ position: style?.position ?? 'relative', ...style }}\n {...rest}\n >\n {children}\n {watermark && (\n <div\n aria-hidden\n className=\"pointer-events-none absolute inset-0\"\n style={{\n zIndex,\n backgroundImage: `url(${watermark.url})`,\n backgroundRepeat: 'repeat',\n backgroundSize: `${watermark.width}px ${watermark.height}px`,\n backgroundPosition: `${offsetX}px ${offsetY}px`,\n }}\n />\n )}\n </div>\n )\n}\n\nWatermark.displayName = 'Watermark'\n"],"names":["DEFAULT_CONTENT","DEFAULT_OPACITY","hexWithOpacity","hex","opacity","alpha","getFontSettings","font","resolvedColor","fontSize","Watermark","children","className","style","content","image","width","height","gap","offset","rotate","zIndex","rest","watermark","setWatermark","useState","gapX","gapY","offsetX","offsetY","colors","useTheme","textLines","useMemo","fontSettings","rotationInRadians","textKey","useEffect","cancelled","ratio","tileWidth","tileHeight","canvas","ctx","commitWatermark","url","drawText","lineHeight","startY","line","index","img","handleLoad","handleError","classes","jsxs","jsx"],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import { default as React } from 'react';
2
+ export type Size = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
3
+ export declare const SizeContext: React.Context<Size | undefined>;
4
+ export interface SizeProviderProps {
5
+ /** Size to provide to child components */
6
+ size: Size;
7
+ /** Child components */
8
+ children: React.ReactNode;
9
+ }
10
+ /**
11
+ * Provides size context to child components.
12
+ *
13
+ * Used by Button, CopyButton, and other sized components to transmit
14
+ * their size to child icons, allowing icons to automatically match
15
+ * the parent component's size.
16
+ */
17
+ export declare function SizeProvider({ size, children }: SizeProviderProps): import("react/jsx-runtime").JSX.Element;
18
+ /**
19
+ * Hook to access the current size context.
20
+ * Returns undefined if not within a SizeProvider.
21
+ */
22
+ export declare function useSize(): Size | undefined;