Skip to content

Commit

Permalink
feat(dropdown): add render-label prop (#321)
Browse files Browse the repository at this point in the history
* feat(dropdown): add render-label prrop

* feat(dropdown): opt code

* feat(dropdown): add render-label test

* feat(dropdown): update snapshot

* feat(dropdown): optimaze test case

* Update src/dropdown/tests/Dropdown.spec.ts

* Update src/dropdown/tests/Dropdown.spec.ts

Co-authored-by: 07akioni <07akioni2@gmail.com>
  • Loading branch information
timeTravelCYN and 07akioni authored Jul 1, 2021
1 parent 8ee2086 commit 8564f95
Show file tree
Hide file tree
Showing 10 changed files with 456 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
- `n-select` add `render-option` prop.
- `n-select` export `SelectOption` & `SelectGroupOption` type.
- `n-popover` add `header` slot.
- `n-dropdown` add `render-label` prop.

### Fixes

Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
- `n-select` 新增 `render-option` 属性
- `n-select` 导出 `SelectOption` & `SelectGroupOption` 类型
- `n-popover` 支持使用 `header` slot
- `n-dropdown` 新增 `render-label` 属性

### Fixes

Expand Down
2 changes: 2 additions & 0 deletions src/dropdown/demos/enUS/index.demo-entry.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ cascade
placement
size
manual-position
render-label
```

## Props
Expand All @@ -21,6 +22,7 @@ manual-position
| inverted | `boolean` | `false` | Use inverted style. |
| keyboard | `boolean` | `true` | Whether is supports keyboard operation. (Be careful about the potential conflicts with other components keyboard operations) |
| options | `Array<DropdownOption \| DropdownDivider \| DropdownSubmenu>` | `[]` | Options of the dropdown. |
| render-label | `(option: DropdownOption \| DropdownSubmenu) => VNodeChild` | `undefined` | Render function that renders all labels. |
| size | `'small'\|'medium'\|'large'\|'huge'` | `'medium'` | Dropdown size. |
| on-clickoutside | `(e: MouseEvent) => void` | `undefined` | Callback function triggered when clickoutside. |
| on-select | `(key: string \| number) => void` | `undefined` | Callback function triggered on blur. |
Expand Down
89 changes: 89 additions & 0 deletions src/dropdown/demos/enUS/render-label.demo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Render Label

The `renderLabel` can be used to batch render dropdown options.

```html
<n-dropdown
:options="options"
placement="bottom-start"
trigger="click"
@select="handleSelect"
:render-label="renderDropdownLabel"
>
<n-button :keyboard="false">People and Some Food to Eat</n-button>
</n-dropdown>
```

```js
import { h, defineComponent } from 'vue'
import { NIcon, useMessage } from 'naive-ui'
import { CashOutline as CashIcon } from '@vicons/ionicons5'

const options = [
{
label: 'Jay Gatsby',
key: 'jay gatsby'
},
{
label: 'Daisy Buchanan',
icon () {
return h(NIcon, null, {
default: () => h(CashIcon)
})
},
key: 'daisy buchanan'
},
{
type: 'divider',
key: 'd1'
},
{
label: 'Nick Carraway',
key: 'nick carraway'
},
{
label: 'Others',
key: 'others1',
children: [
{
label: 'Jordan Baker',
key: 'jordan baker'
},
{
label: 'Tom Buchanan',
key: 'tom buchanan'
},
{
label: 'Others',
key: 'others2',
disabled: true,
children: [
{
label: 'Chicken',
key: 'chicken'
},
{
label: 'Beef',
key: 'beef'
}
]
}
]
}
]

export default defineComponent({
data () {
const message = useMessage()
return {
options,
renderDropdownLabel (option) {
return h('span', {}, { default: () => option.label })
},
handleSelect (key) {
message.info(key)
}
}
}
})
```
2 changes: 2 additions & 0 deletions src/dropdown/demos/zhCN/index.demo-entry.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ placement
size
manual-position
group-debug
render-label
```

## Props
Expand All @@ -22,6 +23,7 @@ group-debug
| inverted | `boolean` | `false` | 使用反转样式 |
| keyboard | `boolean` | `true` | 是否支持键盘操作(注意和其他内容键盘操作可能的冲突) |
| options | `Array<DropdownOption \| DropdownDivider \| DropdownSubmenu>` | `[]` | 下拉菜单传入的 options |
| render-label | `(option: DropdownOption \| DropdownSubmenu) => VNodeChild` | `undefined` | 批量处理下拉菜单渲染 |
| size | `'small'\|'medium'\|'large'\|'huge'` | `'medium'` | 下拉菜单的尺寸大小 |
| on-clickoutside | `(e: MouseEvent) => void` | `undefined` | clickoutside 的时候触发的回调函数 |
| on-select | `(key: string \| number) => void` | `undefined` | select 选中时触发的回调函数 |
Expand Down
89 changes: 89 additions & 0 deletions src/dropdown/demos/zhCN/render-label.demo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# 批量处理菜单渲染

使用 `renderLabel` 可以批量控制下拉菜单的选项渲染。

```html
<n-dropdown
:options="options"
placement="bottom-start"
trigger="click"
@select="handleSelect"
:render-label="renderDropdownLabel"
>
<n-button :keyboard="false">人物和食物</n-button>
</n-dropdown>
```

```js
import { h, defineComponent } from 'vue'
import { NIcon, useMessage } from 'naive-ui'
import { CashOutline as CashIcon } from '@vicons/ionicons5'

const options = [
{
label: '杰·盖茨比',
key: 'jay gatsby'
},
{
label: '黛西·布坎南',
icon () {
return h(NIcon, null, {
default: () => h(CashIcon)
})
},
key: 'daisy buchanan'
},
{
type: 'divider',
key: 'd1'
},
{
label: '尼克·卡拉威',
key: 'nick carraway'
},
{
label: '其他',
key: 'others1',
children: [
{
label: '乔丹·贝克',
key: 'jordan baker'
},
{
label: '汤姆·布坎南',
key: 'tom buchanan'
},
{
label: '其他',
key: 'others2',
disabled: true,
children: [
{
label: '鸡肉',
key: 'chicken'
},
{
label: '牛肉',
key: 'beef'
}
]
}
]
}
]

export default defineComponent({
setup () {
const message = useMessage()
return {
options,
renderDropdownLabel (option) {
return h('span', {}, { default: () => option.label })
},
handleSelect (key) {
message.info(key)
}
}
}
})
```
9 changes: 7 additions & 2 deletions src/dropdown/src/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
CSSProperties,
InjectionKey,
Ref,
mergeProps
mergeProps,
VNodeChild
} from 'vue'
import { createTreeMate, Key, TreeMateOptions, TreeNode } from 'treemate'
import { useMergedState, useKeyboard, useMemo } from 'vooks'
Expand Down Expand Up @@ -58,7 +59,9 @@ DropdownIgnoredOption
}
}

export type RenderLabelImpl = (option: DropdownMixedOption) => VNodeChild
export interface DropdownInjection {
renderLabelRef: Ref<RenderLabelImpl | undefined>
hoverKeyRef: Ref<Key | null>
keyboardKeyRef: Ref<Key | null>
lastToggledSubmenuKeyRef: Ref<Key | null>
Expand Down Expand Up @@ -97,7 +100,8 @@ const dropdownBaseProps = {
default: () => []
},
// for menu, not documented
value: [String, Number] as PropType<Key | null>
value: [String, Number] as PropType<Key | null>,
renderLabel: Function as PropType<RenderLabelImpl>
} as const

const popoverPropKeys = Object.keys(popoverBaseProps) as Array<
Expand Down Expand Up @@ -197,6 +201,7 @@ export default defineComponent({
)

provide(dropdownInjectionKey, {
renderLabelRef: toRef(props, 'renderLabel'),
hoverKeyRef: hoverKeyRef,
keyboardKeyRef: keyboardKeyRef,
lastToggledSubmenuKeyRef: lastToggledSubmenuKeyRef,
Expand Down
11 changes: 8 additions & 3 deletions src/dropdown/src/DropdownOption.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ export default defineComponent({
pendingKeyPathRef,
activeKeyPathRef,
animatedRef,
mergedShowRef
mergedShowRef,
renderLabelRef
} = NDropdown
const NDropdownOption = inject(dropdownOptionInjectionKey, null)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
Expand Down Expand Up @@ -153,6 +154,7 @@ export default defineComponent({
}
}
return {
renderLabel: renderLabelRef,
siblingHasIcon: NDropdownMenu.showIconRef,
siblingHasSubmenu: NDropdownMenu.hasSubmenuRef,
animated: animatedRef,
Expand Down Expand Up @@ -196,7 +198,8 @@ export default defineComponent({
mergedShowSubmenu,
clsPrefix,
siblingHasIcon,
siblingHasSubmenu
siblingHasSubmenu,
renderLabel
} = this
const submenuVNode = mergedShowSubmenu ? (
<NDropdownMenu
Expand Down Expand Up @@ -239,7 +242,9 @@ export default defineComponent({
class={`${clsPrefix}-dropdown-option-body__label`}
>
{/* TODO: Workaround, menu campatible */}
{render(rawNode.label ?? rawNode.title)}
{renderLabel
? renderLabel(rawNode)
: render(rawNode.label ?? rawNode.title)}
</div>
<div
__dropdown-option
Expand Down
32 changes: 29 additions & 3 deletions src/dropdown/tests/Dropdown.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { VueWrapper } from '@vue/test-utils/dist/vueWrapper'
import { mount } from '@vue/test-utils'
import { ComponentPublicInstance, h, nextTick } from 'vue'
import { ComponentPublicInstance, h, nextTick, VNodeChild } from 'vue'
import { NIcon } from '../../icon'
import { DropdownMixedOption } from '../src/interface'
import { CashOutline as CashIcon } from '@vicons/ionicons5'
import { NDropdown, DropdownProps } from '../index'

Expand Down Expand Up @@ -55,7 +56,8 @@ const mountDropdown = ({
inverted = false,
onClickoutside = undefined,
options: data = options,
show = undefined
show = undefined,
renderLabel = undefined
}: DropdownProps = {}): VueWrapper<ComponentPublicInstance> => {
return mount(NDropdown, {
attachTo: document.body,
Expand All @@ -65,7 +67,8 @@ const mountDropdown = ({
onSelect,
inverted,
onClickoutside,
show
show,
renderLabel
},
slots: {
default: () => 'star kirby'
Expand Down Expand Up @@ -233,4 +236,27 @@ describe('n-dropdown', () => {
})
expect(onClickoutside).toHaveBeenCalled()
})

it('should work with `render-label` props', async () => {
const renderDropdownLabel = (option: DropdownMixedOption): VNodeChild => {
return h(
'a',
{
href: 'renderLabel'
},
{ default: () => option.label }
)
}
const wrapper = await mountDropdown({
renderLabel: renderDropdownLabel
})
const triggerNodeWrapper = wrapper.find('span')
await triggerNodeWrapper.trigger('click')
expect(document.querySelector('.n-dropdown')).toMatchSnapshot()
expect(
document
.querySelectorAll('.n-dropdown a[href="renderLabel"]').length
).toBe(3)
wrapper.unmount()
})
})
Loading

0 comments on commit 8564f95

Please # to comment.