import { useCallback, useEffect, useRef, useState, type ReactNode, } from 'react'; interface DropdownProps { trigger: (opts: { open: boolean }) => ReactNode; children: (opts: { close: () => void }) => ReactNode; align?: 'left' | 'right'; className?: string; } export function Dropdown({ trigger, children, align = 'left', className, }: DropdownProps) { const [open, setOpen] = useState(false); const rootRef = useRef(null); const close = useCallback(() => setOpen(false), []); useEffect(() => { if (!open) return; const handlePointer = (e: MouseEvent) => { if (!rootRef.current) return; if (!rootRef.current.contains(e.target as Node)) setOpen(false); }; const handleKey = (e: KeyboardEvent) => { if (e.key === 'Escape') setOpen(false); }; document.addEventListener('mousedown', handlePointer); document.addEventListener('keydown', handleKey); return () => { document.removeEventListener('mousedown', handlePointer); document.removeEventListener('keydown', handleKey); }; }, [open]); return (
setOpen((v) => !v)} role="button" tabIndex={0} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setOpen((v) => !v); } }} > {trigger({ open })}
{open && (
{children({ close })}
)}
); } interface DropdownItemProps { onClick: () => void; children: ReactNode; checked?: boolean; hint?: string; tone?: 'default' | 'warning'; } export function DropdownItem({ onClick, children, checked, hint, tone = 'default', }: DropdownItemProps) { return ( ); }