"use client"; import Link from "next/link"; import { useEffect, useId, useRef, useState, type KeyboardEvent } from "react"; export interface ActionsMenuItem { key: string; label: string; /** Entweder Link (Abo verwalten, Download) oder Aktion (Dialog oeffnen). */ href?: string; download?: boolean; onSelect?: () => void; } /** * Kleines Aktionsmenue im Kopf der Detailseite (Muster "menu button" der * WAI-ARIA Authoring Practices): Pfeiltasten wandern, Escape schliesst und * gibt den Fokus an den Knopf zurueck, ein Klick daneben schliesst ebenfalls. */ export function ActionsMenu({ label, items }: { label: string; items: readonly ActionsMenuItem[] }) { const [open, setOpen] = useState(false); const buttonRef = useRef(null); const menuRef = useRef(null); const menuId = useId(); useEffect(() => { if (!open) return; menuRef.current?.querySelector("[role=menuitem]")?.focus(); function onPointerDown(event: PointerEvent) { const target = event.target as Node; if (!menuRef.current?.contains(target) && !buttonRef.current?.contains(target)) setOpen(false); } document.addEventListener("pointerdown", onPointerDown); return () => document.removeEventListener("pointerdown", onPointerDown); }, [open]); function close(returnFocus: boolean) { setOpen(false); if (returnFocus) buttonRef.current?.focus(); } function onMenuKeyDown(event: KeyboardEvent) { const entries = Array.from(menuRef.current?.querySelectorAll("[role=menuitem]") ?? []); const index = entries.indexOf(document.activeElement as HTMLElement); if (event.key === "Escape") { event.preventDefault(); close(true); } else if (event.key === "ArrowDown" || event.key === "ArrowUp") { event.preventDefault(); const step = event.key === "ArrowDown" ? 1 : -1; entries[(index + step + entries.length) % entries.length]?.focus(); } else if (event.key === "Home" || event.key === "End") { event.preventDefault(); (event.key === "Home" ? entries[0] : entries.at(-1))?.focus(); } else if (event.key === "Tab") { setOpen(false); } } const itemClass = "flex w-full items-center px-4 py-3 text-left text-sm text-ink transition-colors hover:bg-honey/10 focus-visible:bg-honey/10 focus-visible:outline-none"; return (
{open ? ( ) : null}
); }