Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107import React, { useEffect, useId, useRef, useState } from "react";import { DotsThree } from "@phosphor-icons/react";import { IconButton } from "./ui.jsx";
export function ActionMenu({ label, items, tooltip = false }) { const [open, setOpen] = useState(false); const root = useRef(null); const trigger = useRef(null); const menu = useRef(null); const id = useId();
const close = (restoreFocus = false) => { setOpen(false); if (restoreFocus) trigger.current?.focus(); };
useEffect(() => { if (!open) return; menu.current?.querySelector('[role="menuitem"]')?.focus(); const outside = (event) => { if (!root.current?.contains(event.target)) setOpen(false); }; document.addEventListener("pointerdown", outside); return () => document.removeEventListener("pointerdown", outside); }, [open]);
const navigate = (event) => { if (event.key === "Escape") { event.preventDefault(); event.stopPropagation(); close(true); return; } if (event.key === "Tab") { close(true); return; } if (!["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) return; event.preventDefault(); const buttons = [...menu.current.querySelectorAll('[role="menuitem"]')]; const current = buttons.indexOf(document.activeElement); const next = event.key === "Home" ? 0 : event.key === "End" ? buttons.length - 1 : (current + (event.key === "ArrowDown" ? 1 : -1) + buttons.length) % buttons.length; buttons[next]?.focus(); };
return ( <div className="action-menu" ref={root}> <IconButton ref={trigger} label={label} tooltip={tooltip} aria-haspopup="menu" aria-expanded={open} aria-controls={open ? id : undefined} onClick={() => setOpen((value) => !value)} onKeyDown={(event) => { if (["ArrowDown", "ArrowUp"].includes(event.key)) { event.preventDefault(); setOpen(true); } }} > <DotsThree size={23} weight="bold" /> </IconButton> {open && ( <div className="action-menu-items" id={id} role="menu" aria-label={label} ref={menu} onKeyDown={navigate} > {items.map(({ label: title, icon: Icon, href, onSelect, danger }) => { const props = { role: "menuitem", tabIndex: -1, className: danger ? "danger" : "", onClick: () => { close(true); onSelect?.(); }, }; return href ? ( <a key={title} href={href} download {...props}> {Icon && <Icon size={17} />} {title} </a> ) : ( <button key={title} type="button" {...props}> {Icon && <Icon size={17} />} {title} </button> ); })} </div> )} </div> );}