Highly ambitious ATProtocol AppView service and sdks
at fix-postgres 121 lines 2.9 kB view raw
1import { useState, useRef, useEffect, createContext, useContext, ReactNode } from "react"; 2import { cn } from "../utils/cn.ts"; 3 4interface DropdownContextValue { 5 isOpen: boolean; 6 setIsOpen: (isOpen: boolean) => void; 7} 8 9const DropdownContext = createContext<DropdownContextValue | null>(null); 10 11function useDropdownContext() { 12 const context = useContext(DropdownContext); 13 if (!context) { 14 throw new Error("Dropdown components must be used within a Dropdown"); 15 } 16 return context; 17} 18 19interface DropdownProps { 20 children: ReactNode; 21} 22 23function DropdownRoot({ children }: DropdownProps) { 24 const [isOpen, setIsOpen] = useState(false); 25 const dropdownRef = useRef<HTMLDivElement>(null); 26 27 useEffect(() => { 28 const handleClickOutside = (event: MouseEvent) => { 29 if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { 30 setIsOpen(false); 31 } 32 }; 33 34 if (isOpen) { 35 document.addEventListener("mousedown", handleClickOutside); 36 } 37 38 return () => { 39 document.removeEventListener("mousedown", handleClickOutside); 40 }; 41 }, [isOpen]); 42 43 return ( 44 <DropdownContext.Provider value={{ isOpen, setIsOpen }}> 45 <div className="relative" ref={dropdownRef}> 46 {children} 47 </div> 48 </DropdownContext.Provider> 49 ); 50} 51 52interface DropdownTriggerProps { 53 children: ReactNode; 54 className?: string; 55} 56 57function DropdownTrigger({ children, className }: DropdownTriggerProps) { 58 const { isOpen, setIsOpen } = useDropdownContext(); 59 60 return ( 61 <div onClick={() => setIsOpen(!isOpen)} className={className}> 62 {children} 63 </div> 64 ); 65} 66 67interface DropdownContentProps { 68 children: ReactNode; 69 align?: "left" | "right"; 70 className?: string; 71} 72 73function DropdownContent({ children, align = "right", className }: DropdownContentProps) { 74 const { isOpen, setIsOpen } = useDropdownContext(); 75 76 if (!isOpen) return null; 77 78 return ( 79 <div 80 className={cn( 81 "absolute mt-1 w-48 bg-zinc-900 border border-zinc-800 rounded shadow-lg z-20", 82 align === "left" ? "left-0" : "right-0", 83 className 84 )} 85 onClick={() => setIsOpen(false)} 86 > 87 {children} 88 </div> 89 ); 90} 91 92interface DropdownItemProps { 93 onClick?: () => void; 94 children: ReactNode; 95 variant?: "default" | "danger"; 96 className?: string; 97} 98 99function DropdownItem({ onClick, children, variant: _variant = "default", className }: DropdownItemProps) { 100 const variantClasses = "text-zinc-300 hover:bg-zinc-800"; 101 102 return ( 103 <button 104 type="button" 105 onClick={onClick} 106 className={cn( 107 "w-full flex items-center gap-2 px-4 py-2 text-xs transition-colors cursor-pointer", 108 variantClasses, 109 className 110 )} 111 > 112 {children} 113 </button> 114 ); 115} 116 117export const Dropdown = Object.assign(DropdownRoot, { 118 Trigger: DropdownTrigger, 119 Content: DropdownContent, 120 Item: DropdownItem, 121});