Skip to content

Commit

Permalink
feat(Menu): add support for item icon on the end of the item (#965)
Browse files Browse the repository at this point in the history
  • Loading branch information
ogonkov authored Nov 2, 2023
1 parent 96f94df commit 55885d0
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 35 deletions.
12 changes: 3 additions & 9 deletions src/components/DropdownMenu/DropdownMenu.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,9 @@ $block: '.#{variables.$ns}dropdown-menu';
pointer-events: none;
}

&__menu-item-content {
display: flex;
justify-content: space-between;
align-items: center;
gap: 16px;
}

&__menu-item-content-children {
flex: auto;
&__sub-menu-arrow {
right: -4px;
position: relative;
}

&__sub-menu {
Expand Down
14 changes: 8 additions & 6 deletions src/components/DropdownMenu/DropdownMenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,20 +92,22 @@ export const DropdownMenuItem = <T,>({
};
}, [props.extraProps, closeSubmenu, hasSubmenu, openSubmenu]);

const iconEnd = hasSubmenu ? (
<Icon data={ChevronRight} size={10} className={cnDropdownMenu('sub-menu-arrow')} />
) : (
props.iconEnd
);

return (
<React.Fragment>
<Menu.Item
ref={menuItemRef}
{...props}
extraProps={extraProps}
onClick={handleMenuItemClick}
iconEnd={iconEnd}
>
<div className={cnDropdownMenu('menu-item-content')}>
<div className={cnDropdownMenu('menu-item-content-children')}>
{text || children}
</div>
{hasSubmenu && <Icon data={ChevronRight} size={10} />}
</div>
{text || children}
</Menu.Item>
{hasSubmenu && subMenuItems && (
<DropdownMenuPopup
Expand Down
17 changes: 9 additions & 8 deletions src/components/DropdownMenu/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ The example above is oversimplified to demonstrate the idea of the customizable

## Custom icons

A custom icon can be added to a `DropdownMenu` item by assigning the `icon` property. By default, `DropdownMenu` items go without icons.
Custom icons can be added to a `DropdownMenu` item by assigning the `iconStart` or `iconEnd` property. By default, `DropdownMenu` items go without icons.

The menu toggle icon can be changed with the `DropdownMenu`'s `switcher` prop. By default, the menu toggle is a button with the ellipsis icon (****).

Expand All @@ -415,12 +415,12 @@ The menu toggle icon can be changed with the `DropdownMenu`'s `switcher` prop. B
}
items={[
{
icon: <Icon size={16} data={Pencil} />,
iconStart: <Icon size={16} data={Pencil} />,
action: () => console.log('Rename'),
text: 'Rename',
},
{
icon: <Icon size={16} data={TrashBin} />,
iconStart: <Icon size={16} data={TrashBin} />,
action: () => console.log('Delete'),
text: 'Delete',
theme: 'danger',
Expand All @@ -442,7 +442,7 @@ The menu toggle icon can be changed with the `DropdownMenu`'s `switcher` prop. B
}
items={[
{
icon: (
iconStart: (
<UIKit.Icon
data={() => (
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16 16"><path fill="currentColor" fill-rule="evenodd" d="M11.423 1A3.577 3.577 0 0 1 15 4.577c0 .27-.108.53-.3.722l-.528.529-1.971 1.971-5.059 5.059a3 3 0 0 1-1.533.82l-2.638.528a1 1 0 0 1-1.177-1.177l.528-2.638a3 3 0 0 1 .82-1.533l5.059-5.059 2.5-2.5c.191-.191.451-.299.722-.299Zm-2.31 4.009-4.91 4.91a1.5 1.5 0 0 0-.41.766l-.38 1.903 1.902-.38a1.5 1.5 0 0 0 .767-.41l4.91-4.91a2.077 2.077 0 0 0-1.88-1.88Zm3.098.658a3.59 3.59 0 0 0-1.878-1.879l1.28-1.28c.995.09 1.788.884 1.878 1.88l-1.28 1.28Z" clip-rule="evenodd"></path></svg>
Expand All @@ -454,7 +454,7 @@ The menu toggle icon can be changed with the `DropdownMenu`'s `switcher` prop. B
text: 'Rename',
},
{
icon: (
iconStart: (
<UIKit.Icon
data={() => (
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16 16"><path fill="currentColor" fill-rule="evenodd" d="M9 2H7a.5.5 0 0 0-.5.5V3h3v-.5A.5.5 0 0 0 9 2Zm2 1v-.5a2 2 0 0 0-2-2H7a2 2 0 0 0-2 2V3H2.251a.75.75 0 0 0 0 1.5h.312l.317 7.625A3 3 0 0 0 5.878 15h4.245a3 3 0 0 0 2.997-2.875l.318-7.625h.312a.75.75 0 0 0 0-1.5H11Zm.936 1.5H4.064l.315 7.562A1.5 1.5 0 0 0 5.878 13.5h4.245a1.5 1.5 0 0 0 1.498-1.438l.315-7.562Zm-6.186 2v5a.75.75 0 0 0 1.5 0v-5a.75.75 0 0 0-1.5 0Zm3.75-.75a.75.75 0 0 1 .75.75v5a.75.75 0 0 1-1.5 0v-5a.75.75 0 0 1 .75-.75Z" clip-rule="evenodd"></path></svg>
Expand Down Expand Up @@ -483,12 +483,12 @@ LANDING_BLOCK-->
}
items={[
{
icon: <Icon size={16} data={Pencil} />,
iconStart: <Icon size={16} data={Pencil} />,
action: () => console.log('Rename'),
text: 'Rename',
},
{
icon: <Icon size={16} data={TrashBin} />,
iconStart: <Icon size={16} data={TrashBin} />,
action: () => console.log('Delete'),
text: 'Delete',
theme: 'danger',
Expand Down Expand Up @@ -528,7 +528,8 @@ This type describes individual dropdown menu items.
| :----------- | :------------------------------------------------------------------------------------------------------------------------------ | :--------------------------------------------: | :-----: |
| `text` | Menu item content. | `React.ReactNode` | |
| `action` | Menu item click handler. Recieves the parameters from the parent dropdown menu component (both `event` and `data`). | `(event: React.MouseEvent, data: any) => void` | |
| `icon` | Menu item icon. | `React.ReactNode` | |
| `iconStart` | Menu item icon before the item content. | `React.ReactNode` | |
| `iconEnd` | Menu item icon after the item content. Ignored if the item has a submenu. | `React.ReactNode` | |
| `hidden` | Determines whether the item is hidden. | `boolean` | |
| `disabled` | Determines whether the item is disabled. | `boolean` | |
| `href` | Menu item with this prop becomes a link to the specified location. | `string` | |
Expand Down
21 changes: 21 additions & 0 deletions src/components/Menu/Menu.scss
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ $block: '.#{variables.$ns}menu';
display: flex;
}

&-icon-end {
display: flex;
margin-right: 0;
}

&-content {
flex-grow: 1;
min-width: 0;
Expand Down Expand Up @@ -104,6 +109,10 @@ $block: '.#{variables.$ns}menu';
margin-right: 3px;
}

#{$block}__item-icon-end {
margin-left: 3px;
}

#{$block}__list-group-item + #{$block}__list-group-item,
#{$block}__list-item + #{$block}__list-group-item,
#{$block}__list-group-item + #{$block}__list-item {
Expand All @@ -125,6 +134,10 @@ $block: '.#{variables.$ns}menu';
margin-right: 4px;
}

#{$block}__item-icon-end {
margin-left: 4px;
}

#{$block}__list-group-item + #{$block}__list-group-item,
#{$block}__list-item + #{$block}__list-group-item,
#{$block}__list-group-item + #{$block}__list-item {
Expand All @@ -146,6 +159,10 @@ $block: '.#{variables.$ns}menu';
margin-right: 5px;
}

#{$block}__item-icon-end {
margin-left: 5px;
}

#{$block}__list-group-item + #{$block}__list-group-item,
#{$block}__list-item + #{$block}__list-group-item,
#{$block}__list-group-item + #{$block}__list-item {
Expand All @@ -168,6 +185,10 @@ $block: '.#{variables.$ns}menu';
margin-right: 6px;
}

#{$block}__item-icon-end {
margin-left: 6px;
}

#{$block}__list-group-item:not(:first-child) {
margin-top: 6px;
padding-top: 6px;
Expand Down
16 changes: 13 additions & 3 deletions src/components/Menu/MenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import {eventBroker} from '../utils/event-broker';
const b = block('menu');

export interface MenuItemProps extends DOMProps, QAProps {
/** @deprecated use `iconStart` instead */
icon?: React.ReactNode;
iconStart?: React.ReactNode;
iconEnd?: React.ReactNode;
title?: string;
disabled?: boolean;
active?: boolean;
Expand All @@ -27,6 +30,8 @@ export interface MenuItemProps extends DOMProps, QAProps {
export const MenuItem = React.forwardRef<HTMLElement, MenuItemProps>(function MenuItem(
{
icon,
iconStart = icon,
iconEnd,
title,
disabled,
active,
Expand Down Expand Up @@ -69,14 +74,19 @@ export const MenuItem = React.forwardRef<HTMLElement, MenuItemProps>(function Me
'data-qa': qa,
};
const content = [
icon && (
<div key="icon" className={b('item-icon')}>
{icon}
iconStart && (
<div key="icon-start" className={b('item-icon')}>
{iconStart}
</div>
),
<div key="content" className={b('item-content')}>
{children}
</div>,
iconEnd && (
<div key={'icon-end'} className={b('item-icon-end')}>
{iconEnd}
</div>
),
];
let item;

Expand Down
37 changes: 33 additions & 4 deletions src/components/Menu/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,19 +133,19 @@ This property is used to render menu items.

### Icon

The `Icon` property is used to display an icon for a menu item:
The `iconStart` or `iconEnd` properties is used to display an icon at the start or end of a menu item:

<!--LANDING_BLOCK
<ExampleBlock
code={`
<Menu>
<Menu.Item icon={<Icon size={16} data={GearIcon} />}>Item with icon</Menu.Item>
<Menu.Item iconStart={<Icon size={16} data={GearIcon} />}>Item with icon</Menu.Item>
<Menu.Item>Item without icon</Menu.Item>
</Menu>
`}
>
<UIKit.Menu>
<UIKit.Menu.Item icon={
<UIKit.Menu.Item iconStart={
<UIKit.Icon data={() => (
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" class="yc-icon" fill="currentColor" stroke="none" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16 16"><path fill="currentColor" fill-rule="evenodd" d="M7.199 2H8.8a.2.2 0 0 1 .2.2c0 1.808 1.958 2.939 3.524 2.034a.199.199 0 0 1 .271.073l.802 1.388a.199.199 0 0 1-.073.272c-1.566.904-1.566 3.164 0 4.069a.199.199 0 0 1 .073.271l-.802 1.388a.199.199 0 0 1-.271.073C10.958 10.863 9 11.993 9 13.8a.2.2 0 0 1-.199.2H7.2a.199.199 0 0 1-.2-.2c0-1.808-1.958-2.938-3.524-2.034a.199.199 0 0 1-.272-.073l-.8-1.388a.199.199 0 0 1 .072-.271c1.566-.905 1.566-3.165 0-4.07a.199.199 0 0 1-.073-.271l.801-1.388a.199.199 0 0 1 .272-.073C5.042 5.138 7 4.007 7 2.2c0-.11.089-.199.199-.199ZM5.5 2.2c0-.94.76-1.7 1.699-1.7H8.8c.94 0 1.7.76 1.7 1.7a.85.85 0 0 0 1.274.735 1.699 1.699 0 0 1 2.32.622l.802 1.388c.469.813.19 1.851-.622 2.32a.85.85 0 0 0 0 1.472 1.7 1.7 0 0 1 .622 2.32l-.802 1.388a1.699 1.699 0 0 1-2.32.622.85.85 0 0 0-1.274.735c0 .939-.76 1.7-1.699 1.7H7.2a1.7 1.7 0 0 1-1.699-1.7.85.85 0 0 0-1.274-.735 1.698 1.698 0 0 1-2.32-.622l-.802-1.388a1.699 1.699 0 0 1 .622-2.32.85.85 0 0 0 0-1.471 1.699 1.699 0 0 1-.622-2.321l.801-1.388a1.699 1.699 0 0 1 2.32-.622A.85.85 0 0 0 5.5 2.2Zm4 5.8a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0ZM11 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" clip-rule="evenodd"></path></svg></svg>
)} size={16} />
Expand All @@ -155,13 +155,40 @@ The `Icon` property is used to display an icon for a menu item:
<UIKit.Menu.Item>Item without icon</UIKit.Menu.Item>
</UIKit.Menu>
</ExampleBlock>
<ExampleBlock
code={`
<Menu>
<Menu.Item iconEnd={<Icon size={16} data={TriangleExclamation} />}>Item with icon</Menu.Item>
<Menu.Item>Item without icon</Menu.Item>
</Menu>
`}
>
<UIKit.Menu>
<UIKit.Menu.Item iconEnd={
<UIKit.Icon data={() => (
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 16 16"><path fill="currentColor" fill-rule="evenodd" d="M7.134 2.994 2.217 11.5a1 1 0 0 0 .866 1.5h9.834a1 1 0 0 0 .866-1.5L8.866 2.993a1 1 0 0 0-1.732 0Zm3.03-.75c-.962-1.665-3.366-1.665-4.328 0L.919 10.749c-.964 1.666.239 3.751 2.164 3.751h9.834c1.925 0 3.128-2.085 2.164-3.751l-4.917-8.505ZM8 5a.75.75 0 0 1 .75.75v2a.75.75 0 0 1-1.5 0v-2A.75.75 0 0 1 8 5Zm1 5.75a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z" clip-rule="evenodd"/></svg>
)} size={16} />
}>
Item with icon
</UIKit.Menu.Item>
<UIKit.Menu.Item>Item without icon</UIKit.Menu.Item>
</UIKit.Menu>
</ExampleBlock>
LANDING_BLOCK-->

<!--GITHUB_BLOCK-->

```tsx
<Menu>
<Menu.Item icon={<Icon size={16} data={GearIcon} />}>Item with icon</Menu.Item>
<Menu.Item iconStart={<Icon size={16} data={GearIcon} />}>Item with icon</Menu.Item>
<Menu.Item>Item without icon</Menu.Item>
</Menu>
```

```tsx
<Menu>
<Menu.Item iconEnd={<Icon size={16} data={TriangleExclamation} />}>Item with icon</Menu.Item>
<Menu.Item>Item without icon</Menu.Item>
</Menu>
```
Expand Down Expand Up @@ -240,6 +267,8 @@ LANDING_BLOCK-->

| Name | Description | Type | Default |
| :--------- | :----------------------------------------- | :-----------------------: | :--------: |
| iconStart | Menu icon before item text | `ReactNode` | |
| iconEnd | Menu icon after item text | `ReactNode` | |
| selected | Menu item selected flag | `boolean` | `false` |
| disabled | Menu item disabled flag | `boolean` | `false` |
| active | Menu item active flag | `boolean` | `false` |
Expand Down
17 changes: 14 additions & 3 deletions src/components/Menu/__stories__/Menu.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';

import {Gear} from '@gravity-ui/icons';
import {CircleExclamationFill, Gear} from '@gravity-ui/icons';
import type {Meta, StoryFn} from '@storybook/react';

import {Icon} from '../../Icon';
Expand All @@ -10,7 +10,7 @@ import type {MenuProps} from '../Menu';
export default {
title: 'Components/Navigation/Menu',
component: Menu,
} as Meta;
} as Meta<typeof Menu>;

export const Default: StoryFn<MenuProps> = (args) => (
<Menu {...args}>
Expand All @@ -29,7 +29,18 @@ export const ItemActive: StoryFn<MenuProps> = (args) => (

export const ItemIcon: StoryFn<MenuProps> = (args) => (
<Menu {...args}>
<Menu.Item icon={<Icon data={Gear} size={16} />}>Settings</Menu.Item>
<Menu.Item iconStart={<Icon data={Gear} size={16} />}>Settings</Menu.Item>
</Menu>
);

export const ItemBothIcons: StoryFn<MenuProps> = (args) => (
<Menu {...args}>
<Menu.Item
iconStart={<Icon data={Gear} size={16} />}
iconEnd={<Icon data={CircleExclamationFill} size={16} />}
>
Settings
</Menu.Item>
</Menu>
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export interface TableAction<I> {
) => void;
disabled?: boolean;
theme?: MenuItemProps['theme'];
icon?: MenuItemProps['icon'];
icon?: MenuItemProps['iconStart'];
}

export interface TableActionGroup<I> {
Expand Down Expand Up @@ -179,7 +179,7 @@ export function withTableActions<I extends TableDataItem, E extends {} = {}>(
disabled={action.disabled}
onClick={this.handleActionClick.bind(this, action, popupData!)}
theme={action.theme}
icon={action.icon}
iconStart={action.icon}
className={bPopup('menu-item')}
>
{action.text}
Expand Down

0 comments on commit 55885d0

Please sign in to comment.