feat(menu): add render-icon prop (#569)

* feat(menu): n-menu add render-icon prop

* feat: optimization
This commit is contained in:
XieZongChen 2021-07-22 12:58:16 -05:00 committed by GitHub
parent 5d7ae9ea18
commit a833d9cfee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 79 additions and 45 deletions

View File

@ -4,6 +4,7 @@
### Feats
- `n-menu` add `render-icon` prop.
- `n-upload` add `show-file-list` prop.
- `n-dropdown` add `render-icon` prop.
- `n-checkbox-group` add `min` and `max` prop.

View File

@ -4,6 +4,7 @@
### Feats
- `n-menu` 新增 `render-icon` 属性
- `n-upload` 新增 `show-file-list` 属性
- `n-dropdown` 新增 `render-icon` 属性
- `n-checkbox-group` 新增 `min``max` 属性

View File

@ -36,6 +36,7 @@ long-label
| inverted | `boolean` | `false` | Use inverted style. |
| options | `Array<MenuOption \| MenuOptionGroup>` | `[]` | Items data of menu. |
| mode | `'vertical' \| 'horizontal'` | `'vertical'` | Menu layout. |
| render-icon | `(option: MenuOption \| MenuGroupOption) => VNodeChild` | `undefined` | Render function that renders all icons. |
| render-label | `(option: MenuOption \| MenuGroupOption) => VNodeChild` | `undefined` | Render function that renders all labels. |
| root-indent | `number` | `undefined` | The indent of menu's first level children. If not set, menu will use `indent` in place of it. |
| value | `string \| null` | `undefined` | The selected name of menu. |

View File

@ -1,6 +1,6 @@
# Render Label
The `render-label` can be used to batch render menu options.
The `render-label`, `render-icon` can be used to batch render menu options.
```html
<n-space vertical>
@ -22,6 +22,7 @@ The `render-label` can be used to batch render menu options.
:collapsed-icon-size="22"
:options="menuOptions"
:render-label="renderMenuLabel"
:render-icon="renderMenuIcon"
/>
</n-layout-sider>
<n-layout>
@ -34,27 +35,17 @@ The `render-label` can be used to batch render menu options.
```js
import { h, ref, defineComponent } from 'vue'
import { NIcon } from 'naive-ui'
import {
BookOutline as BookIcon,
PersonOutline as PersonIcon,
WineOutline as WineIcon
} from '@vicons/ionicons5'
function renderIcon (icon) {
return () => h(NIcon, null, { default: () => h(icon) })
}
import { HappyOutline } from '@vicons/ionicons5'
const menuOptions = [
{
label: 'Hear the Wind Sing',
key: 'hear-the-wind-sing',
icon: renderIcon(BookIcon),
href: 'https://en.wikipedia.org/wiki/Hear_the_Wind_Sing'
},
{
label: 'Pinball 1973',
key: 'pinball-1973',
icon: renderIcon(BookIcon),
disabled: true,
children: [
{
@ -66,13 +57,11 @@ const menuOptions = [
{
label: 'A Wild Sheep Chase',
key: 'a-wild-sheep-chase',
disabled: true,
icon: renderIcon(BookIcon)
disabled: true
},
{
label: 'Dance Dance Dance',
key: 'Dance Dance Dance',
icon: renderIcon(BookIcon),
children: [
{
type: 'group',
@ -81,20 +70,17 @@ const menuOptions = [
children: [
{
label: 'Narrator',
key: 'narrator',
icon: renderIcon(PersonIcon)
key: 'narrator'
},
{
label: 'Sheep Man',
key: 'sheep-man',
icon: renderIcon(PersonIcon)
key: 'sheep-man'
}
]
},
{
label: 'Beverage',
key: 'beverage',
icon: renderIcon(WineIcon),
children: [
{
label: 'Whisky',
@ -131,6 +117,9 @@ export default defineComponent({
return h('a', { href: option.href, target: '_blank' }, option.label)
}
return option.label
},
renderMenuIcon () {
return h(NIcon, null, { default: () => h(HappyOutline) })
}
}
}

View File

@ -36,7 +36,8 @@ long-label
| inverted | `boolean` | `false` | 使用反转样式 |
| options | `Array<MenuOption \| MenuOptionGroup>` | `[]` | 菜单的数据 |
| mode | `'vertical' \| 'horizontal'` | `'vertical'` | 菜单的布局方式 |
| render-label | `(option: MenuOption \| MenuGroupOption) => VNodeChild` | `undefined` | 批量处理菜单渲染 |
| render-icon | `(option: MenuOption \| MenuGroupOption) => VNodeChild` | `undefined` | 批量处理菜单图标渲染 |
| render-label | `(option: MenuOption \| MenuGroupOption) => VNodeChild` | `undefined` | 批量处理菜单标签渲染 |
| root-indent | `number` | `32` | 菜单第一级的缩进,如果没有设定,使用 `indent` 代替 |
| value | `string \| null` | `undefined` | 菜单当前的选中值 |
| on-update:expanded-keys | `(keys: string[]) => void` | `undefined` | `keys` 是展开菜单项的 `key` 的数组 |

View File

@ -1,6 +1,6 @@
# 批量处理菜单渲染
使用 `render-label` 可以批量控制菜单的选项渲染。
使用 `render-label`、`render-icon` 可以批量控制菜单的选项渲染。
```html
<n-space vertical>
@ -22,6 +22,7 @@
:collapsed-icon-size="22"
:options="menuOptions"
:render-label="renderMenuLabel"
:render-icon="renderMenuIcon"
/>
</n-layout-sider>
<n-layout>
@ -34,27 +35,17 @@
```js
import { h, ref, defineComponent } from 'vue'
import { NIcon } from 'naive-ui'
import {
BookOutline as BookIcon,
PersonOutline as PersonIcon,
WineOutline as WineIcon
} from '@vicons/ionicons5'
function renderIcon (icon) {
return () => h(NIcon, null, { default: () => h(icon) })
}
import { HappyOutline } from '@vicons/ionicons5'
const menuOptions = [
{
label: '且听风吟',
key: 'hear-the-wind-sing',
icon: renderIcon(BookIcon),
href: 'https://baike.baidu.com/item/%E4%B8%94%E5%90%AC%E9%A3%8E%E5%90%9F/3199'
},
{
label: '1973年的弹珠玩具',
key: 'pinball-1973',
icon: renderIcon(BookIcon),
disabled: true,
children: [
{
@ -66,13 +57,11 @@ const menuOptions = [
{
label: '寻羊冒险记',
key: 'a-wild-sheep-chase',
disabled: true,
icon: renderIcon(BookIcon)
disabled: true
},
{
label: '舞,舞,舞',
key: 'dance-dance-dance',
icon: renderIcon(BookIcon),
children: [
{
type: 'group',
@ -81,20 +70,17 @@ const menuOptions = [
children: [
{
label: '叙事者',
key: 'narrator',
icon: renderIcon(PersonIcon)
key: 'narrator'
},
{
label: '羊男',
key: 'sheep-man',
icon: renderIcon(PersonIcon)
key: 'sheep-man'
}
]
},
{
label: '饮品',
key: 'beverage',
icon: renderIcon(WineIcon),
children: [
{
label: '威士忌',
@ -131,6 +117,9 @@ export default defineComponent({
return h('a', { href: option.href, target: '_blank' }, option.label)
}
return option.label
},
renderMenuIcon () {
return h(NIcon, null, { default: () => h(HappyOutline) })
}
}
}

View File

@ -149,6 +149,9 @@ const menuProps = {
},
default: undefined
},
renderIcon: Function as PropType<
(option: MenuOption | MenuGroupOption) => VNodeChild
>,
renderLabel: Function as PropType<
(option: MenuOption | MenuGroupOption) => VNodeChild
>,

View File

@ -63,7 +63,7 @@ export default defineComponent({
const {
clsPrefix,
tmNode,
menuProps: { renderLabel }
menuProps: { renderIcon, renderLabel }
} = this
return (
<div
@ -80,13 +80,13 @@ export default defineComponent({
]}
style={this.style}
>
{this.icon ? (
{renderIcon || this.icon ? (
<div
class={`${clsPrefix}-menu-item-content__icon`}
style={this.iconStyle}
role="none"
>
{render(this.icon)}
{renderIcon ? renderIcon(tmNode.rawNode) : render(this.icon)}
</div>
) : null}
<div class={`${clsPrefix}-menu-item-content-header`} role="none">

View File

@ -116,7 +116,7 @@ export default defineComponent({
render () {
const {
mergedClsPrefix,
menuProps: { renderLabel }
menuProps: { renderIcon, renderLabel }
} = this
const createSubmenuItem = (): VNode => {
const {
@ -184,6 +184,7 @@ export default defineComponent({
options={this.rawNodes}
onSelect={this.doSelect}
inverted={this.inverted}
renderIcon={renderIcon}
renderLabel={renderLabel}
>
{{

View File

@ -1,7 +1,9 @@
import { mount } from '@vue/test-utils'
import { HappyOutline } from '@vicons/ionicons5'
import { h } from 'vue'
import { sleep } from 'seemly'
import { NMenu } from '../index'
import { NIcon } from '../../icon'
describe('n-menu', () => {
it('should work with import on demand', () => {
@ -133,4 +135,50 @@ describe('n-menu', () => {
expect(document.querySelectorAll('a').length).toEqual(3)
expect(document.querySelectorAll('a.fantasy').length).toEqual(1)
})
it('should dropdown work with `render-icon` props', async () => {
const options = [
{
label: 'jj',
key: 'jj'
},
{
label: 'jay',
key: 'jay',
children: [
{
type: 'group',
label: 'song-group',
key: 'group',
children: [
{
label: 'fantasy',
key: 'fantasy'
},
{
label: 'mojito',
key: 'mojito'
}
]
}
]
}
]
function renderMenuIcon (): any {
return h(NIcon, null, { default: () => h(HappyOutline) })
}
const wrapper = mount(NMenu, {
props: {
options: options,
collapsed: true,
renderIcon: renderMenuIcon
}
})
expect(wrapper.find('.n-submenu').exists()).toBe(true)
await wrapper.find('.n-submenu').trigger('mouseenter')
// Popover has delay, so we need to wait
await sleep(150)
expect(document.body.querySelector('.n-dropdown')).not.toEqual(null)
expect(document.querySelectorAll('.n-icon').length).toEqual(2)
})
})