UNPKG

@arolariu/components

Version:

🎨 70+ beautiful, accessible React components built on Radix UI. TypeScript-first, tree-shakeable, SSR-ready. Perfect for modern web apps, design systems & rapid prototyping. Zero config, maximum flexibility! ⚡

560 lines (559 loc) • 22.3 kB
"use client"; import { Fragment, jsx, jsxs } from "react/jsx-runtime"; import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react"; import { AnimatePresence, motion } from "motion/react"; import { Children, cloneElement, createContext, isValidElement, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react"; import { Drawer, DrawerClose, DrawerContent, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger } from "./drawer.js"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger } from "./dropdown-menu.js"; import { useIsMobile } from "../../hooks/useIsMobile.js"; import { cn } from "../../lib/utilities.js"; const DropDrawerContext = /*#__PURE__*/ createContext({ isMobile: false }); const useDropDrawerContext = ()=>{ const context = useContext(DropDrawerContext); if (!context) throw new Error("DropDrawer components cannot be rendered outside the Context"); return context; }; function DropDrawer({ children, ...props }) { const isMobile = useIsMobile(); const DropdownComponent = isMobile ? Drawer : DropdownMenu; return /*#__PURE__*/ jsx(DropDrawerContext.Provider, { value: { isMobile }, children: /*#__PURE__*/ jsx(DropdownComponent, { "data-slot": "drop-drawer", ...isMobile && { autoFocus: true }, ...props, children: children }) }); } function DropDrawerTrigger({ className, children, ...props }) { const { isMobile } = useDropDrawerContext(); const TriggerComponent = isMobile ? DrawerTrigger : DropdownMenuTrigger; return /*#__PURE__*/ jsx(TriggerComponent, { "data-slot": "drop-drawer-trigger", className: className, ...props, children: children }); } function DropDrawerContent({ className, children, ...props }) { const { isMobile } = useDropDrawerContext(); const [activeSubmenu, setActiveSubmenu] = useState(null); const [submenuTitle, setSubmenuTitle] = useState(null); const [submenuStack, setSubmenuStack] = useState([]); const [animationDirection, setAnimationDirection] = useState("forward"); const submenuContentRef = useRef(new Map()); const navigateToSubmenu = useCallback((id, title)=>{ setAnimationDirection("forward"); setActiveSubmenu(id); setSubmenuTitle(title); setSubmenuStack((prev)=>[ ...prev, { id, title } ]); }, []); const goBack = useCallback(()=>{ setAnimationDirection("backward"); if (submenuStack.length <= 1) { setActiveSubmenu(null); setSubmenuTitle(null); setSubmenuStack([]); } else { const newStack = [ ...submenuStack ]; newStack.pop(); const previous = newStack[newStack.length - 1]; setActiveSubmenu(previous.id); setSubmenuTitle(previous.title); setSubmenuStack(newStack); } }, [ submenuStack ]); const registerSubmenuContent = useCallback((id, content)=>{ submenuContentRef.current.set(id, content); }, []); const extractSubmenuContent = useCallback((elements, targetId)=>{ const result = []; const findSubmenuContent = (node)=>{ if (!/*#__PURE__*/ isValidElement(node)) return; const element = node; const props = element.props; if (element.type === DropDrawerSub) { const elementId = props.id; const dataSubmenuId = props["data-submenu-id"]; if (elementId === targetId || dataSubmenuId === targetId) { if (props.children) Children.forEach(props.children, (child)=>{ if (/*#__PURE__*/ isValidElement(child) && child.type === DropDrawerSubContent) { const subContentProps = child.props; if (subContentProps.children) Children.forEach(subContentProps.children, (contentChild)=>{ result.push(contentChild); }); } }); return; } } if (props.children) if (Array.isArray(props.children)) props.children.forEach((child)=>findSubmenuContent(child)); else findSubmenuContent(props.children); }; if (Array.isArray(elements)) elements.forEach((child)=>findSubmenuContent(child)); else findSubmenuContent(elements); return result; }, []); const getSubmenuContent = useCallback((id)=>{ const cachedContent = submenuContentRef.current.get(id || ""); if (cachedContent && cachedContent.length > 0) return cachedContent; const submenuContent = extractSubmenuContent(children, id); if (0 === submenuContent.length) return []; if (id) submenuContentRef.current.set(id, submenuContent); return submenuContent; }, [ children, extractSubmenuContent ]); const variants = { enter: (direction)=>({ x: "forward" === direction ? "100%" : "-100%", opacity: 0 }), center: { x: 0, opacity: 1 }, exit: (direction)=>({ x: "forward" === direction ? "-100%" : "100%", opacity: 0 }) }; const transition = { duration: 0.3, ease: [ 0.25, 0.1, 0.25, 1.0 ] }; if (isMobile) return /*#__PURE__*/ jsx(SubmenuContext.Provider, { value: { activeSubmenu, setActiveSubmenu: (id)=>{ if (null === id) { setActiveSubmenu(null); setSubmenuTitle(null); setSubmenuStack([]); } }, submenuTitle, setSubmenuTitle, navigateToSubmenu, registerSubmenuContent }, children: /*#__PURE__*/ jsx(DrawerContent, { "data-slot": "drop-drawer-content", className: cn("max-h-[90vh]", className), ...props, children: activeSubmenu ? /*#__PURE__*/ jsxs(Fragment, { children: [ /*#__PURE__*/ jsx(DrawerHeader, { children: /*#__PURE__*/ jsxs("div", { className: "flex items-center gap-2", children: [ /*#__PURE__*/ jsx("button", { onClick: goBack, className: "rounded-full p-1 hover:bg-neutral-100/50 dark:hover:bg-neutral-800/50", children: /*#__PURE__*/ jsx(ChevronLeftIcon, { className: "h-5 w-5" }) }), /*#__PURE__*/ jsx(DrawerTitle, { children: submenuTitle || "Submenu" }) ] }) }), /*#__PURE__*/ jsx("div", { className: "relative max-h-[70vh] flex-1 overflow-y-auto", children: /*#__PURE__*/ jsx(AnimatePresence, { initial: false, mode: "wait", custom: animationDirection, children: /*#__PURE__*/ jsx(motion.div, { custom: animationDirection, variants: variants, initial: "enter", animate: "center", exit: "exit", transition: transition, className: "h-full w-full space-y-1.5 pb-6", children: activeSubmenu ? getSubmenuContent(activeSubmenu) : children }, activeSubmenu || "main") }) }) ] }) : /*#__PURE__*/ jsxs(Fragment, { children: [ /*#__PURE__*/ jsx(DrawerHeader, { className: "sr-only", children: /*#__PURE__*/ jsx(DrawerTitle, { children: "Menu" }) }), /*#__PURE__*/ jsx("div", { className: "max-h-[70vh] overflow-y-auto", children: /*#__PURE__*/ jsx(AnimatePresence, { initial: false, mode: "wait", custom: animationDirection, children: /*#__PURE__*/ jsx(motion.div, { custom: animationDirection, variants: variants, initial: "enter", animate: "center", exit: "exit", transition: transition, className: "w-full space-y-1.5 pb-6", children: children }, "main-menu") }) }) ] }) }) }); return /*#__PURE__*/ jsx(SubmenuContext.Provider, { value: { activeSubmenu, setActiveSubmenu, submenuTitle, setSubmenuTitle, registerSubmenuContent }, children: /*#__PURE__*/ jsx(DropdownMenuContent, { "data-slot": "drop-drawer-content", align: "end", sideOffset: 4, className: cn("max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[220px] overflow-y-auto", className), ...props, children: children }) }); } function DropDrawerItem({ className, children, onSelect, onClick, icon, inset, disabled, ...props }) { const { isMobile } = useDropDrawerContext(); const isInGroup = useCallback((element)=>{ if (!element) return false; let parent = element.parentElement; while(parent){ if (parent.hasAttribute("data-drop-drawer-group")) return true; parent = parent.parentElement; } return false; }, []); const itemRef = useRef(null); const [isInsideGroup, setIsInsideGroup] = useState(false); useEffect(()=>{ if (!isMobile) return; const timer = setTimeout(()=>{ if (itemRef.current) setIsInsideGroup(isInGroup(itemRef.current)); }, 0); return ()=>clearTimeout(timer); }, [ isInGroup, isMobile ]); if (isMobile) { const handleClick = (e)=>{ if (disabled) return; if (onClick) onClick(e); if (onSelect) onSelect(e); }; const content = /*#__PURE__*/ jsxs("div", { ref: itemRef, "data-slot": "drop-drawer-item", "data-inset": inset, "data-disabled": disabled, className: cn("flex cursor-pointer items-center justify-between px-4 py-4", !isInsideGroup && "mx-2 my-1.5 rounded-md bg-neutral-100 dark:bg-neutral-100 dark:bg-neutral-800 dark:dark:bg-neutral-800", isInsideGroup && "bg-transparent py-4", inset && "pl-8", disabled && "pointer-events-none opacity-50", className), onClick: handleClick, "aria-disabled": disabled, ...props, children: [ /*#__PURE__*/ jsx("div", { className: "flex items-center gap-2", children: children }), icon && /*#__PURE__*/ jsx("div", { className: "flex-shrink-0", children: icon }) ] }); const isInSubmenu = props["data-parent-submenu-id"] || props["data-parent-submenu"]; if (isInSubmenu) return content; return /*#__PURE__*/ jsx(DrawerClose, { asChild: true, children: content }); } return /*#__PURE__*/ jsx(DropdownMenuItem, { "data-slot": "drop-drawer-item", "data-inset": inset, className: className, onSelect: onSelect, onClick: onClick, inset: inset, disabled: disabled, ...props, children: /*#__PURE__*/ jsxs("div", { className: "flex w-full items-center justify-between", children: [ /*#__PURE__*/ jsx("div", { children: children }), icon && /*#__PURE__*/ jsx("div", { children: icon }) ] }) }); } function DropDrawerSeparator({ className, ...props }) { const { isMobile } = useDropDrawerContext(); if (isMobile) return null; return /*#__PURE__*/ jsx(DropdownMenuSeparator, { "data-slot": "drop-drawer-separator", className: className, ...props }); } function DropDrawerLabel({ className, children, ...props }) { const { isMobile } = useDropDrawerContext(); if (isMobile) return /*#__PURE__*/ jsx(DrawerHeader, { className: "p-0", children: /*#__PURE__*/ jsx(DrawerTitle, { "data-slot": "drop-drawer-label", className: cn("px-4 py-2 text-sm font-medium text-neutral-500 dark:text-neutral-400", className), ...props, children: children }) }); return /*#__PURE__*/ jsx(DropdownMenuLabel, { "data-slot": "drop-drawer-label", className: className, ...props, children: children }); } function DropDrawerFooter({ className, children, ...props }) { const { isMobile } = useDropDrawerContext(); if (isMobile) return /*#__PURE__*/ jsx(DrawerFooter, { "data-slot": "drop-drawer-footer", className: cn("p-4", className), ...props, children: children }); return /*#__PURE__*/ jsx("div", { "data-slot": "drop-drawer-footer", className: cn("p-2", className), ...props, children: children }); } function DropDrawerGroup({ className, children, ...props }) { const { isMobile } = useDropDrawerContext(); const childrenWithSeparators = useMemo(()=>{ if (!isMobile) return children; const childArray = Children.toArray(children); const filteredChildren = childArray.filter((child)=>/*#__PURE__*/ isValidElement(child) && child.type !== DropDrawerSeparator); return filteredChildren.flatMap((child, index)=>{ if (index === filteredChildren.length - 1) return [ child ]; return [ child, /*#__PURE__*/ jsx("div", { className: "h-px bg-neutral-200 dark:bg-neutral-800", "aria-hidden": "true" }, `separator-${index}`) ]; }); }, [ children, isMobile ]); if (isMobile) return /*#__PURE__*/ jsx("div", { "data-drop-drawer-group": true, "data-slot": "drop-drawer-group", role: "group", className: cn("mx-2 my-3 overflow-hidden rounded-xl bg-neutral-100 dark:bg-neutral-100 dark:bg-neutral-800 dark:dark:bg-neutral-800", className), ...props, children: childrenWithSeparators }); return /*#__PURE__*/ jsx("div", { "data-drop-drawer-group": true, "data-slot": "drop-drawer-group", role: "group", className: className, ...props, children: children }); } const SubmenuContext = /*#__PURE__*/ createContext({ activeSubmenu: null, setActiveSubmenu: ()=>{}, submenuTitle: null, setSubmenuTitle: ()=>{}, navigateToSubmenu: void 0, registerSubmenuContent: void 0 }); let submenuIdCounter = 0; function DropDrawerSub({ children, id, ...props }) { const { isMobile } = useDropDrawerContext(); const { registerSubmenuContent } = useContext(SubmenuContext); const [generatedId] = useState(()=>`submenu-${submenuIdCounter++}`); const submenuId = id || generatedId; useEffect(()=>{ if (!registerSubmenuContent) return; const contentItems = []; Children.forEach(children, (child)=>{ if (/*#__PURE__*/ isValidElement(child) && child.type === DropDrawerSubContent) Children.forEach(child.props.children, (contentChild)=>{ contentItems.push(contentChild); }); }); if (contentItems.length > 0) registerSubmenuContent(submenuId, contentItems); }, [ children, registerSubmenuContent, submenuId ]); if (isMobile) { const processedChildren = Children.map(children, (child)=>{ if (!/*#__PURE__*/ isValidElement(child)) return child; if (child.type === DropDrawerSubTrigger) return /*#__PURE__*/ cloneElement(child, { ...child.props, "data-parent-submenu-id": submenuId, "data-submenu-id": submenuId, "data-parent-submenu": submenuId }); if (child.type === DropDrawerSubContent) return /*#__PURE__*/ cloneElement(child, { ...child.props, "data-parent-submenu-id": submenuId, "data-submenu-id": submenuId, "data-parent-submenu": submenuId }); return child; }); return /*#__PURE__*/ jsx("div", { "data-slot": "drop-drawer-sub", "data-submenu-id": submenuId, id: submenuId, children: processedChildren }); } return /*#__PURE__*/ jsx(DropdownMenuSub, { "data-slot": "drop-drawer-sub", "data-submenu-id": submenuId, ...props, children: children }); } function DropDrawerSubTrigger({ className, inset, children, ...props }) { const { isMobile } = useDropDrawerContext(); const { navigateToSubmenu } = useContext(SubmenuContext); const isInGroup = useCallback((element)=>{ if (!element) return false; let parent = element.parentElement; while(parent){ if (parent.hasAttribute("data-drop-drawer-group")) return true; parent = parent.parentElement; } return false; }, []); const itemRef = useRef(null); const [isInsideGroup, setIsInsideGroup] = useState(false); useEffect(()=>{ if (!isMobile) return; const timer = setTimeout(()=>{ if (itemRef.current) setIsInsideGroup(isInGroup(itemRef.current)); }, 0); return ()=>clearTimeout(timer); }, [ isInGroup, isMobile ]); if (isMobile) { const handleClick = (e)=>{ e.preventDefault(); e.stopPropagation(); const element = e.currentTarget; let submenuId = null; if (element.closest("[data-submenu-id]")) { const closestElement = element.closest("[data-submenu-id]"); const id = closestElement?.getAttribute("data-submenu-id"); if (id) submenuId = id; } if (!submenuId) submenuId = props["data-parent-submenu-id"] || props["data-parent-submenu"]; if (!submenuId) return; const title = "string" == typeof children ? children : "Submenu"; if (navigateToSubmenu) navigateToSubmenu(submenuId, title); }; const combinedOnClick = (e)=>{ const typedProps = props; if (typedProps["onClick"]) { const originalOnClick = typedProps["onClick"]; originalOnClick(e); } handleClick(e); }; const { ...restProps } = props; return /*#__PURE__*/ jsxs("div", { ref: itemRef, "data-slot": "drop-drawer-sub-trigger", "data-inset": inset, className: cn("flex cursor-pointer items-center justify-between px-4 py-4", !isInsideGroup && "mx-2 my-1.5 rounded-md bg-neutral-100 dark:bg-neutral-100 dark:bg-neutral-800 dark:dark:bg-neutral-800", isInsideGroup && "bg-transparent py-4", inset && "pl-8", className), onClick: combinedOnClick, ...restProps, children: [ /*#__PURE__*/ jsx("div", { className: "flex items-center gap-2", children: children }), /*#__PURE__*/ jsx(ChevronRightIcon, { className: "h-5 w-5" }) ] }); } return /*#__PURE__*/ jsx(DropdownMenuSubTrigger, { "data-slot": "drop-drawer-sub-trigger", "data-inset": inset, className: className, inset: inset, ...props, children: children }); } function DropDrawerSubContent({ className, sideOffset = 4, children, ...props }) { const { isMobile } = useDropDrawerContext(); if (isMobile) return null; return /*#__PURE__*/ jsx(DropdownMenuSubContent, { "data-slot": "drop-drawer-sub-content", sideOffset: sideOffset, className: cn("z-50 min-w-[8rem] overflow-hidden rounded-md border border-neutral-200 p-1 shadow-lg dark:border-neutral-800", className), ...props, children: children }); } export { DropDrawer, DropDrawerContent, DropDrawerFooter, DropDrawerGroup, DropDrawerItem, DropDrawerLabel, DropDrawerSeparator, DropDrawerSub, DropDrawerSubContent, DropDrawerSubTrigger, DropDrawerTrigger }; //# sourceMappingURL=dropdrawer.js.map