feat(dropdown): support group item

This commit is contained in:
07akioni 2021-01-06 00:09:53 +08:00
parent c75b83f085
commit 566b453d3c
12 changed files with 324 additions and 58 deletions

View File

@ -27,18 +27,18 @@ For other props, see [Popover Props](n-popover#Props). Note that `arrow`, `raw`
### DropdownOption Type
| Property | Type | Description |
| -------- | ------------------ | ---------------- |
| icon | `() => VNode` | |
| key | `string \| number` | Should be unique |
| label | `string` | |
| Property | Type | Description |
| -------- | ------------------ | ----------------- |
| icon | `() => VNode` | |
| key | `string \| number` | Should be unique. |
| label | `string` | |
### DropdownDivider Type
| Property | Type | Description |
| -------- | ------------------ | ---------------- |
| type | `'divider'` | |
| key | `string \| number` | Should be unique |
| Property | Type | Description |
| -------- | ------------------ | ----------------- |
| type | `'divider'` | |
| key | `string \| number` | Should be unique. |
### DropdownSubmenu Type
@ -47,5 +47,15 @@ For other props, see [Popover Props](n-popover#Props). Note that `arrow`, `raw`
| type | `'submenu'` | |
| label | `string` | |
| icon | `() => VNode` | |
| key | `string \| number` | Should be unique |
| key | `string \| number` | Should be unique. |
| children | `Array<DropdownOption \| DropdownDivider \| DropdownSubmenu>` | |
### DropdownGroup Type
| Property | Type | Description |
| --- | --- | --- |
| type | `'group'` | |
| label | `string` | |
| icon | `() => VNode` | |
| key | `string \| number` | Should be unique. |
| children | `Array<DropdownOption \| DropdownDivider \| DropdownSubmenu>` | |

View File

@ -0,0 +1,92 @@
# 成组
弄个选项组。
```html
<n-dropdown
:options="options"
placement="bottom-start"
trigger="click"
@select="handleSelect"
>
<n-button :keyboard="false">里面有组</n-button>
</n-dropdown>
```
```js
import { h, resolveComponent } from 'vue'
import { CashOutline as CashIcon } from '@vicons/ionicons-v5'
const options = [
{
type: 'group',
label: '主角和吃的',
key: 'main',
children: [
{
label: '杰·盖茨比',
key: 'jay gatsby'
},
{
label: '黛西·布坎南',
icon () {
return h(resolveComponent('n-icon'), null, {
default: () => h(CashIcon)
})
},
key: 'daisy buchanan'
},
{
label: '尼克·卡拉威',
key: 'nick carraway'
},
{
label: '吃的',
key: 'food',
children: [
{
label: '鸡肉',
key: 'chicken'
},
{
label: '牛肉',
key: 'beef'
}
]
}
]
},
{
type: 'divider',
key: 'd1'
},
{
label: '其他角色',
key: 'others1',
children: [
{
label: '乔丹·贝克',
key: 'jordan baker'
},
{
label: '汤姆·布坎南',
key: 'tom buchanan'
}
]
}
]
export default {
inject: ['message'],
data () {
return {
options
}
},
methods: {
handleSelect (key) {
this.message.info(key)
}
}
}
```

View File

@ -11,6 +11,7 @@ cascade
placement
size
manual-position
group-debug
```
## Props
@ -48,4 +49,14 @@ manual-position
| label | `string` | |
| icon | `() => VNode` | |
| key | `string \| number` | 需要唯一 |
| children | `Array<DropdownOption \| DropdownDivider \| DropdownGroup \| DropdownSubmenu>` | |
### DropdownGroup Type
| 属性 | 类型 | 说明 |
| --- | --- | --- |
| type | `'group'` | |
| label | `string` | |
| icon | `() => VNode` | |
| key | `string \| number` | 需要唯一 |
| children | `Array<DropdownOption \| DropdownDivider \| DropdownSubmenu>` | |

View File

@ -197,6 +197,7 @@ export default defineComponent({
prefixColor,
optionColorHover,
optionTextColor,
groupHeaderTextColor,
[createKey('optionIconSuffixWidth', size)]: optionIconSuffixWidth,
[createKey('optionSuffixWidth', size)]: optionSuffixWidth,
[createKey('optionIconPrefixWidth', size)]: optionIconPrefixWidth,
@ -221,7 +222,8 @@ export default defineComponent({
'--option-icon-suffix-width': optionIconSuffixWidth,
'--option-text-color': optionTextColor,
'--prefix-color': prefixColor,
'--suffix-color': suffixColor
'--suffix-color': suffixColor,
'--group-header-text-color': groupHeaderTextColor
}
})
}

View File

@ -0,0 +1,53 @@
import { defineComponent, Fragment, h } from 'vue'
import { warn } from '../../_utils'
import NDropdownOption from './DropdownOption'
import NDropdownDivider from './DropdownDivider.vue'
import NDropdownGroupHeader from './DropdownGroupHeader'
import { isDividerNode, isGroupNode } from './utils'
export default defineComponent({
name: 'NDropdownGroup',
props: {
tmNode: {
type: Object,
required: true
},
parentKey: {
type: [String, Number],
default: null
}
},
render () {
const { tmNode, parentKey } = this
const { children } = tmNode
return h(
Fragment,
[
h(NDropdownGroupHeader, {
tmNode,
key: tmNode.key
})
].concat(
children.map((child) => {
if (isDividerNode(child.rawNode)) {
return h(NDropdownDivider, {
key: child.key
})
}
if (isGroupNode(child.rawNode)) {
warn(
'dropdown',
'`group` node is allowed to be put in `group` node.'
)
return null
}
return h(NDropdownOption, {
tmNode: child,
parentKey,
key: child.key
})
})
)
)
}
})

View File

@ -0,0 +1,64 @@
import { defineComponent, h } from 'vue'
import { render } from '../../_utils'
export default defineComponent({
name: 'DropdownGroupHeader',
inject: ['NDropdown', 'NDropdownMenu'],
props: {
tmNode: {
type: Object,
required: true
}
},
render () {
const { rawNode } = this.tmNode
return h(
'div',
{
class: 'n-dropdown-option'
},
[
h(
'div',
{
class: 'n-dropdown-option-body n-dropdown-option-body--group'
},
[
h(
'div',
{
class: [
'n-dropdown-option-body__prefix',
{
'n-dropdown-option-body__prefix--show-icon': this
.NDropdownMenu.showIcon
}
],
'n-dropdown-option': true
},
[h(render, { render: rawNode.icon })]
),
h(
'div',
{
class: 'n-dropdown-option-body__label',
'n-dropdown-option': true
},
[h(render, { render: rawNode.label })]
),
h('div', {
class: [
'n-dropdown-option-body__suffix',
{
'n-dropdown-option-body__suffix--has-submenu': this
.NDropdownMenu.hasSubmenu
}
],
'n-dropdown-option': true
})
]
)
]
)
}
})

View File

@ -1,7 +1,8 @@
import { defineComponent, h } from 'vue'
import NDropdownOption from './DropdownOption'
import NDropdownDivider from './DropdownDivider.vue'
import { isSubmenuNode } from './utils'
import NDropdownGroup from './DropdownGroup'
import { isSubmenuNode, isGroupNode, isDividerNode } from './utils'
export default defineComponent({
name: 'DropdownMenu',
@ -23,35 +24,50 @@ export default defineComponent({
},
computed: {
showIcon () {
return this.tmNodes.some((tmNode) => tmNode.rawNode.icon)
return this.tmNodes.some((tmNode) => {
const { rawNode } = tmNode
if (isGroupNode(rawNode)) {
return !!rawNode.children?.some((rawChild) => rawChild.icon)
}
return rawNode.icon
})
},
hasSubmenu () {
return this.tmNodes.some((tmNode) => isSubmenuNode(tmNode.rawNode))
return this.tmNodes.some((tmNode) => {
const { rawNode } = tmNode
if (isGroupNode(rawNode)) {
return !!rawNode.children?.some((rawChild) => isSubmenuNode(rawChild))
}
return isSubmenuNode(rawNode)
})
}
},
render () {
const {
NDropdown: { size }
} = this
const { parentKey } = this
return h(
'div',
{
class: ['n-dropdown-menu', `n-dropdown-menu--${size}-size`]
class: 'n-dropdown-menu'
},
[
this.tmNodes.map((tmNode) => {
if (tmNode.rawNode.type === 'divider') {
return h(NDropdownDivider, {
key: tmNode.key
})
}
return h(NDropdownOption, {
tmNode: tmNode,
parentKey: this.parentKey,
this.tmNodes.map((tmNode) => {
if (isDividerNode(tmNode.rawNode)) {
return h(NDropdownDivider, {
key: tmNode.key
})
}
if (isGroupNode(tmNode.rawNode)) {
return h(NDropdownGroup, {
tmNode,
parentKey,
key: tmNode.key
})
}
return h(NDropdownOption, {
tmNode,
parentKey,
key: tmNode.key
})
]
})
)
}
})

View File

@ -1,9 +1,9 @@
import { h, computed, inject, ref, Transition, defineComponent } from 'vue'
import { VBinder, VTarget, VFollower } from 'vueuc'
import { useMemo } from 'vooks'
import { render } from '../../_utils'
import { ChevronRightIcon } from '../../_base/icons'
import { NIcon } from '../../icon'
import { useMemo } from 'vooks'
import { useDelayedTrue } from '../../_utils/composable'
import NDropdownMenu from './DropdownMenu'
import { isSubmenuNode } from './utils'

View File

@ -29,15 +29,32 @@ export default cB('dropdown-menu', {
`
}, [
fadeInScaleUpTransition(),
cB('dropdown-option', [
cB('dropdown-option', {
position: 'relative'
}, [
cB('dropdown-option-body', {
display: 'flex',
cursor: 'default',
height: 'var(--option-height)',
lineHeight: 'var(--option-height)',
fontSize: 'var(--font-size)',
color: 'var(--option-text-color)',
transition: 'color .3s var(--bezier)'
}, [
cM('pending', {
backgroundColor: 'var(--option-color-hover)'
}),
cM('group', {
color: 'var(--group-header-text-color)'
}, [
cE('prefix', {
width: 'calc(var(--option-prefix-width) / 2)'
}, [
cM('show-icon', {
width: 'calc(var(--option-icon-prefix-width) / 2)'
})
])
]),
cE('prefix', {
width: 'var(--option-prefix-width)',
display: 'flex',
@ -75,23 +92,13 @@ export default cB('dropdown-menu', {
color: 'var(--suffix-color)',
fontSize: '16px'
})
])
])
]),
cB('dropdown-divider', {
transition: 'background-color .3s var(--bezier)',
backgroundColor: 'var(--divider-color)',
height: '1px',
margin: '4px 0'
}),
cB('dropdown-option', {
position: 'relative'
}, [
cB('dropdown-option-body', {
cursor: 'default'
}, [
cM('pending', {
backgroundColor: 'var(--option-color-hover)'
]),
cB('dropdown-menu', {
pointerEvents: 'all'
}),
cB('dropdown-menu-wrapper', {
transformOrigin: 'inherit',
width: 'fit-content'
})
]),
cB('dropdown-offset-container', {
@ -101,13 +108,12 @@ export default cB('dropdown-menu', {
right: 0,
top: '-4px',
bottom: '-4px'
}),
cB('dropdown-menu', {
pointerEvents: 'all'
}),
cB('dropdown-menu-wrapper', {
transformOrigin: 'inherit',
width: 'fit-content'
})
])
]),
cB('dropdown-divider', {
transition: 'background-color .3s var(--bezier)',
backgroundColor: 'var(--divider-color)',
height: '1px',
margin: '4px 0'
})
])

View File

@ -4,3 +4,11 @@ export function isSubmenuNode (rawNode) {
(rawNode.type === undefined && rawNode.children)
)
}
export function isGroupNode (rawNode) {
return rawNode.type === 'group'
}
export function isDividerNode (rawNode) {
return rawNode.type === 'divider'
}

View File

@ -19,7 +19,8 @@ export default {
heightSmall,
heightMedium,
heightLarge,
heightHuge
heightHuge,
textColor3Overlay
} = vars
return {
...commonVariables,
@ -35,6 +36,7 @@ export default {
suffixColor: textColor2,
prefixColor: textColor2,
optionColorHover: hoverColorOverlay,
groupHeaderTextColor: textColor3Overlay,
fontSizeSmall,
fontSizeMedium,
fontSizeLarge,

View File

@ -19,7 +19,8 @@ export default {
heightSmall,
heightMedium,
heightLarge,
heightHuge
heightHuge,
textColor3
} = vars
return {
...commonVariables,
@ -35,6 +36,7 @@ export default {
suffixColor: textColor2,
prefixColor: textColor2,
optionColorHover: hoverColorOverlay,
groupHeaderTextColor: textColor3,
fontSizeSmall,
fontSizeMedium,
fontSizeLarge,