import { useEffect, useId, useRef, useState } from 'react' import { Link } from 'react-router-dom' export type HeaderNavItem = { to: string label: string } type Props = { label: string active: boolean items: HeaderNavItem[] /** When set with onPinnedChange, pin state is controlled by the parent. */ pinnedOpen: boolean onPinnedChange: (pinned: boolean) => void } /** * Header topic menu: hover opens transiently; click pins until click again, * menu item navigation, Escape, or outside click. */ export function HeaderNavDropdown({ label, active, items, pinnedOpen, onPinnedChange, }: Props) { const menuId = useId() const ref = useRef(null) const onPinnedChangeRef = useRef(onPinnedChange) const [hoverOpen, setHoverOpen] = useState(false) const open = pinnedOpen || hoverOpen useEffect(() => { onPinnedChangeRef.current = onPinnedChange }, [onPinnedChange]) useEffect(() => { if (!open) return function onPointerDown(e: MouseEvent) { if (ref.current && !ref.current.contains(e.target as Node)) { onPinnedChangeRef.current(false) setHoverOpen(false) } } function onKeyDown(e: KeyboardEvent) { if (e.key === 'Escape') { onPinnedChangeRef.current(false) setHoverOpen(false) } } document.addEventListener('mousedown', onPointerDown) document.addEventListener('keydown', onKeyDown) return () => { document.removeEventListener('mousedown', onPointerDown) document.removeEventListener('keydown', onKeyDown) } }, [open]) const closeAll = () => { onPinnedChange(false) setHoverOpen(false) } const triggerClass = `header-nav-link header-nav-trigger${ active ? ' header-nav-link--current' : '' }${open ? ' header-nav-trigger--open' : ''}` return (
setHoverOpen(true)} onMouseLeave={() => { if (!pinnedOpen) setHoverOpen(false) }} > {open && ( )}
) }