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
JavaScript
;
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