Browse Source

add dropdown

pull/9/head
tangjinzhou 7 years ago
parent
commit
804a1eba18
  1. 15
      components/_util/vnode.js
  2. 24
      components/button/button-group.vue
  3. 30
      components/dropdown/demo/basic.md
  4. 22
      components/dropdown/demo/context-menu.md
  5. 59
      components/dropdown/demo/dropdown-button.md
  6. 34
      components/dropdown/demo/event.md
  7. 46
      components/dropdown/demo/index.vue
  8. 29
      components/dropdown/demo/item.md
  9. 41
      components/dropdown/demo/overlay-visible.md
  10. 49
      components/dropdown/demo/placement.md
  11. 31
      components/dropdown/demo/sub-menu.md
  12. 29
      components/dropdown/demo/trigger.md
  13. 81
      components/dropdown/dropdown-button.vue
  14. 76
      components/dropdown/dropdown.vue
  15. 13
      components/dropdown/getDropdownProps.js
  16. 43
      components/dropdown/index.en-US.md
  17. 8
      components/dropdown/index.js
  18. 42
      components/dropdown/index.zh-CN.md
  19. 157
      components/dropdown/src/Dropdown.vue
  20. 2
      components/dropdown/src/index.js
  21. 47
      components/dropdown/src/placements.js
  22. 5
      components/dropdown/style/index.js
  23. 250
      components/dropdown/style/index.less
  24. 11
      components/index.js
  25. 1
      components/style.js
  26. 56
      components/trigger/index.vue
  27. 1
      examples/demo.js
  28. 2
      examples/routes.js

15
components/_util/vnode.js

@ -45,7 +45,14 @@ export function cloneVNodes (vnodes, deep) {
}
export function cloneElement (n, nodeProps, clone) {
const node = clone ? cloneVNode(n, true) : n
let ele = n
if (Array.isArray(n)) {
ele = filterEmpty(n)[0]
}
if (!ele) {
return null
}
const node = clone ? cloneVNode(ele, true) : ele
const { props = {}, key, on = {}} = nodeProps
const data = node.data || {}
const { style = data.style,
@ -95,6 +102,10 @@ export function filterEmpty (children = []) {
return children.filter(c => c.tag || c.text.trim() !== '')
}
export function getPropsData (ele) {
return ele.componentOptions && ele.componentOptions.propsData
}
export function getEvents (child) {
let events = {}
if (child.componentOptions && child.componentOptions.listeners) {
@ -102,5 +113,5 @@ export function getEvents (child) {
} else if (child.data && child.data.on) {
events = child.data.on
}
return events
return { ...events }
}

24
components/button/button-group.vue

@ -1,18 +1,20 @@
<script>
import { filterEmpty } from '../_util/vnode'
export default {
name: 'ButtonGroup',
props: {
prefixCls: {
default: 'ant-btn-group',
type: String,
},
size: {
validator (value) {
return ['small', 'large', 'default'].includes(value)
},
const ButtonGroupProps = {
prefixCls: {
default: 'ant-btn-group',
type: String,
},
size: {
validator (value) {
return ['small', 'large', 'default'].includes(value)
},
},
}
export { ButtonGroupProps }
export default {
name: 'ButtonGroup',
props: ButtonGroupProps,
data () {
return {
sizeMap: {

30
components/dropdown/demo/basic.md

@ -0,0 +1,30 @@
<cn>
#### 基本
最简单的下拉菜单。
</cn>
<us>
#### Basic
The most basic dropdown menu.
</us>
```html
<template>
<a-dropdown>
<a class="ant-dropdown-link" href="#">
Hover me <a-icon type="down" />
</a>
<a-menu slot="overlay">
<a-menu-item>
<a href="javascript:;">1st menu item</a>
</a-menu-item>
<a-menu-item>
<a href="javascript:;">2nd menu item</a>
</a-menu-item>
<a-menu-item>
<a href="javascript:;">3rd menu item</a>
</a-menu-item>
</a-menu>
</a-dropdown>
</template>
```

22
components/dropdown/demo/context-menu.md

@ -0,0 +1,22 @@
<cn>
#### 右键菜单
默认是移入触发菜单,可以鼠标右键触发。
</cn>
<us>
#### Context Menu
The default trigger mode is `hover`, you can change it to `right click`.
</us>
```html
<template>
<a-dropdown :trigger="['contextmenu']">
<span style="user-select: none">Right Click on Me</span>
<a-menu slot="overlay">
<a-menu-item key="1">1st menu item</a-menu-item>
<a-menu-item key="2">2nd menu item</a-menu-item>
<a-menu-item key="3">3rd menu item</a-menu-item>
</a-menu>
</a-dropdown>
</template>
```

59
components/dropdown/demo/dropdown-button.md

@ -0,0 +1,59 @@
<cn>
#### 带下拉框的按钮
左边是按钮,右边是额外的相关功能菜单。
</cn>
<us>
#### Button with dropdown menu
A button is on the left, and a related functional menu is on the right.
</us>
```html
<template>
<div>
<a-dropdown-button @click="handleMenuClick">
Dropdown
<a-menu slot="overlay" @click="handleMenuClick">
<a-menu-item key="1">1st menu item</a-menu-item>
<a-menu-item key="2">2nd menu item</a-menu-item>
<a-menu-item key="3">3rd item</a-menu-item>
</a-menu>
</a-dropdown-button>
<a-dropdown-button
@click="handleButtonClick"
disabled
style="margin-left: 8px"
>
Dropdown
<a-menu slot="overlay" @click="handleMenuClick">
<a-menu-item key="1">1st menu item</a-menu-item>
<a-menu-item key="2">2nd menu item</a-menu-item>
<a-menu-item key="3">3rd item</a-menu-item>
</a-menu>
</a-dropdown-button>
<a-dropdown>
<a-menu slot="overlay" @click="handleMenuClick">
<a-menu-item key="1">1st menu item</a-menu-item>
<a-menu-item key="2">2nd menu item</a-menu-item>
<a-menu-item key="3">3rd item</a-menu-item>
</a-menu>
<a-button style="margin-left: 8px">
Button <a-icon type="down" />
</a-button>
</a-dropdown>
</div>
</template>
<script>
export default {
methods: {
handleButtonClick(e) {
console.log('click left button', e);
},
handleMenuClick(e) {
console.log('click', e);
},
}
}
</script>
```

34
components/dropdown/demo/event.md

@ -0,0 +1,34 @@
<cn>
#### 触发事件
点击菜单项后会触发事件,用户可以通过相应的菜单项 key 进行不同的操作。
</cn>
<us>
#### Click event
An event will be triggered when you click menu items, in which you can make different operations according to item's key.
</us>
```html
<template>
<a-dropdown>
<a class="ant-dropdown-link" href="#">
Hover me, Click menu item <a-icon type="down" />
</a>
<a-menu slot="overlay" @click="onClick">
<a-menu-item key="1">1st menu item</a-menu-item>
<a-menu-item key="2">2nd menu item</a-menu-item>
<a-menu-item key="3">3rd menu item</a-menu-item>
</a-menu>
</a-dropdown>
</template>
<script>
export default {
methods: {
onClick({ key }) {
console.log(`Click on item ${key}`);
}
}
}
</script>
```

46
components/dropdown/demo/index.vue

@ -0,0 +1,46 @@
<script>
import Basic from './basic'
import ContextMenu from './context-menu'
import DropdownButton from './dropdown-button'
import Event from './event'
import Item from './item'
import OverlayVisible from './overlay-visible'
import Placement from './placement'
import SubMenu from './sub-menu'
import Trigger from './trigger'
import CN from '../index.zh-CN.md'
import US from '../index.en-US.md'
const md = {
cn: `# 下拉菜单
向下弹出的列表
## 何时使用
当页面上的操作命令过多时用此组件可以收纳操作元素点击或移入触点会出现一个下拉菜单可在列表中进行选择并执行相应的命令
## 代码演示`,
us: `# Dropdown
A dropdown list.
## When To Use
If there are too many operations to display, you can wrap them in a \`Dropdown\`. By clicking/hovering on the trigger, a dropdown menu should appear, which allows you to choose one option and execute relevant actions.`,
}
export default {
render () {
return (
<div>
<md cn={md.cn} us={md.us}/>
<Basic />
<ContextMenu />
<DropdownButton />
<Event />
<Item/>
<OverlayVisible />
<Placement />
<SubMenu />
<Trigger />
<api>
<CN slot='cn' />
<US/>
</api>
</div>
)
},
}
</script>

29
components/dropdown/demo/item.md

@ -0,0 +1,29 @@
<cn>
#### 其他元素
分割线和不可用菜单项。
</cn>
<us>
#### Other elements
Divider and disabled menu item.
</us>
```html
<template>
<a-dropdown>
<a class="ant-dropdown-link" href="#">
Hover me <a-icon type="down" />
</a>
<a-menu slot="overlay">
<a-menu-item key="0">
<a target="_blank" rel="noopener noreferrer" href="http://www.alipay.com/">1st menu item</a>
</a-menu-item>
<a-menu-item key="1">
<a target="_blank" rel="noopener noreferrer" href="http://www.taobao.com/">2nd menu item</a>
</a-menu-item>
<a-menu-divider />
<a-menu-item key="3" disabled>3rd menu item(disabled)</a-menu-item>
</a-menu>
</a-dropdown>
</template>
```

41
components/dropdown/demo/overlay-visible.md

@ -0,0 +1,41 @@
<cn>
#### 菜单隐藏方式
默认是点击关闭菜单,可以关闭此功能。
</cn>
<us>
#### The way of hiding menu.
The default is to close the menu when you click on menu items, this feature can be turned off.
</us>
```html
<template>
<a-dropdown v-model="visible">
<a class="ant-dropdown-link" href="#">
Hover me <a-icon type="down" />
</a>
<a-menu slot="overlay" @click="handleMenuClick">
<a-menu-item key="1">Clicking me will not close the menu.</a-menu-item>
<a-menu-item key="2">Clicking me will not close the menu also.</a-menu-item>
<a-menu-item key="3">Clicking me will close the menu</a-menu-item>
</a-menu>
</a-dropdown>
</template>
<script>
export default {
data(){
return {
visible: false,
}
},
methods: {
handleMenuClick (e) {
if (e.key === '3') {
this.visible = false
}
},
}
}
</script>
```

49
components/dropdown/demo/placement.md

@ -0,0 +1,49 @@
<cn>
#### 弹出位置
支持 6 个弹出位置。
</cn>
<us>
#### Placement
Support 6 placements.
</us>
```html
<template>
<div id="components-dropdown-demo-placement">
<template v-for="(placement, index) in placements">
<a-dropdown :placement="placement">
<a-button>{{placement}}</a-button>
<a-menu slot="overlay">
<a-menu-item>
<a target="_blank" rel="noopener noreferrer" href="http://www.alipay.com/">1st menu item</a>
</a-menu-item>
<a-menu-item>
<a target="_blank" rel="noopener noreferrer" href="http://www.taobao.com/">2nd menu item</a>
</a-menu-item>
<a-menu-item>
<a target="_blank" rel="noopener noreferrer" href="http://www.tmall.com/">3rd menu item</a>
</a-menu-item>
</a-menu>
</a-dropdown>
<br v-if="index === 2"/>
</template>
</div>
</template>
<script>
export default {
data(){
return {
placements: ['bottomLeft', 'bottomCenter', 'bottomRight', 'topLeft', 'topCenter', 'topRight'],
}
},
}
</script>
<style>
#components-dropdown-demo-placement .ant-btn {
margin-right: 8px;
margin-bottom: 8px;
}
</style>
```

31
components/dropdown/demo/sub-menu.md

@ -0,0 +1,31 @@
<cn>
#### 多级菜单
传入的菜单里有多个层级。
</cn>
<us>
#### Cascading menu
The menu has multiple levels.
</us>
```html
<template>
<a-dropdown>
<a class="ant-dropdown-link" href="#">
Cascading menu <a-icon type="down" />
</a>
<a-menu slot="overlay">
<a-menu-item>1st menu item</a-menu-item>
<a-menu-item>2nd menu item</a-menu-item>
<a-sub-menu title="sub menu" key="test">
<a-menu-item>3rd menu item</a-menu-item>
<a-menu-item>4th menu item</a-menu-item>
</a-sub-menu>
<a-sub-menu title="disabled sub menu" disabled>
<a-menu-item>5d menu item</a-menu-item>
<a-menu-item>6th menu item</a-menu-item>
</a-sub-menu>
</a-menu>
</a-dropdown>
</template>
```

29
components/dropdown/demo/trigger.md

@ -0,0 +1,29 @@
<cn>
#### 触发方式
默认是移入触发菜单,可以点击触发。
</cn>
<us>
#### Trigger mode
The default trigger mode is `hover`, you can change it to `click`.
</us>
```html
<template>
<a-dropdown :trigger="['click']">
<a class="ant-dropdown-link" href="#">
Click me <a-icon type="down" />
</a>
<a-menu slot="overlay">
<a-menu-item key="0">
<a href="http://www.alipay.com/">1st menu item</a>
</a-menu-item>
<a-menu-item key="1">
<a href="http://www.taobao.com/">2nd menu item</a>
</a-menu-item>
<a-menu-divider />
<a-menu-item key="3">3rd menu item</a-menu-item>
</a-menu>
</a-dropdown>
</template>
```

81
components/dropdown/dropdown-button.vue

@ -0,0 +1,81 @@
<script>
import Button from '../button'
import { ButtonGroupProps } from '../button/button-group'
import Icon from '../icon'
import Dropdown from './dropdown'
import PropTypes from '../_util/vue-types'
import { hasProp, getComponentFromProp } from '../_util/props-util'
import getDropdownProps from './getDropdownProps'
const DropdownProps = getDropdownProps()
const ButtonGroup = Button.Group
export default {
props: {
...ButtonGroupProps,
...DropdownProps,
type: PropTypes.oneOf(['primary', 'ghost', 'dashed', 'default']).def('default'),
disabled: PropTypes.bool,
prefixCls: PropTypes.string.def('ant-dropdown-button'),
placement: DropdownProps.placement.def('bottomRight'),
},
methods: {
onClick (e) {
this.$emit('click', e)
},
onVisibleChange (val) {
this.$emit('visibleChange', val)
},
},
model: {
prop: 'visible',
event: 'visibleChange',
},
render () {
const {
type, disabled,
prefixCls, trigger, align,
visible, placement, getPopupContainer,
...restProps
} = this.$props
const dropdownProps = {
props: {
align,
disabled,
trigger: disabled ? [] : trigger,
placement,
getPopupContainer,
},
on: {
visibleChange: this.onVisibleChange,
},
}
if (hasProp(this, 'visible')) {
dropdownProps.visible = visible
}
return (
<ButtonGroup
{...restProps}
class={prefixCls}
>
<Button
type={type}
disabled={disabled}
onClick={this.onClick}
>
{this.$slots.default}
</Button>
<Dropdown {...dropdownProps}>
<template slot='overlay'>
{getComponentFromProp(this, 'overlay')}
</template>
<Button type={type}>
<Icon type='down' />
</Button>
</Dropdown>
</ButtonGroup>
)
},
}
</script>

76
components/dropdown/dropdown.vue

@ -0,0 +1,76 @@
<script>
import RcDropdown from './src/dropdown'
import DropdownButton from './dropdown-button'
// import warning from '../_util/warning'
import PropTypes from '../_util/vue-types'
import { cloneElement, getPropsData } from '../_util/vnode'
import { getOptionProps } from '../_util/props-util'
import getDropdownProps from './getDropdownProps'
const DropdownProps = getDropdownProps()
const Dropdown = {
props: {
...DropdownProps,
prefixCls: PropTypes.string.def('ant-dropdown'),
mouseEnterDelay: PropTypes.number.def(0.15),
mouseLeaveDelay: PropTypes.number.def(0.1),
placement: DropdownProps.placement.def('bottomLeft'),
},
model: {
prop: 'visible',
event: 'visibleChange',
},
methods: {
getTransitionName () {
const { placement = '', transitionName } = this.$props
if (transitionName !== undefined) {
return transitionName
}
if (placement.indexOf('top') >= 0) {
return 'slide-down'
}
return 'slide-up'
},
},
render () {
const { $slots, prefixCls, trigger, disabled, $listeners } = this
const dropdownTrigger = cloneElement($slots.default, {
class: `${prefixCls}-trigger`,
disabled,
})
const overlay = $slots.overlay && $slots.overlay[0]
// menu cannot be selectable in dropdown defaultly
const overlayProps = overlay && getPropsData(overlay)
const selectable = (overlayProps && 'selectable' in overlayProps)
? overlayProps.selectable : false
const fixedModeOverlay = cloneElement(overlay, {
props: {
mode: 'vertical',
selectable,
},
})
const dropdownProps = {
props: {
...getOptionProps(this),
transitionName: this.getTransitionName(),
trigger: disabled ? [] : trigger,
},
on: $listeners,
}
return (
<RcDropdown
{...dropdownProps}
>
{dropdownTrigger}
<template slot='overlay'>
{fixedModeOverlay}
</template>
</RcDropdown>
)
},
}
Dropdown.Button = DropdownButton
export default Dropdown
</script>

13
components/dropdown/getDropdownProps.js

@ -0,0 +1,13 @@
import PropTypes from '../_util/vue-types'
export default () => ({
trigger: PropTypes.array.def(['hover']),
overlay: PropTypes.any,
visible: PropTypes.bool,
disabled: PropTypes.bool,
align: PropTypes.object,
getPopupContainer: PropTypes.func,
prefixCls: PropTypes.string,
transitionName: PropTypes.string,
placement: PropTypes.oneOf(['topLeft', 'topCenter', 'topRight', 'bottomLeft', 'bottomCenter', 'bottomRight']),
forceRender: PropTypes.bool,
})

43
components/dropdown/index.en-US.md

@ -0,0 +1,43 @@
## API
### Dropdown
| Property | Description | Type | Default |
| -------- | ----------- | ---- | ------- |
| disabled | whether the dropdown menu is disabled | boolean | - |
| getPopupContainer | to set the container of the dropdown menu. The default is to create a `div` element in `body`, you can reset it to the scrolling area and make a relative reposition. [example](https://codepen.io/afc163/pen/zEjNOy?editors=0010) | Function(triggerNode) | `() => document.body` |
| overlay(slot) | the dropdown menu | [Menu](#/components/us/menu) | - |
| placement | placement of pop menu: `bottomLeft` `bottomCenter` `bottomRight` `topLeft` `topCenter` `topRight` | String | `bottomLeft` |
| trigger | the trigger mode which executes the drop-down action | Array&lt;`click`\|`hover`\|`contextMenu`> | `['hover']` |
| visible(v-model) | whether the dropdown menu is visible | boolean | - |
### events
| Events Name | Description | Arguments |
| --- | --- | --- |
| visibleChange | a callback function takes an argument: `visible`, is executed when the visible state is changed | function(visible) |
You should use [Menu](#/components/us/menu/) as `overlay`. The menu items and dividers are also available by using `Menu.Item` and `Menu.Divider`.
> Warning: You must set a unique `key` for `Menu.Item`.
>
> Menu of Dropdown is unselectable by default, you can make it selectable via `<Menu selectable>`.
### Dropdown.Button
| Property | Description | Type | Default |
| -------- | ----------- | ---- | ------- |
| disabled | whether the dropdown menu is disabled | boolean | - |
| overlay(slot) | the dropdown menu | [Menu](#/components/us/menu) | - |
| placement | placement of pop menu: `bottomLeft` `bottomCenter` `bottomRight` `topLeft` `topCenter` `topRight` | String | `bottomLeft` |
| size | size of the button, the same as [Button](#/components/us/button) | string | `default` |
| trigger | the trigger mode which executes the drop-down action | Array&lt;`click`\|`hover`\|`contextMenu`> | `['hover']` |
| type | type of the button, the same as [Button](#/components/us/button) | string | `default` |
| visible | whether the dropdown menu is visible | boolean | - |
### Dropdown.Button events
| Events Name | Description | Arguments |
| --- | --- | --- |
| click | a callback function, the same as [Button](#/components/us/button), which will be executed when you click the button on the left | Function |
| visibleChange | a callback function takes an argument: `visible`, is executed when the visible state is changed | Function |

8
components/dropdown/index.js

@ -0,0 +1,8 @@
import Dropdown from './dropdown'
import DropdownButton from './dropdown-button'
export { DropDownProps } from './dropdown'
export { DropdownButtonProps } from './dropdown-button'
Dropdown.Button = DropdownButton
export default Dropdown

42
components/dropdown/index.zh-CN.md

@ -0,0 +1,42 @@
## API
属性如下
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| disabled | 菜单是否禁用 | boolean | - |
| getPopupContainer | 菜单渲染父节点。默认渲染到 body 上,如果你遇到菜单滚动定位问题,试试修改为滚动的区域,并相对其定位。 | Function(triggerNode) | `() => document.body` |
| overlay(slot) | 菜单 | [Menu](#/components/cn/menu) | - |
| placement | 菜单弹出位置:`bottomLeft` `bottomCenter` `bottomRight` `topLeft` `topCenter` `topRight` | String | `bottomLeft` |
| trigger | 触发下拉的行为 | Array&lt;`click`\|`hover`\|`contextMenu`> | `['hover']` |
| visible(v-model) | 菜单是否显示 | boolean | - |
`overlay` 菜单使用 [Menu](#/components/cn/menu/),还包括菜单项 `Menu.Item`,分割线 `Menu.Divider`
> 注意: Menu.Item 必须设置唯一的 key 属性。
>
> Dropdown 下的 Menu 默认不可选中。如果需要菜单可选中,可以指定 `<Menu selectable>`.
### 事件
| 事件名称 | 说明 | 回调参数 |
| --- | --- | --- |
| visibleChange | 菜单显示状态改变时调用,参数为 visible | function(visible) |
### Dropdown.Button
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| disabled | 菜单是否禁用 | boolean | - |
| overlay(slot) | 菜单 | [Menu](#/components/cn/menu/) | - |
| placement | 菜单弹出位置:`bottomLeft` `bottomCenter` `bottomRight` `topLeft` `topCenter` `topRight` | String | `bottomLeft` |
| size | 按钮大小,和 [Button](#/components/cn/button/) 一致 | string | 'default' |
| trigger | 触发下拉的行为 | Array&lt;`click`\|`hover`\|`contextMenu`> | `['hover']` |
| type | 按钮类型,和 [Button](#/components/cn/button/) 一致 | string | 'default' |
| visible(v-model) | 菜单是否显示 | boolean | - |
### Dropdown.Button事件
| 事件名称 | 说明 | 回调参数 |
| --- | --- | --- |
| click | 点击左侧按钮的回调,和 [Button](#/components/cn/button/) 一致 | Function |
| visibleChange | 菜单显示状态改变时调用,参数为 visible | function(visible) |

157
components/dropdown/src/Dropdown.vue

@ -0,0 +1,157 @@
<script>
import PropTypes from '../../_util/vue-types'
import Trigger from '../../trigger'
import placements from './placements'
import { hasProp } from '../../_util/props-util'
import BaseMixin from '../../_util/BaseMixin'
import { cloneElement, getEvents } from '../../_util/vnode'
export default {
mixins: [BaseMixin],
props: {
minOverlayWidthMatchTrigger: PropTypes.bool.def(true),
prefixCls: PropTypes.string.def('rc-dropdown'),
transitionName: PropTypes.string,
overlayClassName: PropTypes.string.def(''),
animation: PropTypes.any,
align: PropTypes.object,
overlayStyle: PropTypes.object.def({}),
placement: PropTypes.string.def('bottomLeft'),
trigger: PropTypes.array.def(['hover']),
showAction: PropTypes.array.def([]),
hideAction: PropTypes.array.def([]),
getPopupContainer: PropTypes.func,
visible: PropTypes.bool,
defaultVisible: PropTypes.bool.def(false),
mouseEnterDelay: PropTypes.number.def(0.15),
mouseLeaveDelay: PropTypes.number.def(0.1),
},
data () {
let sVisible = this.defaultVisible
if (hasProp(this, 'visible')) {
sVisible = this.visible
}
return {
sVisible,
}
},
watch: {
visible (val) {
if (val !== undefined) {
this.setState({
sVisible: val,
})
}
},
},
methods: {
onClick (e) {
// do no call onVisibleChange, if you need click to hide, use onClick and control visible
if (!hasProp(this, 'visible')) {
this.setState({
sVisible: false,
})
}
this.$emit('overlayClick', e)
if (this.childOriginEvents.click) {
this.childOriginEvents.click(e)
}
},
onVisibleChange (visible) {
if (!hasProp(this, 'visible')) {
this.setState({
sVisible: visible,
})
}
this.__emit('visibleChange', visible)
},
getMenuElement () {
const child = this.$slots.overlay[0]
const events = getEvents(child)
if (!events._ANT_DROPDOWN_EVENT_HACK) {
this.childOriginEvents = events
}
const { prefixCls } = this.$props
const extraOverlayProps = {
prefixCls: `${prefixCls}-menu`,
}
const overlay = this.$slots.overlay[0]
return cloneElement(overlay, {
props: extraOverlayProps,
on: {
click: this.onClick,
_ANT_DROPDOWN_EVENT_HACK: () => {},
},
})
},
getPopupDomNode () {
return this.$refs.trigger.getPopupDomNode()
},
afterVisibleChange (visible) {
if (visible && this.$props.minOverlayWidthMatchTrigger) {
const overlayNode = this.getPopupDomNode()
const rootNode = this.$el
if (rootNode && overlayNode && rootNode.offsetWidth > overlayNode.offsetWidth) {
overlayNode.style.width = `${rootNode.offsetWidth}px`
if (this.$refs.trigger &&
this.$refs.trigger._component &&
this.$refs.trigger._component.alignInstance) {
this.$refs.trigger._component.alignInstance.forceAlign()
}
}
}
},
},
render () {
const {
prefixCls,
transitionName, animation,
align, placement, getPopupContainer,
showAction, hideAction,
overlayClassName, overlayStyle,
trigger, ...otherProps
} = this.$props
const triggerProps = {
props: {
...otherProps,
prefixCls,
popupClassName: overlayClassName,
popupStyle: overlayStyle,
builtinPlacements: placements,
action: trigger,
showAction,
hideAction,
popupPlacement: placement,
popupAlign: align,
popupTransitionName: transitionName,
popupAnimation: animation,
popupVisible: this.sVisible,
afterPopupVisibleChange: this.afterVisibleChange,
getPopupContainer: getPopupContainer,
},
on: {
popupVisibleChange: this.onVisibleChange,
},
ref: 'trigger',
}
const child = this.$slots.default && this.$slots.default[0]
return (
<Trigger {...triggerProps}>
{child && !child.tag
? <span>{child}</span>
: child}
<template slot='popup'>
{this.$slots.overlay && this.getMenuElement()}
</template>
</Trigger>
)
},
}
</script>

2
components/dropdown/src/index.js

@ -0,0 +1,2 @@
import Dropdown from './Dropdown'
export default Dropdown

47
components/dropdown/src/placements.js

@ -0,0 +1,47 @@
const autoAdjustOverflow = {
adjustX: 1,
adjustY: 1,
}
const targetOffset = [0, 0]
export const placements = {
topLeft: {
points: ['bl', 'tl'],
overflow: autoAdjustOverflow,
offset: [0, -4],
targetOffset,
},
topCenter: {
points: ['bc', 'tc'],
overflow: autoAdjustOverflow,
offset: [0, -4],
targetOffset,
},
topRight: {
points: ['br', 'tr'],
overflow: autoAdjustOverflow,
offset: [0, -4],
targetOffset,
},
bottomLeft: {
points: ['tl', 'bl'],
overflow: autoAdjustOverflow,
offset: [0, 4],
targetOffset,
},
bottomCenter: {
points: ['tc', 'bc'],
overflow: autoAdjustOverflow,
offset: [0, 4],
targetOffset,
},
bottomRight: {
points: ['tr', 'br'],
overflow: autoAdjustOverflow,
offset: [0, 4],
targetOffset,
},
}
export default placements

5
components/dropdown/style/index.js

@ -0,0 +1,5 @@
import '../../style/index.less'
import './index.less'
// style dependencies
import '../../button/style'

250
components/dropdown/style/index.less

@ -0,0 +1,250 @@
@import "../../style/themes/default";
@import "../../style/mixins/index";
@dropdown-prefix-cls: ~"@{ant-prefix}-dropdown";
.@{dropdown-prefix-cls} {
.reset-component;
position: absolute;
left: -9999px;
top: -9999px;
z-index: @zindex-dropdown;
display: block;
&-wrap {
position: relative;
.@{ant-prefix}-btn > .@{iconfont-css-prefix}-down {
.iconfont-size-under-12px(10px);
}
.@{iconfont-css-prefix}-down:before {
transition: transform .2s;
}
}
&-wrap-open {
.@{iconfont-css-prefix}-down:before {
transform: rotate(180deg);
}
}
&-hidden,
&-menu-hidden {
display: none;
}
&-menu {
outline: none;
position: relative;
list-style-type: none;
padding: 0;
margin: 0;
text-align: left;
background-color: @component-background;
border-radius: @border-radius-base;
box-shadow: @box-shadow-base;
background-clip: padding-box;
&-item-group-title {
color: @text-color-secondary;
padding: 5px @control-padding-horizontal;
transition: all .3s;
}
&-submenu-popup {
position: absolute;
}
&-item,
&-submenu-title {
padding: 5px @control-padding-horizontal;
margin: 0;
clear: both;
font-size: @font-size-base;
font-weight: normal;
color: @text-color;
white-space: nowrap;
cursor: pointer;
transition: all .3s;
line-height: 22px;
> a {
color: @text-color;
display: block;
padding: 5px @control-padding-horizontal;
margin: -5px -@control-padding-horizontal;
transition: all .3s;
&:focus {
text-decoration: none;
}
}
&-selected,
&-selected > a {
color: @primary-color;
background-color: @item-active-bg;
}
&:hover {
background-color: @item-hover-bg;
}
&-disabled {
color: @disabled-color;
cursor: not-allowed;
&:hover {
color: @disabled-color;
background-color: @component-background;
cursor: not-allowed;
}
}
&:first-child,
&:first-child > a {
border-radius: @border-radius-base @border-radius-base 0 0;
}
&:last-child,
&:last-child > a {
border-radius: 0 0 @border-radius-base @border-radius-base;
}
&:only-child,
&:only-child > a {
border-radius: @border-radius-base;
}
&-divider {
height: 1px;
overflow: hidden;
background-color: @border-color-split;
line-height: 0;
}
.@{dropdown-prefix-cls}-menu-submenu-arrow {
position: absolute;
right: @padding-xs;
&:after {
font-family: "anticon" !important;
font-style: normal;
content: "\e61f";
color: @text-color-secondary;
.iconfont-size-under-12px(10px);
}
}
}
&-submenu-title {
padding-right: 26px;
&:first-child,
&:last-child {
border-radius: 0;
}
}
&-submenu-vertical {
position: relative;
}
&-submenu-vertical > & {
top: 0;
left: 100%;
position: absolute;
min-width: 100%;
margin-left: 4px;
transform-origin: 0 0;
}
&-submenu&-submenu-disabled .@{dropdown-prefix-cls}-menu-submenu-title {
&,
.@{dropdown-prefix-cls}-menu-submenu-arrow:after {
color: @disabled-color;
}
}
&-submenu:first-child &-submenu-title {
border-radius: @border-radius-base @border-radius-base 0 0;
}
&-submenu:last-child &-submenu-title {
border-radius: 0 0 @border-radius-base @border-radius-base;
}
}
&.slide-down-enter.slide-down-enter-active&-placement-bottomLeft,
&.slide-down-appear.slide-down-appear-active&-placement-bottomLeft,
&.slide-down-enter.slide-down-enter-active&-placement-bottomCenter,
&.slide-down-appear.slide-down-appear-active&-placement-bottomCenter,
&.slide-down-enter.slide-down-enter-active&-placement-bottomRight,
&.slide-down-appear.slide-down-appear-active&-placement-bottomRight {
animation-name: antSlideUpIn;
}
&.slide-up-enter.slide-up-enter-active&-placement-topLeft,
&.slide-up-appear.slide-up-appear-active&-placement-topLeft,
&.slide-up-enter.slide-up-enter-active&-placement-topCenter,
&.slide-up-appear.slide-up-appear-active&-placement-topCenter,
&.slide-up-enter.slide-up-enter-active&-placement-topRight,
&.slide-up-appear.slide-up-appear-active&-placement-topRight {
animation-name: antSlideDownIn;
}
&.slide-down-leave.slide-down-leave-active&-placement-bottomLeft,
&.slide-down-leave.slide-down-leave-active&-placement-bottomCenter,
&.slide-down-leave.slide-down-leave-active&-placement-bottomRight {
animation-name: antSlideUpOut;
}
&.slide-up-leave.slide-up-leave-active&-placement-topLeft,
&.slide-up-leave.slide-up-leave-active&-placement-topCenter,
&.slide-up-leave.slide-up-leave-active&-placement-topRight {
animation-name: antSlideDownOut;
}
}
.@{dropdown-prefix-cls}-trigger,
.@{dropdown-prefix-cls}-link {
.@{iconfont-css-prefix}-down {
.iconfont-size-under-12px(10px);
}
}
.@{dropdown-prefix-cls}-button {
white-space: nowrap;
&.@{ant-prefix}-btn-group > .@{ant-prefix}-btn:last-child:not(:first-child) {
padding-left: @padding-xs;
padding-right: @padding-xs;
}
.@{iconfont-css-prefix}-down {
.iconfont-size-under-12px(10px);
}
}
// https://github.com/ant-design/ant-design/issues/4903
.@{dropdown-prefix-cls}-menu-dark {
&,
.@{dropdown-prefix-cls}-menu {
background: @menu-dark-bg;
}
.@{dropdown-prefix-cls}-menu-item,
.@{dropdown-prefix-cls}-menu-submenu-title,
.@{dropdown-prefix-cls}-menu-item > a {
color: @text-color-secondary-dark;
.@{dropdown-prefix-cls}-menu-submenu-arrow:after {
color: @text-color-secondary-dark;
}
&:hover {
color: #fff;
background: transparent;
}
}
.@{dropdown-prefix-cls}-menu-item-selected {
&,
&:hover,
> a {
background: @primary-color;
color: #fff;
}
}
}

11
components/index.js

@ -41,6 +41,15 @@ export { default as Popover } from './popover'
export { default as Popconfirm } from './popconfirm'
export { default as Menu } from './menu'
import Menu from './menu'
const MenuItem = Menu.Item
const SubMenu = Menu.SubMenu
const MenuDivider = Menu.Divider
const MenuItemGroup = Menu.ItemGroup
export { Menu, MenuItem, SubMenu, MenuDivider, MenuItemGroup }
export { default as Card } from './card'
import Dropdown from './dropdown'
const DropdownButton = Dropdown.Button
export { Dropdown, DropdownButton }

1
components/style.js

@ -14,3 +14,4 @@ import './tooltip/style'
import './popover/style'
import './popconfirm/style'
import './menu/style'
import './dropdown/style'

56
components/trigger/index.vue

@ -18,7 +18,7 @@ function returnDocument () {
return window.document
}
const ALL_HANDLERS = ['click', 'mousedown', 'touchStart', 'mouseenter',
'mouseleave', 'focus', 'blur', 'contextMenu']
'mouseleave', 'focus', 'blur', 'contextmenu']
export default {
name: 'Trigger',
@ -124,7 +124,7 @@ export default {
// https://github.com/react-component/trigger/issues/50
if (state.sPopupVisible) {
let currentDocument
if (!this.clickOutsideHandler && (this.isClickToHide() || this.isContextMenuToShow())) {
if (!this.clickOutsideHandler && (this.isClickToHide() || this.isContextmenuToShow())) {
currentDocument = props.getDocument()
this.clickOutsideHandler = addEventListener(currentDocument,
'mousedown', this.onDocumentClick)
@ -135,16 +135,16 @@ export default {
this.touchOutsideHandler = addEventListener(currentDocument,
'touchstart', this.onDocumentClick)
}
// close popup when trigger type contains 'onContextMenu' and document is scrolling.
if (!this.contextMenuOutsideHandler1 && this.isContextMenuToShow()) {
// close popup when trigger type contains 'onContextmenu' and document is scrolling.
if (!this.contextmenuOutsideHandler1 && this.isContextmenuToShow()) {
currentDocument = currentDocument || props.getDocument()
this.contextMenuOutsideHandler1 = addEventListener(currentDocument,
'scroll', this.onContextMenuClose)
this.contextmenuOutsideHandler1 = addEventListener(currentDocument,
'scroll', this.onContextmenuClose)
}
// close popup when trigger type contains 'onContextMenu' and window is blur.
if (!this.contextMenuOutsideHandler2 && this.isContextMenuToShow()) {
this.contextMenuOutsideHandler2 = addEventListener(window,
'blur', this.onContextMenuClose)
// close popup when trigger type contains 'onContextmenu' and window is blur.
if (!this.contextmenuOutsideHandler2 && this.isContextmenuToShow()) {
this.contextmenuOutsideHandler2 = addEventListener(window,
'blur', this.onContextmenuClose)
}
return
}
@ -203,14 +203,14 @@ export default {
}
},
onContextMenu (e) {
onContextmenu (e) {
e.preventDefault()
this.fireEvents('contextMenu', e)
this.fireEvents('contextmenu', e)
this.setPopupVisible(true)
},
onContextMenuClose () {
if (this.isContextMenuToShow()) {
onContextmenuClose () {
if (this.isContextmenuToShow()) {
this.close()
}
},
@ -401,14 +401,14 @@ export default {
this.clickOutsideHandler = null
}
if (this.contextMenuOutsideHandler1) {
this.contextMenuOutsideHandler1.remove()
this.contextMenuOutsideHandler1 = null
if (this.contextmenuOutsideHandler1) {
this.contextmenuOutsideHandler1.remove()
this.contextmenuOutsideHandler1 = null
}
if (this.contextMenuOutsideHandler2) {
this.contextMenuOutsideHandler2.remove()
this.contextMenuOutsideHandler2 = null
if (this.contextmenuOutsideHandler2) {
this.contextmenuOutsideHandler2.remove()
this.contextmenuOutsideHandler2 = null
}
if (this.touchOutsideHandler) {
@ -433,9 +433,9 @@ export default {
return action.indexOf('click') !== -1 || showAction.indexOf('click') !== -1
},
isContextMenuToShow () {
isContextmenuToShow () {
const { action, showAction } = this.$props
return action.indexOf('contextMenu') !== -1 || showAction.indexOf('contextMenu') !== -1
return action.indexOf('contextmenu') !== -1 || showAction.indexOf('contextmenu') !== -1
},
isClickToHide () {
@ -485,20 +485,20 @@ export default {
}
const child = children[0]
const events = getEvents(child)
// vue使_ANT_EVENT_HACK
if (!events._ANT_EVENT_HACK) {
// vue使_ANT_TRIGGER_EVENT_HACK
if (!events._ANT_TRIGGER_EVENT_HACK) {
this.childOriginEvents = events
}
const newChildProps = {
props: {},
on: { _ANT_EVENT_HACK: () => {} },
on: { _ANT_TRIGGER_EVENT_HACK: () => {} },
key: 'trigger',
}
if (this.isContextMenuToShow()) {
newChildProps.on.contextMenu = this.onContextMenu
if (this.isContextmenuToShow()) {
newChildProps.on.contextmenu = this.onContextmenu
} else {
newChildProps.on.contextMenu = this.createTwoChains('contextMenu')
newChildProps.on.contextmenu = this.createTwoChains('contextmenu')
}
if (this.isClickToHide() || this.isClickToShow()) {

1
examples/demo.js

@ -16,4 +16,5 @@ export { default as rate } from 'antd/rate/demo/index.vue'
export { default as tabs } from 'antd/tabs/demo/index.vue'
export { default as tag } from 'antd/tag/demo/index.vue'
export { default as tooltip } from 'antd/tooltip/demo/index.vue'
export { default as dropdown } from 'antd/dropdown/demo/index.vue'

2
examples/routes.js

@ -1,7 +1,7 @@
import Demo from './components/demo.vue'
const AsyncComp = () => {
return {
component: import(`../components/avatar/demo/index.vue`),
component: import(`../components/dropdown/demo/sub-menu.md`),
}
}
export default [

Loading…
Cancel
Save