web/app/components/ui/dropdown.tsxblame
View source
4dfd09b1"use client";
4dfd09b2
4dfd09b3import { useState, useRef, useEffect } from "react";
4dfd09b4
4dfd09b5export interface DropdownProps {
4dfd09b6 trigger: React.ReactNode;
4dfd09b7 children: React.ReactNode;
4dfd09b8 align?: "left" | "right";
4dfd09b9 className?: string;
4dfd09b10}
4dfd09b11
4dfd09b12export function Dropdown({
4dfd09b13 trigger,
4dfd09b14 children,
4dfd09b15 align = "left",
4dfd09b16 className = "",
4dfd09b17}: DropdownProps) {
4dfd09b18 const [open, setOpen] = useState(false);
4dfd09b19 const ref = useRef<HTMLDivElement>(null);
4dfd09b20
4dfd09b21 useEffect(() => {
4dfd09b22 if (!open) return;
4dfd09b23
4dfd09b24 function handleClick(e: MouseEvent) {
4dfd09b25 if (ref.current && !ref.current.contains(e.target as Node)) {
4dfd09b26 setOpen(false);
4dfd09b27 }
4dfd09b28 }
4dfd09b29
4dfd09b30 function handleKey(e: KeyboardEvent) {
4dfd09b31 if (e.key === "Escape") setOpen(false);
4dfd09b32 }
4dfd09b33
4dfd09b34 document.addEventListener("mousedown", handleClick);
4dfd09b35 document.addEventListener("keydown", handleKey);
4dfd09b36 return () => {
4dfd09b37 document.removeEventListener("mousedown", handleClick);
4dfd09b38 document.removeEventListener("keydown", handleKey);
4dfd09b39 };
4dfd09b40 }, [open]);
4dfd09b41
4dfd09b42 return (
4dfd09b43 <div ref={ref} className={`relative ${className}`}>
4dfd09b44 <div onClick={() => setOpen((v) => !v)}>{trigger}</div>
4dfd09b45 {open && (
4dfd09b46 <div
4dfd09b47 className="absolute mt-1 py-1 min-w-[160px] z-50"
8a2c7d448 onClick={() => setOpen(false)}
4dfd09b49 style={{
4dfd09b50 backgroundColor: "var(--bg-card)",
4dfd09b51 border: "1px solid var(--border)",
4dfd09b52 boxShadow: "0 4px 12px rgba(0,0,0,0.1)",
4dfd09b53 [align === "right" ? "right" : "left"]: 0,
4dfd09b54 }}
4dfd09b55 >
4dfd09b56 {children}
4dfd09b57 </div>
4dfd09b58 )}
4dfd09b59 </div>
4dfd09b60 );
4dfd09b61}
4dfd09b62
4dfd09b63export interface DropdownItemProps
4dfd09b64 extends React.ButtonHTMLAttributes<HTMLButtonElement> {
4dfd09b65 active?: boolean;
4dfd09b66}
4dfd09b67
4dfd09b68export function DropdownItem({
4dfd09b69 active = false,
4dfd09b70 className = "",
4dfd09b71 style,
4dfd09b72 children,
4dfd09b73 ...props
4dfd09b74}: DropdownItemProps) {
4dfd09b75 return (
4dfd09b76 <button
4dfd09b77 className={`w-full text-left text-sm px-3 py-1.5 ${className}`}
4dfd09b78 style={{
4dfd09b79 background: active ? "var(--bg-hover)" : "none",
4dfd09b80 border: "none",
4dfd09b81 color: "var(--text-primary)",
4dfd09b82 cursor: "pointer",
4dfd09b83 font: "inherit",
4dfd09b84 fontSize: "inherit",
4dfd09b85 ...style,
4dfd09b86 }}
4dfd09b87 onMouseEnter={(e) => {
4dfd09b88 (e.currentTarget as HTMLButtonElement).style.backgroundColor =
4dfd09b89 "var(--bg-hover)";
4dfd09b90 }}
4dfd09b91 onMouseLeave={(e) => {
4dfd09b92 (e.currentTarget as HTMLButtonElement).style.backgroundColor = active
4dfd09b93 ? "var(--bg-hover)"
4dfd09b94 : "transparent";
4dfd09b95 }}
4dfd09b96 {...props}
4dfd09b97 >
4dfd09b98 {children}
4dfd09b99 </button>
4dfd09b100 );
4dfd09b101}