Skip to content

Commit

Permalink
support custom arrow icon in vertical and inline mode. (#182)
Browse files Browse the repository at this point in the history
向外暴露`arrowIcon`接口来自定义菜单图标。
只限于垂直和内联模式。
以便未来使用svg图标。
  • Loading branch information
HeskeyBaozi authored and yesmeck committed Aug 22, 2018
1 parent d7eaed4 commit 07b4ccc
Show file tree
Hide file tree
Showing 10 changed files with 325 additions and 1 deletion.
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,18 @@ ReactDOM.render(<Menu>
<th>see <a href="./src/placements.jsx">placements.jsx</a></th>
<td>Describes how the popup menus should be positioned</td>
</tr>
<tr>
<td>itemIcon</td>
<td>ReactNode | (props: MenuItemProps) => ReactNode</td>
<th></th>
<td>Specify the menu item icon.</td>
</tr>
<tr>
<td>expandIcon</td>
<td>ReactNode | (props: SubMenuProps & { isSubMenu: boolean }) => ReactNode</td>
<th></th>
<td>Specify the menu item icon.</td>
</tr>
</tbody>
</table>

Expand Down Expand Up @@ -237,6 +249,12 @@ ReactDOM.render(<Menu>
<th></th>
<td></td>
</tr>
<tr>
<td>itemIcon</td>
<td>ReactNode | (props: MenuItemProps) => ReactNode</td>
<th></th>
<td>Specify the menu item icon.</td>
</tr>
</tbody>
</table>

Expand Down Expand Up @@ -313,6 +331,18 @@ ReactDOM.render(<Menu>
<th></th>
<td>The offset of the popup submenu, in an x, y coordinate array. e.g.: `[0,15]`</td>
</tr>
<tr>
<td>expandIcon</td>
<td>ReactNode | (props: SubMenuProps) => ReactNode</td>
<th></th>
<td>Specify the menu item icon.</td>
</tr>
<tr>
<td>itemIcon</td>
<td>ReactNode | (props: SubMenuProps & { isSubMenu: boolean }) => ReactNode</td>
<th></th>
<td>Specify the menu item icon.</td>
</tr>
</tbody>
</table>

Expand Down
Empty file added examples/custom-icon.html
Empty file.
174 changes: 174 additions & 0 deletions examples/custom-icon.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/* eslint no-console:0 */
import * as React from 'react';
import ReactDOM from 'react-dom';
import Menu, { SubMenu, Item as MenuItem, Divider } from 'rc-menu';
import 'rc-menu/assets/index.less';
import animate from 'css-animation';

const getSvgIcon = (style = {}, text) => {
if (text) {
return (
<i style={style}>
{text}
</i>
);
}
const path = 'M869 487.8L491.2 159.9c-2.9-2.5-6.6-3.9-10.5-3.9h' +
'-88.5c-7.4 0-10.8 9.2-5.2 14l350.2 304H152c-4.4 0-8 3.6-8 8v' +
'60c0 4.4 3.6 8 8 8h585.1L386.9 854c-5.6 4.9-2.2 14 5.2 14h91' +
'.5c1.9 0 3.8-0.7 5.2-2L869 536.2c14.7-12.8 14.7-35.6 0-48.4z';
return (
<i style={style}>
<svg
viewBox="0 0 1024 1024"
width="1em"
height="1em"
fill="currentColor"
style={{ verticalAlign: '-.125em' }}
>
<path d={path} />
</svg>
</i>
);
};

function itemIcon(props) {
return getSvgIcon({
position: 'absolute',
right: '1rem',
color: props.isSelected ? 'pink' : 'inherit',
});
}

function expandIcon(props) {
return getSvgIcon({
position: 'absolute',
right: '1rem',
color: 'lightblue',
transform: `rotate(${props.isOpen ? 90 : 0}deg)`,
});
}

const animation = {
enter(node, done) {
let height;
return animate(node, 'rc-menu-collapse', {
start() {
height = node.offsetHeight;
node.style.height = 0;
},
active() {
node.style.height = `${height}px`;
},
end() {
node.style.height = '';
done();
},
});
},

appear() {
return this.enter.apply(this, arguments);
},

leave(node, done) {
return animate(node, 'rc-menu-collapse', {
start() {
node.style.height = `${node.offsetHeight}px`;
},
active() {
node.style.height = 0;
},
end() {
node.style.height = '';
done();
},
});
},
};

class Demo extends React.Component {

onOpenChange = (value) => {
console.log('onOpenChange', value);
}

handleClick = (info) => {
console.log(`clicked ${info.key}`);
console.log(info);
}

renderNestSubMenu = (props = {}) => {
return (
<SubMenu title={<span>offset sub menu 2</span>} key="4" popupOffset={[10, 15]} {...props}>
<MenuItem key="4-1">inner inner</MenuItem>
<Divider />
<SubMenu
key="4-2"
title={<span>sub menu 3</span>}
>
<SubMenu title="sub 4-2-0" key="4-2-0">
<MenuItem key="4-2-0-1">inner inner</MenuItem>
<MenuItem key="4-2-0-2">inner inner2</MenuItem>
</SubMenu>
<MenuItem key="4-2-1">inn</MenuItem>
<SubMenu title={<span>sub menu 4</span>} key="4-2-2">
<MenuItem key="4-2-2-1">inner inner</MenuItem>
<MenuItem key="4-2-2-2">inner inner2</MenuItem>
</SubMenu>
<SubMenu title="sub 4-2-3" key="4-2-3">
<MenuItem key="4-2-3-1">inner inner</MenuItem>
<MenuItem key="4-2-3-2">inner inner2</MenuItem>
</SubMenu>
</SubMenu>
</SubMenu>
);
}

renderCommonMenu = (props = {}) => {
return (
<Menu onClick={this.handleClick} onOpenChange={this.onOpenChange} {...props}>
<SubMenu title={<span>sub menu</span>} key="1">
<MenuItem key="1-1">0-1</MenuItem>
<MenuItem key="1-2">0-2</MenuItem>
</SubMenu>
{this.renderNestSubMenu()}
<MenuItem key="2">1</MenuItem>
<MenuItem key="3">outer</MenuItem>
<MenuItem disabled>disabled</MenuItem>
<MenuItem key="5">outer3</MenuItem>
</Menu>
);
}

render() {
const verticalMenu = this.renderCommonMenu({
mode: 'vertical',
openAnimation: 'zoom',
itemIcon,
expandIcon,
});

const inlineMenu = this.renderCommonMenu({
mode: 'inline',
defaultOpenKeys: ['1'],
openAnimation: animation,
itemIcon,
expandIcon,
});

return (
<div style={{ margin: 20 }}>
<h2>Antd menu - Custom icon</h2>
<div>
<h3>vertical</h3>
<div style={{ margin: 20, width: 200 }}>{verticalMenu}</div>
<h3>inline</h3>
<div style={{ margin: 20, width: 400 }}>{inlineMenu}</div>
</div>
</div>
);
}
}

ReactDOM.render(<Demo />, document.getElementById('__react-content'));
2 changes: 2 additions & 0 deletions src/Menu.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ class Menu extends React.Component {
activeKey: PropTypes.string,
prefixCls: PropTypes.string,
builtinPlacements: PropTypes.object,
itemIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
expandIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
};

static defaultProps = {
Expand Down
6 changes: 6 additions & 0 deletions src/MenuItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export class MenuItem extends React.Component {
multiple: PropTypes.bool,
isSelected: PropTypes.bool,
manualRef: PropTypes.func,
itemIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
};

static defaultProps = {
Expand Down Expand Up @@ -182,6 +183,10 @@ export class MenuItem extends React.Component {
style.paddingLeft = props.inlineIndent * props.level;
}
menuAllProps.forEach(key => delete props[key]);
let icon = this.props.itemIcon;
if (typeof this.props.itemIcon === 'function') {
icon = React.createElement(this.props.itemIcon, this.props);
}
return (
<li
{...props}
Expand All @@ -190,6 +195,7 @@ export class MenuItem extends React.Component {
style={style}
>
{props.children}
{icon}
</li>
);
}
Expand Down
18 changes: 17 additions & 1 deletion src/SubMenu.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ export class SubMenu extends React.Component {
store: PropTypes.object,
mode: PropTypes.oneOf(['horizontal', 'vertical', 'vertical-left', 'vertical-right', 'inline']),
manualRef: PropTypes.func,
itemIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
expandIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
};

static defaultProps = {
Expand Down Expand Up @@ -366,6 +368,8 @@ export class SubMenu extends React.Component {
prefixCls: props.rootPrefixCls,
id: this._menuId,
manualRef: this.saveMenuInstance,
itemIcon: props.itemIcon,
expandIcon: props.expandIcon,
};

const haveRendered = this.haveRendered;
Expand Down Expand Up @@ -461,6 +465,18 @@ export class SubMenu extends React.Component {
};
}

// expand custom icon should NOT be displayed in menu with horizontal mode.
let icon = null;
if (props.mode !== 'horizontal') {
icon = this.props.expandIcon; // ReactNode
if (typeof this.props.expandIcon === 'function') {
icon = React.createElement(
this.props.expandIcon,
{ ...this.props }
);
}
}

const title = (
<div
ref={this.saveSubMenuTitle}
Expand All @@ -474,7 +490,7 @@ export class SubMenu extends React.Component {
title={typeof props.title === 'string' ? props.title : undefined}
>
{props.title}
<i className={`${prefixCls}-arrow`} />
{icon || <i className={`${prefixCls}-arrow`} />}
</div>
);
const children = this.renderChildren(props.children);
Expand Down
4 changes: 4 additions & 0 deletions src/SubPopupMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ export class SubPopupMenu extends React.Component {
triggerSubMenuAction: PropTypes.oneOf(['click', 'hover']),
inlineIndent: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
manualRef: PropTypes.func,
itemIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
expandIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
};

static defaultProps = {
Expand Down Expand Up @@ -286,6 +288,8 @@ export class SubPopupMenu extends React.Component {
onDeselect: this.onDeselect,
onSelect: this.onSelect,
builtinPlacements: props.builtinPlacements,
itemIcon: childProps.itemIcon || this.props.itemIcon,
expandIcon: childProps.expandIcon || this.props.expandIcon,
...extraProps,
};
if (props.mode === 'inline') {
Expand Down
2 changes: 2 additions & 0 deletions src/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,6 @@ export const menuAllProps = [
'inlineCollapsed',
'menu',
'theme',
'itemIcon',
'expandIcon',
];
34 changes: 34 additions & 0 deletions tests/MenuItem.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,40 @@ import Menu, { MenuItem, MenuItemGroup, SubMenu } from '../src';
import { MenuItem as NakedMenuItem } from '../src/MenuItem';

describe('MenuItem', () => {
const subMenuIconText = 'SubMenuIcon';
const menuItemIconText = 'MenuItemIcon';
function itemIcon() {
return <span>{menuItemIconText}</span>;
}

function expandIcon() {
return <span>{subMenuIconText}</span>;
}

describe('custom icon', () => {
it('should render custom arrow icon correctly.', () => {
const wrapper = mount(
<Menu mode="vertical" itemIcon={itemIcon} expandIcon={expandIcon}>
<MenuItem key="1">1</MenuItem>
</Menu>
);
const menuItemText = wrapper.find('.rc-menu-item').first().text();
expect(menuItemText).toEqual(`1${menuItemIconText}`);
});

it('should render custom arrow icon correctly (with children props).', () => {
const targetText = 'target';
const wrapper = mount(
<Menu mode="vertical" itemIcon={itemIcon} expandIcon={expandIcon}>
<MenuItem key="1" itemIcon={() => <span>{targetText}</span>}>1</MenuItem>
<MenuItem key="2">2</MenuItem>
</Menu>
);
const menuItemText = wrapper.find('.rc-menu-item').first().text();
expect(menuItemText).toEqual(`1${targetText}`);
});
});

describe('disabled', () => {
it('can not be active by key down', () => {
const wrapper = mount(
Expand Down
Loading

0 comments on commit 07b4ccc

Please sign in to comment.