mirror of
https://github.com/tusen-ai/naive-ui.git
synced 2025-01-24 12:45:18 +08:00
feat(dropdown): support group item
This commit is contained in:
parent
c75b83f085
commit
566b453d3c
@ -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>` | |
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
@ -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>` | |
|
||||
|
@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
|
53
src/dropdown/src/DropdownGroup.js
Normal file
53
src/dropdown/src/DropdownGroup.js
Normal 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
|
||||
})
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
})
|
64
src/dropdown/src/DropdownGroupHeader.js
Normal file
64
src/dropdown/src/DropdownGroupHeader.js
Normal 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
|
||||
})
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
}
|
||||
})
|
@ -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
|
||||
})
|
||||
]
|
||||
})
|
||||
)
|
||||
}
|
||||
})
|
||||
|
@ -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'
|
||||
|
@ -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'
|
||||
})
|
||||
])
|
||||
|
@ -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'
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user