1+ import { ReactNode , SyntheticEvent , useCallback , useEffect , useRef , useState } from "react" ;
2+ import { twMerge } from "tailwind-merge" ;
3+ import ChevronDownIcon from "@heroicons/react/24/solid/ChevronDownIcon" ;
4+
5+ import { style as navlinkStyle } from "./NavLink" ;
6+ import { style as linkStyle } from "./Link" ;
7+ import useOutsideClick from "@/hooks/useOutsideClick" ;
8+ import useHover from "@/hooks/useHover" ;
9+
10+ export interface NavDropdownProps {
11+ summary : ReactNode ;
12+ children : ReactNode ;
13+ }
14+
15+ const SUMMARY_CLASSNAME = twMerge (
16+ navlinkStyle ( ) + " " + linkStyle ( { color : "primary" } ) ,
17+ "flex items-center"
18+ ) ;
19+
20+ const NavDropdown = ( { summary, children } : NavDropdownProps ) => {
21+ const summaryRef = useRef < HTMLDetailsElement > ( null ) ;
22+ const containerRef = useRef < HTMLDivElement > ( null ) ;
23+
24+ const [ open , setOpen ] = useState < boolean | undefined > ( false ) ;
25+
26+ const handleToggle = useCallback ( ( e : SyntheticEvent < HTMLDetailsElement , Event > ) => {
27+ const target = e . target as HTMLDetailsElement ;
28+ setOpen ( target . open ) ;
29+ } , [ ] ) ;
30+
31+ const isSummaryHovering = useHover ( summaryRef ) ;
32+ const isContainerHovering = useHover ( containerRef ) ;
33+
34+ useEffect ( ( ) => {
35+ const isHovering = isSummaryHovering || isContainerHovering ;
36+ if ( ! isHovering ) {
37+ setOpen ( false ) ;
38+ return ;
39+ }
40+
41+ setOpen ( true ) ;
42+ } , [ isSummaryHovering , isContainerHovering ] ) ;
43+
44+ useOutsideClick ( [ summaryRef , containerRef ] , ( ) => {
45+ setOpen ( false ) ;
46+ } ) ;
47+
48+ return (
49+ < details className = "relative group/details" open = { open } onToggle = { handleToggle } >
50+ < summary ref = { summaryRef } className = { SUMMARY_CLASSNAME } >
51+ { summary }
52+ < ChevronDownIcon
53+ className = "w-5 h-5 data-[open='true']:rotate-180 transition-all"
54+ data-open = { open }
55+ />
56+ </ summary >
57+ < div ref = { containerRef } className = "absolute inset-x-0 flex flex-col gap-2 px-4 py-2 border-2 rounded-md group/dropdown border-primary bg-fill md:w-fit" data-in-group >
58+ { children }
59+ </ div >
60+ </ details >
61+ ) ;
62+ } ;
63+
64+ export default NavDropdown ;
0 commit comments