Files
writer-work-flow/apps/web/components/AiToolbarMoreMenu.tsx
2026-06-28 07:31:20 +02:00

95 lines
2.7 KiB
TypeScript

"use client";
import Link from "next/link";
import { MoreHorizontal } from "lucide-react";
import { useEffect, useId, useRef, useState } from "react";
import type { AiToolItem } from "@/lib/nav/ai-tools";
import type { ActiveNav } from "@/lib/nav/items";
import { buttonClass, cn } from "@/lib/ui/variants";
interface AiToolbarMoreMenuProps {
items: AiToolItem[];
activeNav?: ActiveNav;
}
export function AiToolbarMoreMenu({
items,
activeNav,
}: AiToolbarMoreMenuProps) {
const [open, setOpen] = useState(false);
const menuId = useId();
const rootRef = useRef<HTMLDivElement>(null);
const buttonRef = useRef<HTMLButtonElement>(null);
useEffect(() => {
if (!open) return;
const onKeyDown = (event: KeyboardEvent): void => {
if (event.key === "Escape") {
setOpen(false);
buttonRef.current?.focus();
}
};
const onPointerDown = (event: PointerEvent): void => {
const target = event.target;
if (target instanceof Node && !rootRef.current?.contains(target)) {
setOpen(false);
}
};
window.addEventListener("keydown", onKeyDown);
window.addEventListener("pointerdown", onPointerDown);
return () => {
window.removeEventListener("keydown", onKeyDown);
window.removeEventListener("pointerdown", onPointerDown);
};
}, [open]);
return (
<div ref={rootRef} className="relative">
<button
ref={buttonRef}
type="button"
aria-haspopup="menu"
aria-expanded={open}
aria-controls={menuId}
onClick={() => setOpen((value) => !value)}
className={buttonClass({ variant: "secondary", size: "sm" })}
>
<MoreHorizontal className="h-4 w-4" aria-hidden="true" />
</button>
{open ? (
<div
id={menuId}
role="menu"
className="absolute right-0 z-30 mt-2 min-w-36 rounded border border-line bg-panel p-1 shadow-paper"
>
{items.map((item) => {
const active = item.key === activeNav;
return (
<Link
key={item.href}
href={item.href}
role="menuitem"
aria-current={active ? "page" : undefined}
onClick={() => setOpen(false)}
className={cn(
"block rounded px-3 py-2 text-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cinnabar/35",
active
? "bg-[var(--color-cinnabar-wash)] text-cinnabar"
: "text-ink hover:bg-bg hover:text-cinnabar",
)}
>
{item.label}
</Link>
);
})}
</div>
) : null}
</div>
);
}