"use client"; import * as React from "react"; import { cn } from "./lib/utils"; export interface FlyoutMenuItem { id: string; label: string; shortcut?: string; } export interface FlyoutMenuProps { trigger: React.ReactNode; items: FlyoutMenuItem[]; onSelect?: (id: string) => void; className?: string; } export function FlyoutMenu({ trigger, items, onSelect, className, }: FlyoutMenuProps) { const [open, setOpen] = React.useState(false); const rootRef = React.useRef(null); const handleTriggerKeyDown = React.useCallback( (event: React.KeyboardEvent) => { if (event.key === "ArrowDown" || event.key === "Enter" || event.key === " ") { event.preventDefault(); setOpen(true); } else if (event.key === "Escape") { setOpen(false); } }, [] ); const handleBlur = React.useCallback((event: React.FocusEvent) => { const next = event.relatedTarget as Node | null; if (next && rootRef.current?.contains(next)) return; setOpen(false); }, []); const handleRootKeyDown = React.useCallback((event: React.KeyboardEvent) => { if (event.key === "Escape") setOpen(false); }, []); const prevTriggerProps = React.isValidElement>(trigger) ? trigger.props : undefined; const renderedTrigger = React.isValidElement>(trigger) ? React.cloneElement(trigger, { onClick: (event: React.MouseEvent) => { prevTriggerProps?.onClick?.(event); if (!event.defaultPrevented) setOpen((o) => !o); }, onKeyDown: (event: React.KeyboardEvent) => { prevTriggerProps?.onKeyDown?.(event); if (!event.defaultPrevented) handleTriggerKeyDown(event); }, "aria-expanded": open, "aria-haspopup": "menu", }) : trigger; return (
{renderedTrigger} {open && (
{items.map((item) => { const handleClick = () => { onSelect?.(item.id); setOpen(false); }; return ( ); })}
)}
); }