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.

365 lines 17.5 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.DropdownMenuGroup = exports.DropdownMenuSeparator = exports.DropdownMenuItem = exports.DropdownMenuLabel = exports.DropdownMenuContent = exports.DropdownMenuTrigger = exports.DropdownMenu = void 0; const jsx_runtime_1 = require("react/jsx-runtime"); const React = __importStar(require("react")); const react_dom_1 = __importDefault(require("react-dom")); const utils_1 = require("@/components/lib/utils"); const class_variance_authority_1 = require("class-variance-authority"); const framer_motion_1 = require("framer-motion"); const DropdownMenuContext = React.createContext(undefined); const DropdownMenu = ({ children, defaultOpen = false, open: controlledOpen, onOpenChange, hoverMode = false, }) => { const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen); const triggerRef = React.useRef(null); const isControlled = controlledOpen !== undefined; const open = isControlled ? controlledOpen : uncontrolledOpen; const setOpen = React.useCallback((value) => { if (!isControlled) { setUncontrolledOpen(value); } else if (onOpenChange) { const nextOpen = typeof value === "function" ? value(controlledOpen ?? false) : value; onOpenChange(nextOpen); } }, [isControlled, onOpenChange, controlledOpen]); // Notify parent of uncontrolled state changes — outside state updaters to avoid Strict Mode issues React.useEffect(() => { if (!isControlled && onOpenChange) { onOpenChange(uncontrolledOpen); } }, [uncontrolledOpen, isControlled, onOpenChange]); const timeoutRef = React.useRef(null); React.useEffect(() => { return () => { if (timeoutRef.current) clearTimeout(timeoutRef.current); }; }, []); return ((0, jsx_runtime_1.jsx)(DropdownMenuContext.Provider, { value: { open: open || false, setOpen, hoverMode, triggerRef, timeoutRef }, children: children })); }; exports.DropdownMenu = DropdownMenu; const DropdownMenuTrigger = React.forwardRef(({ children, asChild, ...props }, ref) => { const context = React.useContext(DropdownMenuContext); if (!context) throw new Error("DropdownMenuTrigger must be used within a DropdownMenu"); const { setOpen, hoverMode, triggerRef, timeoutRef } = context; const handleClick = (e) => { e.stopPropagation(); if (hoverMode) { // In hover mode, click should only open (not toggle), to avoid // fighting with the hover timers on first interaction if (timeoutRef.current) clearTimeout(timeoutRef.current); setOpen(true); } else { setOpen((prev) => !prev); } if (props.onClick) props.onClick(e); }; React.useImperativeHandle(ref, () => { if (!triggerRef.current) return document.createElement("button"); return triggerRef.current; }, [triggerRef]); const handleMouseEnter = (e) => { if (hoverMode) { // Always clear any pending timer (open or close) if (timeoutRef.current) clearTimeout(timeoutRef.current); // Schedule open — setOpen(true) is idempotent if already open timeoutRef.current = setTimeout(() => setOpen(true), 150); } if (props.onMouseEnter) props.onMouseEnter(e); }; const handleMouseLeaveTrigger = (e) => { if (hoverMode) { if (timeoutRef.current) clearTimeout(timeoutRef.current); timeoutRef.current = setTimeout(() => setOpen(false), 200); } if (props.onMouseLeave) props.onMouseLeave(e); }; const { onClick, onMouseEnter, onMouseLeave, ...otherProps } = props; if (asChild) { const child = React.Children.only(children); return React.cloneElement(child, { ...child.props, ref: (node) => { triggerRef.current = node; if (typeof ref === "function") ref(node); else if (ref) ref.current = node; // Handle child's original ref const childRef = child.ref; if (childRef) { if (typeof childRef === "function") childRef(node); else if (childRef.hasOwnProperty("current")) childRef.current = node; } }, onClick: (e) => { handleClick(e); if (child.props.onClick) child.props.onClick(e); }, onMouseEnter: (e) => { handleMouseEnter(e); if (child.props.onMouseEnter) child.props.onMouseEnter(e); }, onMouseLeave: (e) => { // Start a close timer; the content's onMouseEnter will cancel it if // the cursor moves into the portal-rendered dropdown before the timer fires. handleMouseLeaveTrigger(e); if (child.props.onMouseLeave) child.props.onMouseLeave(e); }, ...otherProps, }); } return ((0, jsx_runtime_1.jsx)("button", { ref: (node) => { triggerRef.current = node; if (typeof ref === "function") ref(node); else if (ref) ref.current = node; }, type: "button", onClick: handleClick, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeaveTrigger, ...otherProps, children: children })); }); exports.DropdownMenuTrigger = DropdownMenuTrigger; DropdownMenuTrigger.displayName = "DropdownMenuTrigger"; const dropdownMenuContentVariants = (0, class_variance_authority_1.cva)("z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md", { variants: { variant: { default: "", contextMenu: "min-w-0", }, }, defaultVariants: { variant: "default", }, }); const DropdownMenuContent = React.forwardRef(({ className, children, align = "center", alignOffset = 0, side = "bottom", sideOffset = 4, variant, ...props }, ref) => { const context = React.useContext(DropdownMenuContext); if (!context) throw new Error("DropdownMenuContent must be used within a DropdownMenu"); const { open, setOpen, hoverMode, triggerRef } = context; const menuRef = React.useRef(null); const [position, setPosition] = React.useState({ top: 0, left: 0 }); const [mounted, setMounted] = React.useState(false); const [positioned, setPositioned] = React.useState(false); React.useEffect(() => { setMounted(true); }, []); // Reset positioned to false ONLY when closed, avoids flickering when children change React.useEffect(() => { if (!open) { setPositioned(false); } }, [open]); // Body scroll lock removed // Previous logic was interfering with page navigation // Close on click outside React.useEffect(() => { if (!open) return; const handleClickOutside = (e) => { if (menuRef.current && !menuRef.current.contains(e.target) && triggerRef.current && !triggerRef.current.contains(e.target)) { setOpen(false); } }; document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, [open, setOpen, triggerRef]); // Position updates React.useEffect(() => { if (!open || !triggerRef.current) return; const updatePosition = () => { if (!triggerRef.current) return; const triggerRect = triggerRef.current.getBoundingClientRect(); let menuRect = menuRef.current?.getBoundingClientRect(); if (!menuRect) { // Temporary render to get size if not yet rendered // This part in a Portal scenario is tricky because ref might be null initially. // We'll rely on a second pass or basic estimation if ref is null, // but Framer Motion should attach ref quickly. // For simplicity in this fix, if no menuRect, we assume a default width/height or wait for next tick. if (!menuRef.current) return; menuRect = menuRef.current.getBoundingClientRect(); } let top = 0; let left = 0; // Basic positioning logic tailored for Portal (relative to viewport) if (side === "bottom") { top = triggerRect.bottom + sideOffset; } else if (side === "top") { top = triggerRect.top - (menuRect?.height || 0) - sideOffset; } else if (side === "left" || side === "right") { top = triggerRect.top + triggerRect.height / 2 - (menuRect?.height || 0) / 2; } if (side === "right") { left = triggerRect.right + sideOffset; } else if (side === "left") { left = triggerRect.left - (menuRect?.width || 0) - sideOffset; } else { if (align === "start") left = triggerRect.left + alignOffset; else if (align === "center") left = triggerRect.left + triggerRect.width / 2 - (menuRect?.width || 0) / 2 + alignOffset; else if (align === "end") left = triggerRect.right - (menuRect?.width || 0) - alignOffset; } // Viewport collision detection const windowWidth = window.innerWidth; const windowHeight = window.innerHeight; if (left + (menuRect?.width || 0) > windowWidth) left = windowWidth - (menuRect?.width || 0) - 8; if (left < 8) left = 8; if (top + (menuRect?.height || 0) > windowHeight) { if (side === "bottom" && triggerRect.top > (menuRect?.height || 0) + sideOffset) { top = triggerRect.top - (menuRect?.height || 0) - sideOffset; } else { const maxHeight = windowHeight - top - 8; if (menuRef.current) menuRef.current.style.maxHeight = `${maxHeight}px`; } } setPosition({ top, left }); }; // Run update immediately and on scroll/resize // Use rAF to ensure the DOM has painted the portal content before measuring const raf = requestAnimationFrame(() => { updatePosition(); setPositioned(true); }); window.addEventListener("scroll", updatePosition, true); window.addEventListener("resize", updatePosition); // Also update after a short delay to ensure Framer Motion has rendered the initial frame const timeout = setTimeout(() => { updatePosition(); setPositioned(true); }, 0); return () => { window.removeEventListener("scroll", updatePosition, true); window.removeEventListener("resize", updatePosition); cancelAnimationFrame(raf); clearTimeout(timeout); }; }, [open, align, alignOffset, side, sideOffset, triggerRef, children, variant, className, mounted]); if (!mounted) return null; return react_dom_1.default.createPortal((0, jsx_runtime_1.jsx)(framer_motion_1.AnimatePresence, { children: open && ((0, jsx_runtime_1.jsx)(framer_motion_1.motion.div, { ref: (node) => { if (typeof ref === "function") ref(node); else if (ref) ref.current = node; menuRef.current = node; }, className: (0, utils_1.cn)(dropdownMenuContentVariants({ variant }), "dropdown-scrollbar", className), style: { position: "fixed", top: `${position.top}px`, left: `${position.left}px`, zIndex: 99999, maxHeight: "calc(90vh - 60px)", overflowY: "auto", transformOrigin: side === "bottom" ? "top center" : side === "top" ? "bottom center" : side === "left" ? "center right" : "center left", }, initial: { opacity: 0, scale: 0.9, y: side === "bottom" ? -4 : side === "top" ? 4 : 0 }, animate: positioned ? { opacity: 1, scale: 1, y: 0, pointerEvents: "auto" } : { opacity: 0, scale: 0.9, pointerEvents: "none" }, exit: { opacity: 0, scale: 0.9, y: side === "bottom" ? -4 : side === "top" ? 4 : 0, transition: { duration: 0.15 }, pointerEvents: "none" }, transition: { type: "spring", damping: 20, stiffness: 300 }, onMouseEnter: (e) => { if (hoverMode && context.timeoutRef.current) { clearTimeout(context.timeoutRef.current); } if (props.onMouseEnter) props.onMouseEnter(e); }, onMouseLeave: (e) => { if (hoverMode) { context.timeoutRef.current = setTimeout(() => setOpen(false), 200); } if (props.onMouseLeave) props.onMouseLeave(e); }, "data-lenis-prevent": true, ...props, children: children })) }), document.body); }); exports.DropdownMenuContent = DropdownMenuContent; DropdownMenuContent.displayName = "DropdownMenuContent"; const DropdownMenuLabel = React.forwardRef(({ className, ...props }, ref) => ((0, jsx_runtime_1.jsx)("div", { ref: ref, className: (0, utils_1.cn)("px-2 py-1.5 text-sm font-semibold", className), ...props }))); exports.DropdownMenuLabel = DropdownMenuLabel; DropdownMenuLabel.displayName = "DropdownMenuLabel"; const DropdownMenuItem = React.forwardRef(({ className, inset, asChild, disabled = false, ...props }, ref) => { const context = React.useContext(DropdownMenuContext); if (!context) throw new Error("DropdownMenuItem must be used within a DropdownMenu"); const { setOpen } = context; const handleClick = (e) => { if (disabled) { e.preventDefault(); return; } setOpen(false); if (props.onClick) props.onClick(e); }; return ((0, jsx_runtime_1.jsx)("div", { ref: ref, className: (0, utils_1.cn)("relative flex gap-1 cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground hover:bg-accent hover:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", inset && "pl-8", className), onClick: handleClick, "data-disabled": disabled ? "" : undefined, ...props })); }); exports.DropdownMenuItem = DropdownMenuItem; DropdownMenuItem.displayName = "DropdownMenuItem"; const DropdownMenuSeparator = React.forwardRef(({ className, ...props }, ref) => ((0, jsx_runtime_1.jsx)("div", { ref: ref, className: (0, utils_1.cn)("-mx-1 my-1 h-px bg-muted", className), ...props }))); exports.DropdownMenuSeparator = DropdownMenuSeparator; DropdownMenuSeparator.displayName = "DropdownMenuSeparator"; const DropdownMenuGroup = React.forwardRef(({ className, ...props }, ref) => ((0, jsx_runtime_1.jsx)("div", { ref: ref, className: (0, utils_1.cn)("space-y-1", className), ...props }))); exports.DropdownMenuGroup = DropdownMenuGroup; DropdownMenuGroup.displayName = "DropdownMenuGroup"; //# sourceMappingURL=dropdown-menu.js.map