From 719b5f4dbfd296b0bade7a4623b3853ca5b6dd48 Mon Sep 17 00:00:00 2001 From: Gururaj J <89023023+Gururajj77@users.noreply.github.com> Date: Wed, 10 Apr 2024 19:46:47 +0530 Subject: [PATCH] refactor(headermenu): added typescript types to headermenu (#16116) * refactor(headermenu): added typescript types to headermenu * Update packages/react/src/components/UIShell/HeaderMenu.tsx taylor's suggestion Co-authored-by: Taylor Jones * refactor(headermenu): changed customclassname to string literal * refactor(headermenu): added proptype comments to interface --------- Co-authored-by: Taylor Jones --- .../UIShell/{HeaderMenu.js => HeaderMenu.tsx} | 106 +++++++++++++++--- 1 file changed, 93 insertions(+), 13 deletions(-) rename packages/react/src/components/UIShell/{HeaderMenu.js => HeaderMenu.tsx} (76%) diff --git a/packages/react/src/components/UIShell/HeaderMenu.js b/packages/react/src/components/UIShell/HeaderMenu.tsx similarity index 76% rename from packages/react/src/components/UIShell/HeaderMenu.js rename to packages/react/src/components/UIShell/HeaderMenu.tsx index 383141a8b91e..7944554674ce 100644 --- a/packages/react/src/components/UIShell/HeaderMenu.js +++ b/packages/react/src/components/UIShell/HeaderMenu.tsx @@ -21,7 +21,80 @@ import { composeEventHandlers } from '../../tools/events'; * with managing focus. It also passes along refs to each child so that it can * help manage focus state of its children. */ -class HeaderMenu extends React.Component { + +interface HeaderMenuProps { + /** + * Required props for the accessibility label of the menu + */ + 'aria-label'?: string; + 'aria-labelledby'?: string; + + /** + * Optionally provide a custom class to apply to the underlying `
  • ` node + */ + className?: string; + + /** + * Provide a custom ref handler for the menu button + */ + focusRef?: React.Ref; + + /** + * Applies selected styles to the item if a user sets this to true and `aria-current !== 'page'`. + */ + isActive?: boolean; + + /** + * Applies selected styles to the item if a user sets this to true and `aria-current !== 'page'`. + * @deprecated Please use `isActive` instead. This will be removed in the next major release. + */ + isCurrentPage?: boolean; + + /** + * Provide a label for the link text + */ + menuLinkName: string; + + /** + * Optionally provide an onBlur handler that is called when the underlying + * button fires it's onblur event + */ + onBlur?: (event: React.FocusEvent) => void; + + /** + * Optionally provide an onClick handler that is called when the underlying + * button fires it's onclick event + */ + onClick?: (event: React.MouseEvent) => void; + + /** + * Optionally provide an onKeyDown handler that is called when the underlying + * button fires it's onkeydown event + */ + onKeyDown?: (event: React.KeyboardEvent) => void; + + /** + * Optional component to render instead of string + */ + renderMenuContent?: () => JSX.Element; + + /** + * Optionally provide a tabIndex for the underlying menu button + */ + tabIndex?: number; + + /** + * The children should be a series of `HeaderMenuItem` components. + */ + children?: React.ReactNode; +} + +interface HeaderMenuState { + expanded: boolean; + selectedIndex: number | null; +} + +class HeaderMenu extends React.Component { static propTypes = { /** * Required props for the accessibility label of the menu @@ -89,7 +162,9 @@ class HeaderMenu extends React.Component { static contextType = PrefixContext; - _subMenus = React.createRef(); + _subMenus: React.RefObject = React.createRef(); + private items: Array = []; + private menuButtonRef: HTMLElement | null = null; constructor(props) { super(props); @@ -163,8 +238,10 @@ class HeaderMenu extends React.Component { * button node when that child should receive focus. */ handleMenuButtonRef = (node) => { - if (this.props.focusRef) { - this.props.focusRef(node); + const { focusRef } = this.props; + // Check if focusRef is a function before calling it + if (typeof focusRef === 'function') { + focusRef(node); } this.menuButtonRef = node; }; @@ -189,7 +266,9 @@ class HeaderMenu extends React.Component { })); // Return focus to menu button when the user hits ESC. - this.menuButtonRef.focus(); + if (this.menuButtonRef !== null) { + this.menuButtonRef.focus(); + } return; } }; @@ -205,7 +284,8 @@ class HeaderMenu extends React.Component { children, renderMenuContent: MenuContent, menuLinkName, - focusRef, // eslint-disable-line no-unused-vars + // eslint-disable-next-line @typescript-eslint/no-unused-vars + focusRef, onBlur, onClick, onKeyDown, @@ -215,10 +295,10 @@ class HeaderMenu extends React.Component { const hasActiveDescendant = (childrenArg) => React.Children.toArray(childrenArg).some( (child) => - child.props && + React.isValidElement(child) && // This is the type guard (child.props.isActive || child.props.isCurrentPage || - (child.props.children instanceof Array && + (Array.isArray(child.props.children) && hasActiveDescendant(child.props.children))) ); @@ -228,9 +308,9 @@ class HeaderMenu extends React.Component { }; const itemClassName = cx({ [`${prefix}--header__submenu`]: true, - [customClassName]: !!customClassName, + [`${customClassName}`]: !!customClassName, }); - let isActivePage = isActive ? isActive : isCurrentPage; + const isActivePage = isActive ? isActive : isCurrentPage; const linkClassName = cx({ [`${prefix}--header__menu-item`]: true, [`${prefix}--header__menu-title`]: true, @@ -286,9 +366,9 @@ class HeaderMenu extends React.Component { * `tabIndex: -1` so the user won't hit a large number of items in their tab * sequence when they might not want to go through all the items. */ - _renderMenuItem = (item, index) => { + _renderMenuItem = (item: React.ReactNode, index: number) => { if (React.isValidElement(item)) { - return React.cloneElement(item, { + return React.cloneElement(item as React.ReactElement, { ref: this.handleItemRef(index), }); } @@ -296,7 +376,7 @@ class HeaderMenu extends React.Component { } const HeaderMenuForwardRef = React.forwardRef((props, ref) => { - return ; + return ; }); HeaderMenuForwardRef.displayName = 'HeaderMenu';