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';