| | import React, {
|
| | createContext,
|
| | useContext,
|
| | useState,
|
| | useRef,
|
| | useEffect,
|
| | } from "react";
|
| | import { cn } from "@/lib/utils";
|
| |
|
| | const DropdownContext = createContext(null);
|
| |
|
| | export function DropdownMenu({ children }) {
|
| | const [open, setOpen] = useState(false);
|
| | const triggerRef = useRef(null);
|
| |
|
| |
|
| | useEffect(() => {
|
| | if (!open) return;
|
| | function handleClick(e) {
|
| | if (!triggerRef.current) return;
|
| | if (!triggerRef.current.parentElement.contains(e.target)) {
|
| | setOpen(false);
|
| | }
|
| | }
|
| | document.addEventListener("mousedown", handleClick);
|
| | return () => document.removeEventListener("mousedown", handleClick);
|
| | }, [open]);
|
| |
|
| | return (
|
| | <DropdownContext.Provider value={{ open, setOpen, triggerRef }}>
|
| | <div className="relative inline-block">{children}</div>
|
| | </DropdownContext.Provider>
|
| | );
|
| | }
|
| |
|
| | export function DropdownMenuTrigger({ asChild, children }) {
|
| | const { setOpen, triggerRef } = useContext(DropdownContext);
|
| |
|
| | const handleClick = (e) => {
|
| | e.stopPropagation();
|
| | setOpen((o) => !o);
|
| | };
|
| |
|
| | if (asChild && React.isValidElement(children)) {
|
| | return React.cloneElement(children, {
|
| | ref: triggerRef,
|
| | onClick: (e) => {
|
| | children.props.onClick?.(e);
|
| | handleClick(e);
|
| | },
|
| | });
|
| | }
|
| |
|
| | return (
|
| | <button
|
| | ref={triggerRef}
|
| | type="button"
|
| | onClick={handleClick}
|
| | className="inline-flex"
|
| | >
|
| | {children}
|
| | </button>
|
| | );
|
| | }
|
| |
|
| | export function DropdownMenuContent({ className, align = "end", ...props }) {
|
| | const { open } = useContext(DropdownContext);
|
| | if (!open) return null;
|
| |
|
| | const alignment =
|
| | align === "end"
|
| | ? "right-0 origin-top-right"
|
| | : align === "start"
|
| | ? "left-0 origin-top-left"
|
| | : "left-1/2 -translate-x-1/2 origin-top";
|
| |
|
| | return (
|
| | <div
|
| | className={cn(
|
| | "absolute z-50 mt-2 min-w-[8rem] rounded-md border border-slate-200 bg-white shadow-lg focus:outline-none",
|
| | alignment,
|
| | className
|
| | )}
|
| | {...props}
|
| | />
|
| | );
|
| | }
|
| |
|
| | export function DropdownMenuItem({ className, onClick, ...props }) {
|
| | const { setOpen } = useContext(DropdownContext);
|
| | const handleClick = (e) => {
|
| | onClick?.(e);
|
| | setOpen(false);
|
| | };
|
| | return (
|
| | <div
|
| | className={cn(
|
| | "flex cursor-pointer select-none items-center px-2 py-1.5 text-sm text-slate-700 hover:bg-slate-100 rounded-md",
|
| | className
|
| | )}
|
| | onClick={handleClick}
|
| | {...props}
|
| | />
|
| | );
|
| | }
|
| |
|
| | export function DropdownMenuSeparator({ className }) {
|
| | return (
|
| | <div
|
| | className={cn("my-1 h-px bg-slate-200 w-full", className)}
|
| | />
|
| | );
|
| | }
|
| |
|