mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
* Navigation: Start creating new NavBarMenu component * Navigation: Apply new NavBarMenu to NavBarNext * Navigation: Remove everything to do with .sidemenu-open--xs * Navigation: Ensure search is passed to NavBarMenu * Navigation: Standardise NavBarMenuItem * This extra check isn't needed anymore * Navigation: Refactor <li> out of NavBarMenu * Navigation: Combine NavBarMenuItem with DropdownChild * use spread syntax since performance shouldn't be a concern for such small arrays * Improve active item logic * Ensure unique keys * Remove this duplicate code * Add unit tests for getActiveItem * Add tests for NavBarMenu * Rename mobileMenuOpen -> menuOpen in NavBarNext (since it can be used for mobile menu or megamenu) * just use index to key the items * Use exact versions of @react-aria packages * Navigation: Make the dropdown header a NavBarMenuItem * Navigation: Stop using dropdown-menu for styles * Navigation: Hide divider in NavBarMenu + tweak color on section header
122 lines
3.6 KiB
TypeScript
122 lines
3.6 KiB
TypeScript
import React, { useRef } from 'react';
|
|
import { GrafanaTheme2, NavModelItem } from '@grafana/data';
|
|
import { CustomScrollbar, Icon, IconButton, IconName, useTheme2 } from '@grafana/ui';
|
|
import { FocusScope } from '@react-aria/focus';
|
|
import { useOverlay } from '@react-aria/overlays';
|
|
import { css } from '@emotion/css';
|
|
import { NavBarMenuItem } from './NavBarMenuItem';
|
|
|
|
export interface Props {
|
|
activeItem?: NavModelItem;
|
|
navItems: NavModelItem[];
|
|
onClose: () => void;
|
|
}
|
|
|
|
export function NavBarMenu({ activeItem, navItems, onClose }: Props) {
|
|
const theme = useTheme2();
|
|
const styles = getStyles(theme);
|
|
const ref = useRef(null);
|
|
const { overlayProps } = useOverlay(
|
|
{
|
|
isDismissable: true,
|
|
isOpen: true,
|
|
onClose,
|
|
},
|
|
ref
|
|
);
|
|
|
|
return (
|
|
<FocusScope contain restoreFocus autoFocus>
|
|
<div data-testid="navbarmenu" className={styles.container} ref={ref} {...overlayProps}>
|
|
<div className={styles.header}>
|
|
<Icon name="bars" size="xl" />
|
|
<IconButton aria-label="Close navigation menu" name="times" onClick={onClose} size="xl" variant="secondary" />
|
|
</div>
|
|
<nav className={styles.content}>
|
|
<CustomScrollbar>
|
|
<ul>
|
|
{navItems.map((link, index) => (
|
|
<div className={styles.section} key={index}>
|
|
<NavBarMenuItem
|
|
isActive={activeItem === link}
|
|
onClick={() => {
|
|
link.onClick?.();
|
|
onClose();
|
|
}}
|
|
styleOverrides={styles.sectionHeader}
|
|
target={link.target}
|
|
text={link.text}
|
|
url={link.url}
|
|
/>
|
|
{link.children?.map(
|
|
(childLink, childIndex) =>
|
|
!childLink.divider && (
|
|
<NavBarMenuItem
|
|
key={childIndex}
|
|
icon={childLink.icon as IconName}
|
|
isActive={activeItem === childLink}
|
|
isDivider={childLink.divider}
|
|
onClick={() => {
|
|
childLink.onClick?.();
|
|
onClose();
|
|
}}
|
|
styleOverrides={styles.item}
|
|
target={childLink.target}
|
|
text={childLink.text}
|
|
url={childLink.url}
|
|
/>
|
|
)
|
|
)}
|
|
</div>
|
|
))}
|
|
</ul>
|
|
</CustomScrollbar>
|
|
</nav>
|
|
</div>
|
|
</FocusScope>
|
|
);
|
|
}
|
|
|
|
NavBarMenu.displayName = 'NavBarMenu';
|
|
|
|
const getStyles = (theme: GrafanaTheme2) => ({
|
|
container: css`
|
|
background-color: ${theme.colors.background.canvas};
|
|
bottom: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
left: 0;
|
|
min-width: 300px;
|
|
position: fixed;
|
|
right: 0;
|
|
top: 0;
|
|
|
|
${theme.breakpoints.up('md')} {
|
|
border-right: 1px solid ${theme.colors.border.weak};
|
|
right: unset;
|
|
}
|
|
`,
|
|
content: css`
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: auto;
|
|
`,
|
|
header: css`
|
|
border-bottom: 1px solid ${theme.colors.border.weak};
|
|
display: flex;
|
|
justify-content: space-between;
|
|
padding: ${theme.spacing(2)};
|
|
`,
|
|
item: css`
|
|
padding: ${theme.spacing(1)} ${theme.spacing(2)};
|
|
`,
|
|
section: css`
|
|
border-bottom: 1px solid ${theme.colors.border.weak};
|
|
`,
|
|
sectionHeader: css`
|
|
color: ${theme.colors.text.primary};
|
|
font-size: ${theme.typography.h5.fontSize};
|
|
padding: ${theme.spacing(1)} ${theme.spacing(2)};
|
|
`,
|
|
});
|