grafana/public/app/core/components/MegaMenu/NavBarMenuItem.tsx
Torkel Ödegaard 8440baab91
NavBarMenu: Section as links and design tweak (#55538)
* scaffold new component + remove storing of expanded state

* some padding fixes

* simplify!

* move browse back to being a child of dashboards

* behaviour working

* improve child matcher to look recursively

* increase NavBarMenu zIndex to ensure it overlays explore drawer

* some renaming

* fix unit test

* make dashboards a top level item again and make chevrons their own buttons

* remove active background state

* Finished tweaks

* remove theme change

* Remove exit animation

* align button centrally + fix empty message alignment

* only show the empty message if there are no children

* ensure overflowing menu items truncate correctly

Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
2022-09-21 11:57:30 +02:00

123 lines
3.2 KiB
TypeScript

import { css, cx } from '@emotion/css';
import React from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { Icon, IconName, Link, useTheme2 } from '@grafana/ui';
export interface Props {
children: React.ReactNode;
icon?: IconName;
isActive?: boolean;
isChild?: boolean;
onClick?: () => void;
target?: HTMLAnchorElement['target'];
url?: string;
}
export function NavBarMenuItem({ children, icon, isActive, isChild, onClick, target, url }: Props) {
const theme = useTheme2();
const styles = getStyles(theme, isActive, isChild);
const linkContent = (
<div className={styles.linkContent}>
{icon && <Icon data-testid="dropdown-child-icon" name={icon} />}
<div className={styles.linkText}>{children}</div>
{target === '_blank' && (
<Icon data-testid="external-link-icon" name="external-link-alt" className={styles.externalLinkIcon} />
)}
</div>
);
let element = (
<button className={cx(styles.button, styles.element)} onClick={onClick}>
{linkContent}
</button>
);
if (url) {
element =
!target && url.startsWith('/') ? (
<Link className={styles.element} href={url} target={target} onClick={onClick}>
{linkContent}
</Link>
) : (
<a href={url} target={target} className={styles.element} onClick={onClick}>
{linkContent}
</a>
);
}
return <li className={styles.listItem}>{element}</li>;
}
NavBarMenuItem.displayName = 'NavBarMenuItem';
const getStyles = (theme: GrafanaTheme2, isActive: Props['isActive'], isChild: Props['isActive']) => ({
button: css({
backgroundColor: 'unset',
borderStyle: 'unset',
}),
linkContent: css({
alignItems: 'center',
display: 'flex',
gap: '0.5rem',
height: '100%',
width: '100%',
}),
linkText: css({
textOverflow: 'ellipsis',
overflow: 'hidden',
whiteSpace: 'nowrap',
}),
externalLinkIcon: css({
color: theme.colors.text.secondary,
}),
element: css({
alignItems: 'center',
boxSizing: 'border-box',
position: 'relative',
color: isActive ? theme.colors.text.primary : theme.colors.text.secondary,
padding: theme.spacing(1, 1, 1, isChild ? 5 : 0),
...(isChild && {
borderRadius: theme.shape.borderRadius(),
}),
width: '100%',
'&:hover, &:focus-visible': {
...(isChild && {
background: theme.colors.emphasize(theme.colors.background.primary, 0.03),
}),
textDecoration: 'underline',
color: theme.colors.text.primary,
},
'&:focus-visible': {
boxShadow: 'none',
outline: `2px solid ${theme.colors.primary.main}`,
outlineOffset: '-2px',
transition: 'none',
},
'&::before': {
display: isActive ? 'block' : 'none',
content: '" "',
height: theme.spacing(3),
position: 'absolute',
left: theme.spacing(1),
top: '50%',
transform: 'translateY(-50%)',
width: theme.spacing(0.5),
borderRadius: theme.shape.borderRadius(1),
backgroundImage: theme.colors.gradients.brandVertical,
},
}),
listItem: css({
boxSizing: 'border-box',
position: 'relative',
display: 'flex',
width: '100%',
...(isChild && {
padding: theme.spacing(0, 2),
}),
}),
});