'use client'; import './menu.css'; import type { ComponentChildren } from 'preact'; import { useId, useRef } from 'preact/hooks'; import { iCheck } from './icons'; import { Icon } from './icon'; /** A button and its popover panel, placed at the button; picking an item, an outside click or Escape closes it. */ export function Menu({ summary, summaryClass, label, tip, align = 'right', wide, class: cls, children, }: { summary: ComponentChildren; summaryClass?: string; label?: string; /** A styled tooltip in place of the native title, where the layout shows one. */ tip?: string; align?: 'right' | 'up' | 'left'; wide?: boolean; class?: string; children: ComponentChildren; }) { const id = useId(); return (
{ // A tall menu scrolls within the room on its side of the button instead of leaving the viewport. if ((event as ToggleEvent).newState !== 'open') return; const pop = event.currentTarget as HTMLElement; const box = pop.parentElement?.querySelector('button')?.getBoundingClientRect(); if (!box) return; const room = align === 'up' ? box.top - 20 : window.innerHeight - box.bottom - 20; pop.style.maxHeight = `${Math.max(120, Math.round(room))}px`; }} onClick={event => { if ((event.target as Element).closest('a, button:not([data-keep])')) event.currentTarget.hidePopover(); }} > {children}
); } export function MenuItem({ icon, title, sub, href, onClick, danger, }: { icon?: string; title: ComponentChildren; sub?: ComponentChildren; /** An outside page; in-app links use Link. */ href?: string; onClick?: () => void; /** Destructive: red on hover. */ danger?: boolean; }) { const inner = ( <> {icon ? : null} {title} {sub ? {sub} : null} ); const cls = `pop-item${sub ? ' rich' : ''}${danger ? ' danger' : ''}`; if (href) return ( {inner} ); return ( ); } /** A fixed "+n more" that opens the rest in a scrolling list, on hover or click. */ export function MoreList({ label, children }: { label: string; children: ComponentChildren }) { const id = useId(); const list = useRef(null); const hover = (open: boolean) => (event: PointerEvent) => { if (event.pointerType === 'mouse') list.current!.togglePopover(open); }; return (
{children}
); } /** One choice in a menu section; the picked one shows a check. */ export function MenuRadio({ on, onPick, children, }: { on: boolean; onPick: () => void; children: ComponentChildren; }) { return ( ); }