Spaces:
Sleeping
Sleeping
Download frontend/src/components/HeaderNavDropdown.tsx from BrainForge/AdvisoryBuilderWorkshop: direct link, hf CLI and curl.
- Browser
- Download file 3 kB
-
https://huggingface.co/spaces/BrainForge/AdvisoryBuilderWorkshop/resolve/main/frontend/src/components/HeaderNavDropdown.tsx
- Command line
-
hf download hf://spaces/BrainForge/AdvisoryBuilderWorkshop/frontend/src/components/HeaderNavDropdown.tsx
-
curl -L -o HeaderNavDropdown.tsx https://huggingface.co/spaces/BrainForge/AdvisoryBuilderWorkshop/resolve/main/frontend/src/components/HeaderNavDropdown.tsx
3 kB
| 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<HTMLDivElement>(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 ( | |
| <div | |
| className="header-nav-dropdown" | |
| ref={ref} | |
| onMouseEnter={() => setHoverOpen(true)} | |
| onMouseLeave={() => { | |
| if (!pinnedOpen) setHoverOpen(false) | |
| }} | |
| > | |
| <button | |
| type="button" | |
| className={triggerClass} | |
| aria-expanded={open} | |
| aria-haspopup="menu" | |
| aria-controls={menuId} | |
| onClick={() => { | |
| const next = !pinnedOpen | |
| onPinnedChange(next) | |
| setHoverOpen(next) | |
| }} | |
| > | |
| <span>{label}</span> | |
| <span className="header-nav-caret" aria-hidden> | |
| ▾ | |
| </span> | |
| </button> | |
| {open && ( | |
| <div className="header-nav-menu" role="menu" id={menuId}> | |
| {items.map((item) => ( | |
| <Link | |
| key={item.to} | |
| to={item.to} | |
| role="menuitem" | |
| className="header-nav-menu-item" | |
| onClick={closeAll} | |
| > | |
| {item.label} | |
| </Link> | |
| ))} | |
| </div> | |
| )} | |
| </div> | |
| ) | |
| } | |