1
- import PropTypes from 'prop-types' ;
2
1
import React , {
3
2
useCallback ,
4
3
useMemo ,
@@ -14,10 +13,6 @@ import { usePrevious } from '../../common/usePrevious';
14
13
* Menubar manages a collection of menu items and their submenus. It provides keyboard navigation,
15
14
* focus and state management, and other accessibility features for the menu items and submenus.
16
15
*
17
- * @param {React.ReactNode } props.children - Menu items that will be rendered in the menubar
18
- * @param {string } [props.className='nav__menubar'] - CSS class name to apply to the menubar
19
- * @returns {JSX.Element }
20
- *
21
16
* @example
22
17
* <Menubar>
23
18
* <MenubarSubmenu id="file" title="File">
@@ -26,16 +21,26 @@ import { usePrevious } from '../../common/usePrevious';
26
21
* </Menubar>
27
22
*/
28
23
29
- function Menubar ( { children, className } ) {
30
- const [ menuOpen , setMenuOpen ] = useState ( 'none' ) ;
31
- const [ activeIndex , setActiveIndex ] = useState ( 0 ) ;
32
- const prevIndex = usePrevious ( activeIndex ) ;
33
- const [ hasFocus , setHasFocus ] = useState ( false ) ;
24
+ export interface MenubarProps {
25
+ /** Menu items that will be rendered in the menubar */
26
+ children ?: React . ReactNode ;
27
+ /** CSS class name to apply to the menubar */
28
+ className ?: string ;
29
+ }
30
+
31
+ export function Menubar ( {
32
+ children,
33
+ className = 'nav__menubar'
34
+ } : MenubarProps ) {
35
+ const [ menuOpen , setMenuOpen ] = useState < string > ( 'none' ) ;
36
+ const [ activeIndex , setActiveIndex ] = useState < number > ( 0 ) ;
37
+ const prevIndex = usePrevious < number > ( activeIndex ) ;
38
+ const [ hasFocus , setHasFocus ] = useState < boolean > ( false ) ;
34
39
35
- const menuItems = useRef ( new Set ( ) ) . current ;
40
+ const menuItems = useRef < Set < HTMLUListElement > > ( new Set ( ) ) . current ;
36
41
const menuItemToId = useRef ( new Map ( ) ) . current ;
37
42
38
- const timerRef = useRef ( null ) ;
43
+ const timerRef = useRef < ReturnType < typeof setTimeout > | null > ( null ) ;
39
44
40
45
const getMenuId = useCallback (
41
46
( index ) => {
@@ -85,7 +90,7 @@ function Menubar({ children, className }) {
85
90
86
91
const toggleMenuOpen = useCallback ( ( id ) => {
87
92
setMenuOpen ( ( prevState ) => ( prevState === id ? 'none' : id ) ) ;
88
- } ) ;
93
+ } , [ ] ) ;
89
94
90
95
const registerTopLevelItem = useCallback (
91
96
( ref , submenuId ) => {
@@ -105,7 +110,7 @@ function Menubar({ children, className }) {
105
110
) ;
106
111
107
112
const clearHideTimeout = useCallback ( ( ) => {
108
- if ( timerRef . current ) {
113
+ if ( timerRef . current !== null ) {
109
114
clearTimeout ( timerRef . current ) ;
110
115
timerRef . current = null ;
111
116
}
@@ -116,7 +121,7 @@ function Menubar({ children, className }) {
116
121
setMenuOpen ( 'none' ) ;
117
122
} , [ setMenuOpen ] ) ;
118
123
119
- const nodeRef = useModalClose ( handleClose ) ;
124
+ const nodeRef = useModalClose < HTMLUListElement > ( handleClose ) ;
120
125
121
126
const handleFocus = useCallback ( ( ) => {
122
127
setHasFocus ( true ) ;
@@ -138,7 +143,7 @@ function Menubar({ children, className }) {
138
143
[ nodeRef ]
139
144
) ;
140
145
141
- const keyHandlers = {
146
+ const keyHandlers : Record < string , ( e : React . KeyboardEvent ) => void > = {
142
147
ArrowLeft : ( e ) => {
143
148
e . preventDefault ( ) ;
144
149
e . stopPropagation ( ) ;
@@ -173,8 +178,11 @@ function Menubar({ children, className }) {
173
178
useEffect ( ( ) => {
174
179
if ( activeIndex !== prevIndex ) {
175
180
const items = Array . from ( menuItems ) ;
181
+ const prevNode =
182
+ prevIndex != null /** check against undefined or null */
183
+ ? items [ prevIndex ]
184
+ : undefined ;
176
185
const activeNode = items [ activeIndex ] ;
177
- const prevNode = items [ prevIndex ] ;
178
186
179
187
prevNode ?. setAttribute ( 'tabindex' , '-1' ) ;
180
188
activeNode ?. setAttribute ( 'tabindex' , '0' ) ;
@@ -191,7 +199,7 @@ function Menubar({ children, className }) {
191
199
192
200
const contextValue = useMemo (
193
201
( ) => ( {
194
- createMenuHandlers : ( menu ) => ( {
202
+ createMenuHandlers : ( menu : string ) => ( {
195
203
onMouseOver : ( ) => {
196
204
setMenuOpen ( ( prevState ) => ( prevState === 'none' ? 'none' : menu ) ) ;
197
205
} ,
@@ -210,8 +218,8 @@ function Menubar({ children, className }) {
210
218
onBlur : handleBlur ,
211
219
onFocus : clearHideTimeout
212
220
} ) ,
213
- createMenuItemHandlers : ( menu ) => ( {
214
- onMouseUp : ( e ) => {
221
+ createMenuItemHandlers : ( menu : string ) => ( {
222
+ onMouseUp : ( e : React . MouseEvent ) => {
215
223
if ( e . button === 2 ) {
216
224
return ;
217
225
}
@@ -278,15 +286,3 @@ function Menubar({ children, className }) {
278
286
</ MenubarContext . Provider >
279
287
) ;
280
288
}
281
-
282
- Menubar . propTypes = {
283
- children : PropTypes . node ,
284
- className : PropTypes . string
285
- } ;
286
-
287
- Menubar . defaultProps = {
288
- children : null ,
289
- className : 'nav__menubar'
290
- } ;
291
-
292
- export default Menubar ;
0 commit comments