@equinor/eds-core-react
Version:
The React implementation of the Equinor Design System
104 lines (100 loc) • 3.49 kB
JavaScript
import { forwardRef, useMemo, Children, cloneElement, useEffect, isValidElement } from 'react';
import styled from 'styled-components';
import { useMenu } from './Menu.context.js';
import { MenuItem } from './MenuItem.js';
import { MenuSection } from './MenuSection.js';
import { menu } from './Menu.tokens.js';
import { spacingsTemplate } from '@equinor/eds-utils';
import { jsx } from 'react/jsx-runtime';
const List = styled.div.withConfig({
displayName: "MenuList__List",
componentId: "sc-104rzof-0"
})(["position:relative;list-style:none;display:flex;flex-direction:column;margin:0;", " li:first-child{z-index:3;}"], spacingsTemplate(menu.spacings));
function isIndexable(item) {
if (/*#__PURE__*/isValidElement(item) && !item.props.disabled && item.type === MenuItem) return true;
return false;
}
function closeMenuOnClick(item) {
if (/*#__PURE__*/isValidElement(item) && item.type === MenuItem && item.props.closeMenuOnClick !== false) return true;
return false;
}
const MenuList = /*#__PURE__*/forwardRef(function MenuList({
addCloseMenuOnClickIndex,
children,
...rest
}, ref) {
const {
focusedIndex,
setFocusedIndex,
initialFocus
} = useMenu();
let index = -1;
const focusableIndexs = useMemo(() => [], []);
const updatedChildren = useMemo(() => Children.map(children, child => {
if (!child) return child;
if (child.type === MenuSection) {
index++;
const menuSectionIndex = index;
const updatedGrandChildren = Children.map(child.props.children, grandChild => {
index++;
if (isIndexable(grandChild)) focusableIndexs.push(index);
return /*#__PURE__*/cloneElement(grandChild, {
index
});
});
return /*#__PURE__*/cloneElement(child, {
index: menuSectionIndex
}, updatedGrandChildren);
} else {
index++;
if (isIndexable(child)) focusableIndexs.push(index);
if (closeMenuOnClick(child)) {
addCloseMenuOnClickIndex(index);
}
return /*#__PURE__*/cloneElement(child, {
index
});
}
}), [children, focusableIndexs, index, addCloseMenuOnClickIndex]);
const firstFocusIndex = focusableIndexs[0];
const lastFocusIndex = focusableIndexs[focusableIndexs.length - 1];
useEffect(() => {
if (initialFocus === 'first') {
setFocusedIndex(firstFocusIndex);
}
if (initialFocus === 'last') {
setFocusedIndex(lastFocusIndex);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [initialFocus, firstFocusIndex, lastFocusIndex]);
const handleMenuItemChange = (direction, fallbackIndex) => {
const i = direction === 'down' ? 1 : -1;
const currentFocus = focusableIndexs.indexOf(focusedIndex);
const nextMenuItem = focusableIndexs[currentFocus + i];
const nextFocusedIndex = typeof nextMenuItem === 'undefined' ? fallbackIndex : nextMenuItem;
setFocusedIndex(nextFocusedIndex);
};
const handleKeyPress = event => {
const {
key
} = event;
event.stopPropagation();
if (key === 'ArrowDown') {
event.preventDefault();
handleMenuItemChange('down', firstFocusIndex);
}
if (key === 'ArrowUp') {
event.preventDefault();
handleMenuItemChange('up', lastFocusIndex);
}
};
return /*#__PURE__*/jsx(List, {
onKeyDown: handleKeyPress,
role: "menu",
...rest,
ref: ref,
children: updatedChildren
});
});
// MenuList.displayName = 'EdsMenuList'
export { MenuList };