fix(dropdown): scrollable prop not work (#3099)

* fix(dropdown): scrollable prop not work

* fix(dropdown): teleport to menu node when scrollable

* feat(dropdown): showSubmenu when hover scrollbar

* fix(dropdown): use popoverBody in teleport

* fix(docs): conflicts

Co-authored-by: 07akioni <07akioni2@gmail.com>
This commit is contained in:
Yhspehy 2022-07-03 20:23:37 +08:00 committed by GitHub
parent 1dd6f421f0
commit 4388c109d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 110 additions and 51 deletions

View File

@ -9,6 +9,8 @@ Show an arrow with the dropdown.
trigger="click"
:options="options"
:show-arrow="true"
scrollable
style="max-height: 100px"
@select="handleSelect"
>
<n-button>Go For a Trip</n-button>

View File

@ -7,6 +7,8 @@
trigger="click"
:options="options"
:show-arrow="true"
scrollable
style="max-height: 100px"
@select="handleSelect"
>
<n-button>找个地方休息</n-button>

View File

@ -5,14 +5,27 @@
</markdown>
<template>
<n-dropdown
:options="options"
placement="bottom-start"
trigger="click"
@select="handleSelect"
>
<n-button>人物和食物</n-button>
</n-dropdown>
<n-space>
<n-dropdown
:options="options"
placement="bottom-start"
trigger="click"
@select="handleSelect"
>
<n-button>人物和食物</n-button>
</n-dropdown>
<n-dropdown
:options="options"
placement="bottom-start"
trigger="click"
scrollable
style="max-height: 100px"
@select="handleSelect"
>
<n-button>人物和食物-scrollable</n-button>
</n-dropdown>
</n-space>
</template>
<script lang="ts">
@ -57,7 +70,6 @@ const options = [
{
label: '其他',
key: 'others2',
disabled: true,
children: [
{
label: '鸡肉',

View File

@ -431,6 +431,7 @@ export default defineComponent({
style: [style, this.cssVars as any],
showArrow: this.showArrow,
arrowStyle: this.arrowStyle,
scrollable: this.scrollable,
onMouseenter,
onMouseleave
}

View File

@ -11,6 +11,7 @@ import {
} from 'vue'
import { TreeNode } from 'treemate'
import { renderArrow } from '../../popover/src/PopoverBody'
import { NxScrollbar } from '../../_internal/scrollbar'
import NDropdownDivider from './DropdownDivider'
// eslint-disable-next-line import/no-cycle
import NDropdownGroup from './DropdownGroup'
@ -42,6 +43,7 @@ export interface NDropdownMenuInjection {
export default defineComponent({
name: 'DropdownMenu',
props: {
scrollable: Boolean,
showArrow: Boolean,
arrowStyle: [String, Object] as PropType<string | CSSProperties>,
clsPrefix: {
@ -99,42 +101,57 @@ export default defineComponent({
}
},
render () {
const { parentKey, clsPrefix } = this
const { parentKey, clsPrefix, scrollable } = this
const menuOptionsNode = this.tmNodes.map((tmNode) => {
const { rawNode } = tmNode
if (isRenderNode(rawNode)) {
return (
<NDropdownRenderOption
tmNode={tmNode as unknown as TreeNode<DropdownRenderOption>}
key={tmNode.key}
/>
)
}
if (isDividerNode(rawNode)) {
return <NDropdownDivider clsPrefix={clsPrefix} key={tmNode.key} />
}
if (isGroupNode(rawNode)) {
return (
<NDropdownGroup
clsPrefix={clsPrefix}
tmNode={tmNode}
parentKey={parentKey}
key={tmNode.key}
/>
)
}
return (
<NDropdownOption
clsPrefix={clsPrefix}
tmNode={tmNode}
parentKey={parentKey}
key={tmNode.key}
props={rawNode.props}
scrollable={scrollable}
/>
)
})
return (
<div class={`${clsPrefix}-dropdown-menu`} ref="bodyRef">
{this.tmNodes.map((tmNode) => {
const { rawNode } = tmNode
if (isRenderNode(rawNode)) {
return (
<NDropdownRenderOption
tmNode={tmNode as unknown as TreeNode<DropdownRenderOption>}
key={tmNode.key}
/>
)
}
if (isDividerNode(rawNode)) {
return <NDropdownDivider clsPrefix={clsPrefix} key={tmNode.key} />
}
if (isGroupNode(rawNode)) {
return (
<NDropdownGroup
clsPrefix={clsPrefix}
tmNode={tmNode}
parentKey={parentKey}
key={tmNode.key}
/>
)
}
return (
<NDropdownOption
clsPrefix={clsPrefix}
tmNode={tmNode}
parentKey={parentKey}
key={tmNode.key}
props={rawNode.props}
/>
)
})}
<div
class={`${clsPrefix}-dropdown-menu ${
scrollable ? clsPrefix + '-dropdown-menu--scrollable' : ''
} `}
ref="bodyRef"
>
{scrollable ? (
<NxScrollbar contentClass={`${clsPrefix}-dropdown-menu__content`}>
{{
default: () => menuOptionsNode
}}
</NxScrollbar>
) : (
menuOptionsNode
)}
{this.showArrow
? renderArrow({
clsPrefix,

View File

@ -23,6 +23,7 @@ import {
dropdownInjectionKey,
dropdownOptionInjectionKey
} from './context'
import { popoverBodyInjectionKey } from '../../popover/src/interface'
import { isSubmenuNode } from './utils'
import { TreeNode } from 'treemate'
import {
@ -57,7 +58,8 @@ export default defineComponent({
type: String as PropType<FollowerPlacement>,
default: 'right-start'
},
props: Object as PropType<HTMLAttributes>
props: Object as PropType<HTMLAttributes>,
scrollable: Boolean
},
setup (props) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
@ -80,6 +82,7 @@ export default defineComponent({
const NDropdownOption = inject(dropdownOptionInjectionKey, null)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const NDropdownMenu = inject(dropdownMenuInjectionKey)!
const NPopoverBody = inject(popoverBodyInjectionKey) as Ref<HTMLElement>
const rawNodeRef = computed(() => props.tmNode.rawNode)
const hasSubmenuRef = computed(() => {
const { value: childrenField } = childrenFieldRef
@ -145,9 +148,13 @@ export default defineComponent({
function handleMouseLeave (e: MouseEvent): void {
if (!mergedShowRef.value) return
const { relatedTarget } = e
const { clsPrefix } = props
if (
relatedTarget &&
!happensIn({ target: relatedTarget }, 'dropdownOption')
!happensIn({ target: relatedTarget }, 'dropdownOption') &&
(relatedTarget as HTMLElement).className.indexOf(
`${clsPrefix}-scrollbar-rail__scrollbar`
)
) {
hoverKeyRef.value = null
}
@ -164,12 +171,14 @@ export default defineComponent({
NDropdown.doUpdateShow(false)
}
}
return {
labelField: labelFieldRef,
renderLabel: renderLabelRef,
renderIcon: renderIconRef,
siblingHasIcon: NDropdownMenu.showIconRef,
siblingHasSubmenu: NDropdownMenu.hasSubmenuRef,
popoverBody: NPopoverBody,
animated: animatedRef,
mergedShowSubmenu: computed(() => {
return deferredShowSubmenuRef.value && !parentEnteringSubmenuRef.value
@ -218,7 +227,9 @@ export default defineComponent({
renderIcon,
renderOption,
nodeProps,
props
props,
scrollable,
popoverBody
} = this
const submenuVNode = mergedShowSubmenu ? (
<NDropdownMenu
@ -290,7 +301,8 @@ export default defineComponent({
<VFollower
show={this.mergedShowSubmenu}
placement={this.placement}
teleportDisabled
to={scrollable ? popoverBody : undefined}
teleportDisabled={!scrollable}
>
{{
default: () => {

View File

@ -1,4 +1,4 @@
import { c, cB, cM, cE } from '../../../_utils/cssr'
import { c, cB, cM, cE, cNotM } from '../../../_utils/cssr'
import { fadeInScaleUpTransition } from '../../../_styles/transitions/fade-in-scale-up.cssr'
// vars:
@ -29,7 +29,6 @@ import { fadeInScaleUpTransition } from '../../../_styles/transitions/fade-in-sc
export default cB('dropdown-menu', `
transform-origin: inherit;
padding: var(--n-padding);
background-color: var(--n-color);
border-radius: var(--n-border-radius);
box-shadow: var(--n-box-shadow);
@ -174,5 +173,19 @@ export default cB('dropdown-menu', `
cB('dropdown-menu-wrapper', `
transform-origin: inherit;
width: fit-content;
`)
`),
c('>', [
cB('scrollbar', `
height: inherit;
max-height: inherit;
`)
]),
cNotM('scrollable', `
padding: var(--n-padding);
`),
cM('scrollable', [
cE('content', `
padding: var(--n-padding);
`)
])
])