@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
JavaScript
"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