mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Grafana UI: Add description to Menu component (#77808)
Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
This commit is contained in:
parent
641a47c71d
commit
10269cb7f5
@ -49,6 +49,42 @@ export function Examples() {
|
||||
<Menu.Item label="With destructive prop set" icon="trash-alt" destructive />
|
||||
</Menu>
|
||||
</StoryExample>
|
||||
<StoryExample name="With item menu description">
|
||||
<Menu>
|
||||
<Menu.Item label="item1" icon="history" description="item 1 is an important element" shortcut="q p" />
|
||||
<Menu.Item
|
||||
label="Item with a very long title"
|
||||
icon="apps"
|
||||
description="long titles can be hard to read"
|
||||
childItems={[
|
||||
<Menu.Item key="subitem1" label="subitem1" icon="history" />,
|
||||
<Menu.Item key="subitem2" label="subitem2" icon="apps" />,
|
||||
<Menu.Item
|
||||
key="subitem3"
|
||||
label="subitem3"
|
||||
icon="search-plus"
|
||||
childItems={[
|
||||
<Menu.Item key="subitem1" label="subitem1" icon="history" />,
|
||||
<Menu.Item key="subitem2" label="subitem2" icon="apps" />,
|
||||
<Menu.Item key="subitem3" label="subitem3" icon="search-plus" />,
|
||||
]}
|
||||
/>,
|
||||
]}
|
||||
shortcut="p s"
|
||||
/>
|
||||
<Menu.Item
|
||||
label="item3"
|
||||
icon="filter"
|
||||
description="item 3 is an important element"
|
||||
childItems={[
|
||||
<Menu.Item key="subitem1" label="subitem1" icon="history" description="a subitem with a description" />,
|
||||
<Menu.Item key="subitem2" label="subitem2" icon="apps" />,
|
||||
<Menu.Item key="subitem3" label="subitem3" icon="search-plus" />,
|
||||
]}
|
||||
/>
|
||||
</Menu>
|
||||
</StoryExample>
|
||||
|
||||
<StoryExample name="With disabled items">
|
||||
<Menu>
|
||||
<Menu.Item label="Google" icon="search-plus" />
|
||||
|
@ -7,6 +7,7 @@ import { useStyles2 } from '../../themes';
|
||||
import { getFocusStyles } from '../../themes/mixins';
|
||||
import { IconName } from '../../types/icon';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
import { Stack } from '../Layout/Stack/Stack';
|
||||
|
||||
import { SubMenu } from './SubMenu';
|
||||
|
||||
@ -17,6 +18,8 @@ export type MenuItemElement = HTMLAnchorElement & HTMLButtonElement & HTMLDivEle
|
||||
export interface MenuItemProps<T = unknown> {
|
||||
/** Label of the menu item */
|
||||
label: string;
|
||||
/** Description of item */
|
||||
description?: string;
|
||||
/** Aria label for accessibility support */
|
||||
ariaLabel?: string;
|
||||
/** Aria checked for accessibility support */
|
||||
@ -57,6 +60,7 @@ export const MenuItem = React.memo(
|
||||
url,
|
||||
icon,
|
||||
label,
|
||||
description,
|
||||
ariaLabel,
|
||||
ariaChecked,
|
||||
target,
|
||||
@ -104,6 +108,7 @@ export const MenuItem = React.memo(
|
||||
},
|
||||
className
|
||||
);
|
||||
|
||||
const disabledProps = {
|
||||
[ItemElement === 'button' ? 'disabled' : 'aria-disabled']: disabled,
|
||||
...(ItemElement === 'a' && disabled && { href: undefined, onClick: undefined }),
|
||||
@ -159,10 +164,9 @@ export const MenuItem = React.memo(
|
||||
tabIndex={tabIndex}
|
||||
{...disabledProps}
|
||||
>
|
||||
<>
|
||||
<Stack direction="row" justifyContent="flex-start" alignItems="center">
|
||||
{icon && <Icon name={icon} className={styles.icon} aria-hidden />}
|
||||
{label}
|
||||
|
||||
<div className={cx(styles.rightWrapper, { [styles.withShortcut]: hasShortcut })}>
|
||||
{hasShortcut && (
|
||||
<div className={styles.shortcut}>
|
||||
@ -181,7 +185,16 @@ export const MenuItem = React.memo(
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
</Stack>
|
||||
{description && (
|
||||
<div
|
||||
className={cx(styles.description, {
|
||||
[styles.descriptionWithIcon]: icon !== undefined,
|
||||
})}
|
||||
>
|
||||
{description}
|
||||
</div>
|
||||
)}
|
||||
</ItemElement>
|
||||
);
|
||||
})
|
||||
@ -197,7 +210,8 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
whiteSpace: 'nowrap',
|
||||
color: theme.colors.text.primary,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'stretch',
|
||||
padding: theme.spacing(0.5, 2),
|
||||
minHeight: theme.spacing(4),
|
||||
margin: 0,
|
||||
@ -234,7 +248,7 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
}),
|
||||
disabled: css({
|
||||
color: theme.colors.action.disabledText,
|
||||
|
||||
label: 'menu-item-disabled',
|
||||
'&:hover, &:focus, &:focus-visible': {
|
||||
cursor: 'not-allowed',
|
||||
background: 'none',
|
||||
@ -243,8 +257,6 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
}),
|
||||
icon: css({
|
||||
opacity: 0.7,
|
||||
marginRight: '10px',
|
||||
marginLeft: '-4px',
|
||||
color: theme.colors.text.secondary,
|
||||
}),
|
||||
rightWrapper: css({
|
||||
@ -252,9 +264,6 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
alignItems: 'center',
|
||||
marginLeft: 'auto',
|
||||
}),
|
||||
shortcutIcon: css({
|
||||
marginRight: theme.spacing(1),
|
||||
}),
|
||||
withShortcut: css({
|
||||
minWidth: theme.spacing(10.5),
|
||||
}),
|
||||
@ -266,5 +275,13 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
color: theme.colors.text.secondary,
|
||||
opacity: 0.7,
|
||||
}),
|
||||
description: css({
|
||||
...theme.typography.bodySmall,
|
||||
color: theme.colors.text.secondary,
|
||||
textAlign: 'start',
|
||||
}),
|
||||
descriptionWithIcon: css({
|
||||
marginLeft: theme.spacing(3),
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
@ -49,8 +49,8 @@ export const SubMenu = React.memo(
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.iconWrapper} aria-label={selectors.components.Menu.SubMenu.icon}>
|
||||
<Icon name="angle-right" className={styles.icon} aria-hidden />
|
||||
<div className={styles.iconWrapper} aria-hidden aria-label={selectors.components.Menu.SubMenu.icon}>
|
||||
<Icon name="angle-right" className={styles.icon} />
|
||||
</div>
|
||||
{isOpen && (
|
||||
<div
|
||||
|
@ -25,18 +25,21 @@ describe('NewActionsButton', () => {
|
||||
it('should display the correct urls with a given parent folder', async () => {
|
||||
await renderAndOpen(mockParentFolder);
|
||||
|
||||
expect(screen.getByText('New dashboard')).toHaveAttribute(
|
||||
expect(screen.getByRole('link', { name: 'New dashboard' })).toHaveAttribute(
|
||||
'href',
|
||||
`/dashboard/new?folderUid=${mockParentFolder.uid}`
|
||||
);
|
||||
expect(screen.getByText('Import')).toHaveAttribute('href', `/dashboard/import?folderUid=${mockParentFolder.uid}`);
|
||||
expect(screen.getByRole('link', { name: 'Import' })).toHaveAttribute(
|
||||
'href',
|
||||
`/dashboard/import?folderUid=${mockParentFolder.uid}`
|
||||
);
|
||||
});
|
||||
|
||||
it('should display urls without params when there is no parent folder', async () => {
|
||||
await renderAndOpen();
|
||||
|
||||
expect(screen.getByText('New dashboard')).toHaveAttribute('href', '/dashboard/new');
|
||||
expect(screen.getByText('Import')).toHaveAttribute('href', '/dashboard/import');
|
||||
expect(screen.getByRole('link', { name: 'New dashboard' })).toHaveAttribute('href', '/dashboard/new');
|
||||
expect(screen.getByRole('link', { name: 'Import' })).toHaveAttribute('href', '/dashboard/import');
|
||||
});
|
||||
|
||||
it('clicking the "New folder" button opens the drawer', async () => {
|
||||
@ -57,7 +60,7 @@ describe('NewActionsButton', () => {
|
||||
const newButton = screen.getByText('New');
|
||||
await userEvent.click(newButton);
|
||||
|
||||
expect(screen.getByText('New dashboard')).toBeInTheDocument();
|
||||
expect(screen.getByRole('link', { name: 'New dashboard' })).toBeInTheDocument();
|
||||
expect(screen.getByText('Import')).toBeInTheDocument();
|
||||
expect(screen.queryByText('New folder')).not.toBeInTheDocument();
|
||||
});
|
||||
|
@ -68,7 +68,7 @@ it('renders with all buttons enabled except paste a panel', () => {
|
||||
expect(screen.getByText('visualization', { exact: false })).not.toBeDisabled();
|
||||
expect(screen.getByText('row', { exact: false })).not.toBeDisabled();
|
||||
expect(screen.getByText('library', { exact: false })).not.toBeDisabled();
|
||||
expect(screen.getByText('paste panel', { exact: false })).toBeDisabled();
|
||||
expect(screen.getByRole('menuitem', { name: 'Paste panel' })).toBeDisabled();
|
||||
});
|
||||
|
||||
it('renders with all buttons enabled', () => {
|
||||
|
Loading…
Reference in New Issue
Block a user