1- import PropTypes from 'prop-types' ;
21import React , {
32 useCallback ,
43 useMemo ,
@@ -14,10 +13,6 @@ import { usePrevious } from '../../common/usePrevious';
1413 * Menubar manages a collection of menu items and their submenus. It provides keyboard navigation,
1514 * focus and state management, and other accessibility features for the menu items and submenus.
1615 *
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- *
2116 * @example
2217 * <Menubar>
2318 * <MenubarSubmenu id="file" title="File">
@@ -26,16 +21,26 @@ import { usePrevious } from '../../common/usePrevious';
2621 * </Menubar>
2722 */
2823
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 ) ;
3439
35- const menuItems = useRef ( new Set ( ) ) . current ;
40+ const menuItems = useRef < Set < HTMLUListElement > > ( new Set ( ) ) . current ;
3641 const menuItemToId = useRef ( new Map ( ) ) . current ;
3742
38- const timerRef = useRef ( null ) ;
43+ const timerRef = useRef < ReturnType < typeof setTimeout > | null > ( null ) ;
3944
4045 const getMenuId = useCallback (
4146 ( index ) => {
@@ -85,7 +90,7 @@ function Menubar({ children, className }) {
8590
8691 const toggleMenuOpen = useCallback ( ( id ) => {
8792 setMenuOpen ( ( prevState ) => ( prevState === id ? 'none' : id ) ) ;
88- } ) ;
93+ } , [ ] ) ;
8994
9095 const registerTopLevelItem = useCallback (
9196 ( ref , submenuId ) => {
@@ -105,7 +110,7 @@ function Menubar({ children, className }) {
105110 ) ;
106111
107112 const clearHideTimeout = useCallback ( ( ) => {
108- if ( timerRef . current ) {
113+ if ( timerRef . current !== null ) {
109114 clearTimeout ( timerRef . current ) ;
110115 timerRef . current = null ;
111116 }
@@ -116,7 +121,7 @@ function Menubar({ children, className }) {
116121 setMenuOpen ( 'none' ) ;
117122 } , [ setMenuOpen ] ) ;
118123
119- const nodeRef = useModalClose ( handleClose ) ;
124+ const nodeRef = useModalClose < HTMLUListElement > ( handleClose ) ;
120125
121126 const handleFocus = useCallback ( ( ) => {
122127 setHasFocus ( true ) ;
@@ -138,7 +143,7 @@ function Menubar({ children, className }) {
138143 [ nodeRef ]
139144 ) ;
140145
141- const keyHandlers = {
146+ const keyHandlers : Record < string , ( e : React . KeyboardEvent ) => void > = {
142147 ArrowLeft : ( e ) => {
143148 e . preventDefault ( ) ;
144149 e . stopPropagation ( ) ;
@@ -173,8 +178,11 @@ function Menubar({ children, className }) {
173178 useEffect ( ( ) => {
174179 if ( activeIndex !== prevIndex ) {
175180 const items = Array . from ( menuItems ) ;
181+ const prevNode =
182+ prevIndex != null /** check against undefined or null */
183+ ? items [ prevIndex ]
184+ : undefined ;
176185 const activeNode = items [ activeIndex ] ;
177- const prevNode = items [ prevIndex ] ;
178186
179187 prevNode ?. setAttribute ( 'tabindex' , '-1' ) ;
180188 activeNode ?. setAttribute ( 'tabindex' , '0' ) ;
@@ -191,7 +199,7 @@ function Menubar({ children, className }) {
191199
192200 const contextValue = useMemo (
193201 ( ) => ( {
194- createMenuHandlers : ( menu ) => ( {
202+ createMenuHandlers : ( menu : string ) => ( {
195203 onMouseOver : ( ) => {
196204 setMenuOpen ( ( prevState ) => ( prevState === 'none' ? 'none' : menu ) ) ;
197205 } ,
@@ -210,8 +218,8 @@ function Menubar({ children, className }) {
210218 onBlur : handleBlur ,
211219 onFocus : clearHideTimeout
212220 } ) ,
213- createMenuItemHandlers : ( menu ) => ( {
214- onMouseUp : ( e ) => {
221+ createMenuItemHandlers : ( menu : string ) => ( {
222+ onMouseUp : ( e : React . MouseEvent ) => {
215223 if ( e . button === 2 ) {
216224 return ;
217225 }
@@ -278,15 +286,3 @@ function Menubar({ children, className }) {
278286 </ MenubarContext . Provider >
279287 ) ;
280288}
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