1
+ import React , { useState , useRef , useEffect } from 'react' ;
2
+ import clsx from 'clsx' ;
3
+ import {
4
+ isRegexpStringMatch ,
5
+ useCollapsible ,
6
+ Collapsible ,
7
+ } from '@docusaurus/theme-common' ;
8
+ import { isSamePath , useLocalPathname } from '@docusaurus/theme-common/internal' ;
9
+ import NavbarNavLink from '@theme/NavbarItem/NavbarNavLink' ;
10
+ import NavbarItem , { type LinkLikeNavbarItemProps } from '@theme/NavbarItem' ;
11
+ import type {
12
+ DesktopOrMobileNavBarItemProps ,
13
+ Props ,
14
+ } from '@theme/NavbarItem/DropdownNavbarItem' ;
15
+ import styles from './styles.module.scss' ;
16
+ import Link from '@docusaurus/Link' ;
17
+
18
+ function isItemActive (
19
+ item : LinkLikeNavbarItemProps ,
20
+ localPathname : string ,
21
+ ) : boolean {
22
+ if ( isSamePath ( item . to , localPathname ) ) {
23
+ return true ;
24
+ }
25
+ if ( isRegexpStringMatch ( item . activeBaseRegex , localPathname ) ) {
26
+ return true ;
27
+ }
28
+ if ( item . activeBasePath && localPathname . startsWith ( item . activeBasePath ) ) {
29
+ return true ;
30
+ }
31
+ return false ;
32
+ }
33
+
34
+ function containsActiveItems (
35
+ items : readonly LinkLikeNavbarItemProps [ ] ,
36
+ localPathname : string ,
37
+ ) : boolean {
38
+ return items . some ( ( item ) => isItemActive ( item , localPathname ) ) ;
39
+ }
40
+
41
+ function DropdownNavbarItemDesktop ( {
42
+ label,
43
+ items,
44
+ position,
45
+ className,
46
+ onClick,
47
+ ...props
48
+ } : DesktopOrMobileNavBarItemProps ) {
49
+ const dropdownRef = useRef < HTMLDivElement > ( null ) ;
50
+ const [ showDropdown , setShowDropdown ] = useState ( false ) ;
51
+
52
+ useEffect ( ( ) => {
53
+ const handleClickOutside = (
54
+ event : MouseEvent | TouchEvent | FocusEvent ,
55
+ ) => {
56
+ if (
57
+ ! dropdownRef . current ||
58
+ dropdownRef . current . contains ( event . target as Node )
59
+ ) {
60
+ return ;
61
+ }
62
+ setShowDropdown ( false ) ;
63
+ } ;
64
+
65
+ document . addEventListener ( 'mousedown' , handleClickOutside ) ;
66
+ document . addEventListener ( 'touchstart' , handleClickOutside ) ;
67
+ document . addEventListener ( 'focusin' , handleClickOutside ) ;
68
+
69
+ return ( ) => {
70
+ document . removeEventListener ( 'mousedown' , handleClickOutside ) ;
71
+ document . removeEventListener ( 'touchstart' , handleClickOutside ) ;
72
+ document . removeEventListener ( 'focusin' , handleClickOutside ) ;
73
+ } ;
74
+ } , [ dropdownRef ] ) ;
75
+
76
+ return (
77
+ < div
78
+ ref = { dropdownRef }
79
+ className = { clsx ( 'navbar__item' , 'dropdown' , 'dropdown--hoverable' , {
80
+ 'dropdown--right' : position === 'right' ,
81
+ 'dropdown--show' : showDropdown ,
82
+ } ) } >
83
+ < NavbarNavLink
84
+ aria-haspopup = "true"
85
+ aria-expanded = { showDropdown }
86
+ role = "button"
87
+ label = { label }
88
+ // # hash permits to make the <a> tag focusable in case no link target
89
+ // See https://github.com/facebook/docusaurus/pull/6003
90
+ // There's probably a better solution though...
91
+ href = { props . to ? undefined : '#' }
92
+ className = { clsx ( 'navbar__link' , className ) }
93
+ { ...props }
94
+ onClick = { props . to ? undefined : ( e ) => e . preventDefault ( ) }
95
+ onKeyDown = { ( e ) => {
96
+ if ( e . key === 'Enter' ) {
97
+ e . preventDefault ( ) ;
98
+ setShowDropdown ( ! showDropdown ) ;
99
+ }
100
+ } } >
101
+ { props . children ?? props . label }
102
+ </ NavbarNavLink >
103
+ < ul className = { clsx ( "dropdown__menu" , styles . dropdown__menu ) } >
104
+ < > </ >
105
+ { /* {items.map((childItemProps, i) => (
106
+ <NavbarItem
107
+ isDropdownItem
108
+ activeClassName="dropdown__link--active"
109
+ {...childItemProps}
110
+ key={i}
111
+ />
112
+ ))} */ }
113
+ < div className = { clsx ( styles . col , styles . wrapper ) } >
114
+ { items . map ( ( item , index ) => (
115
+ < div key = { `${ index } ` } className = { clsx ( styles . cardWrapper ) } >
116
+ < Link className = { clsx ( styles . card ) } to = { item . href } >
117
+ < div className = { clsx ( styles . icon ) } >
118
+ < img src = { item . iconImage } alt = { item . title } />
119
+ </ div >
120
+ < div className = { clsx ( styles . title ) } > { item . title } </ div >
121
+ </ Link >
122
+ </ div >
123
+ ) ) }
124
+ </ div >
125
+ </ ul >
126
+ </ div >
127
+ ) ;
128
+ }
129
+
130
+ function DropdownNavbarItemMobile ( {
131
+ items,
132
+ className,
133
+ position, // Need to destructure position from props so that it doesn't get passed on.
134
+ onClick,
135
+ ...props
136
+ } : DesktopOrMobileNavBarItemProps ) {
137
+ const localPathname = useLocalPathname ( ) ;
138
+ const containsActive = containsActiveItems ( items , localPathname ) ;
139
+
140
+ const { collapsed, toggleCollapsed, setCollapsed} = useCollapsible ( {
141
+ initialState : ( ) => ! containsActive ,
142
+ } ) ;
143
+
144
+ // Expand/collapse if any item active after a navigation
145
+ useEffect ( ( ) => {
146
+ if ( containsActive ) {
147
+ setCollapsed ( ! containsActive ) ;
148
+ }
149
+ } , [ localPathname , containsActive , setCollapsed ] ) ;
150
+
151
+ return (
152
+ < li
153
+ className = { clsx ( 'menu__list-item' , {
154
+ 'menu__list-item--collapsed' : collapsed ,
155
+ } ) } >
156
+ < NavbarNavLink
157
+ role = "button"
158
+ className = { clsx (
159
+ styles . dropdownNavbarItemMobile ,
160
+ 'menu__link menu__link--sublist menu__link--sublist-caret' ,
161
+ className ,
162
+ ) }
163
+ { ...props }
164
+ onClick = { ( e ) => {
165
+ e . preventDefault ( ) ;
166
+ toggleCollapsed ( ) ;
167
+ } } >
168
+ { props . children ?? props . label }
169
+ </ NavbarNavLink >
170
+ < Collapsible lazy as = "ul" className = "menu__list" collapsed = { collapsed } >
171
+ < > </ >
172
+ </ Collapsible >
173
+ </ li >
174
+ ) ;
175
+ }
176
+
177
+ export default function DropdownNavbarItem ( {
178
+ mobile = false ,
179
+ ...props
180
+ } : Props ) : JSX . Element {
181
+ const Comp = mobile ? DropdownNavbarItemMobile : DropdownNavbarItemDesktop ;
182
+ return < Comp { ...props } /> ;
183
+ }
0 commit comments