'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 (
);
}