Spaces:
Running
Running
| 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); | |
| // Close on outside click | |
| 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)} | |
| /> | |
| ); | |
| } | |