nextuiq
Version:
NextUIQ is a modern, lightweight, and developer-friendly UI component library for React and Next.js. Built with TypeScript and Tailwind CSS, it offers customizable, accessible, and performance-optimized components with built-in dark mode, theme customizat
140 lines (137 loc) • 4.78 kB
JavaScript
import { j as jsxRuntimeExports } from './index46.mjs';
import { useState, useRef, useId, useEffect } from 'react';
import { cn } from './index38.mjs';
const Dropdown = ({
trigger,
items = [],
align = "end",
className,
onSelect,
isOpen: controlledIsOpen,
onClose,
children,
id,
role = "menu",
"aria-modal": ariaModal,
"aria-label": ariaLabel
}) => {
const [internalIsOpen, setInternalIsOpen] = useState(false);
const [activeIndex, setActiveIndex] = useState(-1);
const dropdownRef = useRef(null);
const menuId = useId();
const isOpen = controlledIsOpen ?? internalIsOpen;
const setIsOpen = (value) => {
setInternalIsOpen(value);
if (!value && onClose) {
onClose();
}
};
const handleItemClick = (item) => {
if (!item.disabled) {
item.onClick?.();
onSelect?.(item.value);
setIsOpen(false);
setActiveIndex(-1);
}
};
useEffect(() => {
const handleClickOutside = (event) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
setIsOpen(false);
}
};
const handleKeyDown = (event) => {
if (!isOpen) return;
switch (event.key) {
case "ArrowDown":
event.preventDefault();
setActiveIndex((prev) => prev < items.length - 1 ? prev + 1 : 0);
break;
case "ArrowUp":
event.preventDefault();
setActiveIndex((prev) => prev > 0 ? prev - 1 : items.length - 1);
break;
case "Enter":
case "Space":
event.preventDefault();
if (activeIndex >= 0) {
handleItemClick(items[activeIndex]);
}
break;
case "Escape":
event.preventDefault();
setIsOpen(false);
break;
}
};
document.addEventListener("mousedown", handleClickOutside);
document.addEventListener("keydown", handleKeyDown);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
document.removeEventListener("keydown", handleKeyDown);
};
}, [isOpen, items, activeIndex]);
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative inline-block", ref: dropdownRef, children: [
trigger && /* @__PURE__ */ jsxRuntimeExports.jsx(
"div",
{
onClick: () => setIsOpen(!isOpen),
onKeyDown: (e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
setIsOpen(!isOpen);
}
},
role: "button",
tabIndex: 0,
"aria-haspopup": "true",
"aria-expanded": isOpen,
"aria-controls": id || menuId,
children: trigger
}
),
isOpen && /* @__PURE__ */ jsxRuntimeExports.jsx(
"div",
{
id: id || menuId,
className: cn(
"absolute z-50 mt-2 min-w-[8rem] overflow-hidden rounded-md border",
"border-[oklch(var(--theme-border))] bg-[oklch(var(--theme-background))]",
"shadow-md animate-in fade-in-0 zoom-in-95",
align === "end" ? "right-0" : "left-0",
className
),
role,
"aria-modal": ariaModal,
"aria-label": ariaLabel,
children: children || /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "py-1", role: "menu", "aria-orientation": "vertical", children: items?.map((item, index) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
"button",
{
onClick: () => handleItemClick(item),
onMouseEnter: () => setActiveIndex(index),
onFocus: () => setActiveIndex(index),
className: cn(
"relative flex w-full cursor-pointer select-none items-center px-3 py-2 text-sm outline-none transition-colors",
"text-[oklch(var(--theme-foreground))] hover:bg-[oklch(var(--theme-muted))] hover:text-[oklch(var(--theme-foreground))]",
"focus:bg-[oklch(var(--theme-muted))] focus:text-[oklch(var(--theme-foreground))]",
item.disabled && "cursor-not-allowed opacity-50",
item.selected && "bg-[oklch(var(--theme-muted))] text-[oklch(var(--theme-foreground))]",
activeIndex === index && "bg-[oklch(var(--theme-muted))] text-[oklch(var(--theme-foreground))]"
),
disabled: item.disabled,
role: "menuitem",
tabIndex: -1,
"aria-selected": item.selected,
"aria-disabled": item.disabled,
children: [
item.icon && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "mr-2 h-4 w-4", "aria-hidden": "true", children: item.icon }),
item.label
]
},
index
)) })
}
)
] });
};
export { Dropdown };