UNPKG

lightswind

Version:

A collection of beautifully crafted React Components, Blocks & Templates for Modern Developers. Create stunning web applications effortlessly by using our 160+ professional and animated react components.

359 lines 15.7 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TooltipProvider = exports.TooltipContent = exports.Tooltips = exports.TooltipTrigger = exports.Tooltip = void 0; const jsx_runtime_1 = require("react/jsx-runtime"); const React = __importStar(require("react")); const utils_1 = require("@/components/lib/utils"); // Assuming utils.ts is in lib/ const framer_motion_1 = require("framer-motion"); const react_dom_1 = __importDefault(require("react-dom")); const TooltipContext = React.createContext(undefined); // --- Tooltip Component --- const Tooltip = ({ children, content, defaultOpen = false, open: controlledOpen, onOpenChange, delayDuration = 300, hideDelay = 100, side = "top", align = "center", sideOffset = 8, variant = "default", hideArrow = false, maxWidth = "20rem", asChild = false, disabled = false, }) => { // Manage uncontrolled open state const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen); // Determine if the component is controlled or uncontrolled const isControlled = controlledOpen !== undefined; const open = isControlled ? controlledOpen : uncontrolledOpen; // Ref for the trigger element, passed via context to TooltipContentDisplay const triggerRef = React.useRef(null); // Function to update the open state, handling both controlled and uncontrolled modes const setOpen = React.useCallback((value) => { if (!isControlled) { setUncontrolledOpen(value); } if (onOpenChange) { // Calculate the new value if a function is passed const newValue = typeof value === "function" ? value(open) : value; onOpenChange(newValue); } }, [isControlled, onOpenChange, open]); // Refs for managing show/hide timeouts const showTimeoutRef = React.useRef(null); const hideTimeoutRef = React.useRef(null); // Memoize configuration object to prevent unnecessary re-renders const config = React.useMemo(() => ({ side, align, sideOffset, variant, hideArrow, maxWidth, }), [side, align, sideOffset, variant, hideArrow, maxWidth]); // Clean up any pending timeouts on component unmount React.useEffect(() => { return () => { if (showTimeoutRef.current) clearTimeout(showTimeoutRef.current); if (hideTimeoutRef.current) clearTimeout(hideTimeoutRef.current); }; }, []); const handleMouseEnter = React.useCallback(() => { if (disabled) return; // Always clear both timers to prevent race conditions if (hideTimeoutRef.current) { clearTimeout(hideTimeoutRef.current); hideTimeoutRef.current = null; } if (showTimeoutRef.current) { clearTimeout(showTimeoutRef.current); showTimeoutRef.current = null; } showTimeoutRef.current = setTimeout(() => setOpen(true), delayDuration); }, [disabled, setOpen, delayDuration]); const handleMouseLeave = React.useCallback(() => { if (disabled) return; // Always clear both timers to prevent race conditions if (showTimeoutRef.current) { clearTimeout(showTimeoutRef.current); showTimeoutRef.current = null; } if (hideTimeoutRef.current) { clearTimeout(hideTimeoutRef.current); hideTimeoutRef.current = null; } hideTimeoutRef.current = setTimeout(() => setOpen(false), hideDelay); }, [disabled, setOpen, hideDelay]); // Memoize the context value to prevent unnecessary re-renders of consumers const contextValue = React.useMemo(() => ({ open, setOpen, content, config, triggerRef, showTimeoutRef, hideTimeoutRef, handleMouseEnter, handleMouseLeave, }), [open, setOpen, content, config, triggerRef, handleMouseEnter, handleMouseLeave]); return ((0, jsx_runtime_1.jsxs)(TooltipContext.Provider, { value: contextValue, children: [disabled ? children : ((0, jsx_runtime_1.jsx)(TooltipTrigger, { asChild: asChild, triggerRef: triggerRef, children: children })), (0, jsx_runtime_1.jsx)(TooltipContentDisplay, {})] })); }; exports.Tooltip = Tooltip; // --- TooltipTrigger Component --- const TooltipTrigger = React.forwardRef(({ children, asChild = false, triggerRef }, ref) => { const context = React.useContext(TooltipContext); if (!context) { throw new Error("TooltipTrigger must be used within a Tooltip"); } // Combined ref to set both the forwarded ref and the internal triggerRef const combinedRef = React.useCallback((node) => { if (typeof ref === 'function') { ref(node); } else if (ref) { ref.current = node; } if (triggerRef) { triggerRef.current = node; } }, [ref, triggerRef]); // Props to be applied to the trigger element const triggerProps = { ref: combinedRef, // Use the combined ref onMouseEnter: context.handleMouseEnter, onMouseLeave: context.handleMouseLeave, onFocus: () => context.setOpen(true), onBlur: () => context.setOpen(false), }; // If asChild is true, clone props onto the child element if (asChild && React.isValidElement(children)) { return React.cloneElement(children, triggerProps); } // Default rendering if asChild is false return ((0, jsx_runtime_1.jsx)("div", { className: "inline-block relative" // Keeps the trigger as a block for correct positioning , ...triggerProps, children: children })); }); exports.TooltipTrigger = TooltipTrigger; TooltipTrigger.displayName = "TooltipTrigger"; // --- TooltipContentDisplay Component --- // This component renders the actual tooltip content and handles its positioning. const TooltipContentDisplay = () => { const context = React.useContext(TooltipContext); if (!context) { throw new Error("TooltipContentDisplay must be used within a Tooltip"); } const { open, content, config, triggerRef } = context; const [position, setPosition] = React.useState({ x: 0, y: 0 }); const contentRef = React.useRef(null); const [mounted, setMounted] = React.useState(false); React.useEffect(() => { setMounted(true); }, []); const getAnimationVariants = React.useCallback(() => { const { side } = config; const distance = 8; return { hidden: { opacity: 0, scale: 0.8, x: side === "left" ? distance : side === "right" ? -distance : 0, y: side === "top" ? distance : side === "bottom" ? -distance : 0, }, visible: { opacity: 1, scale: 1, x: 0, y: 0, transition: { type: "spring", damping: 20, stiffness: 400, }, }, exit: { opacity: 0, scale: 0.8, transition: { duration: 0.15, ease: "easeIn" }, pointerEvents: "none", }, }; }, [config]); const updatePosition = React.useCallback(() => { if (!contentRef.current || !triggerRef.current) return; const triggerRect = triggerRef.current.getBoundingClientRect(); const contentRect = contentRef.current.getBoundingClientRect(); let x = 0; let y = 0; const { side, align, sideOffset } = config; switch (side) { case "top": y = triggerRect.top - contentRect.height - sideOffset; break; case "bottom": y = triggerRect.bottom + sideOffset; break; case "left": x = triggerRect.left - contentRect.width - sideOffset; break; case "right": x = triggerRect.right + sideOffset; break; } if (side === "top" || side === "bottom") { switch (align) { case "start": x = triggerRect.left; break; case "end": x = triggerRect.right - contentRect.width; break; default: x = triggerRect.left + triggerRect.width / 2 - contentRect.width / 2; break; } } else { switch (align) { case "start": y = triggerRect.top; break; case "end": y = triggerRect.bottom - contentRect.height; break; default: y = triggerRect.top + triggerRect.height / 2 - contentRect.height / 2; break; } } const padding = 8; if (x < padding) x = padding; if (x + contentRect.width > window.innerWidth - padding) x = window.innerWidth - contentRect.width - padding; if (y < padding) y = padding; if (y + contentRect.height > window.innerHeight - padding) y = window.innerHeight - contentRect.height - padding; setPosition({ x, y }); }, [config, triggerRef]); React.useEffect(() => { if (open) { const id = requestAnimationFrame(updatePosition); window.addEventListener("resize", updatePosition); window.addEventListener("scroll", updatePosition, true); return () => { cancelAnimationFrame(id); window.removeEventListener("resize", updatePosition); window.removeEventListener("scroll", updatePosition, true); }; } }, [open, updatePosition]); const getArrowStyle = React.useCallback(() => { const { side, align } = config; const arrowSize = 8; let style = { position: "absolute", width: arrowSize, height: arrowSize, transform: "rotate(45deg)", zIndex: -1, }; switch (side) { case "top": style.bottom = -arrowSize / 2; style.left = align === "center" ? "50%" : align === "start" ? "15%" : undefined; if (align === "end") style.right = "15%"; if (align === "center") style.transform = "translateX(-50%) rotate(45deg)"; break; case "bottom": style.top = -arrowSize / 2; style.left = align === "center" ? "50%" : align === "start" ? "15%" : undefined; if (align === "end") style.right = "15%"; if (align === "center") style.transform = "translateX(-50%) rotate(45deg)"; break; case "left": style.right = -arrowSize / 2; style.top = align === "center" ? "50%" : align === "start" ? "15%" : undefined; if (align === "end") style.bottom = "15%"; if (align === "center") style.transform = "translateY(-50%) rotate(45deg)"; break; case "right": style.left = -arrowSize / 2; style.top = align === "center" ? "50%" : align === "start" ? "15%" : undefined; if (align === "end") style.bottom = "15%"; if (align === "center") style.transform = "translateY(-50%) rotate(45deg)"; break; } return style; }, [config]); const getVariantClasses = React.useCallback(() => { const { variant } = config; switch (variant) { case "info": return "bg-primarylw text-white border-[color-mix(in_srgb,var(--primarylw)_20%,transparent)]"; case "success": return "bg-emerald-600 text-white border-emerald-400/20"; case "warning": return "bg-amber-500 text-black border-amber-400/20"; case "error": return "bg-rose-600 text-white border-rose-400/20"; default: return "bg-popover/90 text-popover-foreground border border-gray-200 dark:border-gray-800 backdrop-blur-md shadow-xl"; } }, [config]); if (!open || !mounted) return null; return react_dom_1.default.createPortal((0, jsx_runtime_1.jsx)(framer_motion_1.AnimatePresence, { children: (0, jsx_runtime_1.jsxs)(framer_motion_1.motion.div, { ref: contentRef, onMouseEnter: context.handleMouseEnter, onMouseLeave: context.handleMouseLeave, style: { position: "fixed", top: position.y, left: position.x, maxWidth: config.maxWidth, zIndex: 9999, }, initial: "hidden", animate: "visible", exit: "exit", variants: getAnimationVariants(), className: (0, utils_1.cn)("rounded-md px-3 py-1.5 text-xs font-medium", getVariantClasses()), children: [!config.hideArrow && ((0, jsx_runtime_1.jsx)("div", { className: (0, utils_1.cn)("absolute w-2 h-2", getVariantClasses(), "border-0"), style: getArrowStyle() })), content] }) }), document.body); }; // For legacy compatibility (if needed, otherwise remove) exports.Tooltips = Tooltip; // Deprecated TooltipContent, kept for backward compatibility with a warning exports.TooltipContent = React.forwardRef((props, _ref) => { console.warn("TooltipContent is deprecated. Use the Tooltip component with content prop instead."); return (0, jsx_runtime_1.jsx)("div", { ...props }); }); exports.TooltipContent.displayName = "TooltipContent"; // Backward compatibility for TooltipProvider (if needed, otherwise remove) const TooltipProvider = ({ children }) => { return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: children }); }; exports.TooltipProvider = TooltipProvider; //# sourceMappingURL=tooltip.js.map