diff --git a/docs/dropdown/demo/basic.md b/docs/dropdown/demo/basic.md
index 8dccfa12c1..73b03529d1 100644
--- a/docs/dropdown/demo/basic.md
+++ b/docs/dropdown/demo/basic.md
@@ -27,7 +27,10 @@ const menu = (
);
ReactDOM.render(
- Hello dropdown} afterOpen={() => console.log('after open')}>
- {menu}
- , mountNode);
+
+ Hello dropdown} triggerType={["click", "hover"]} afterOpen={() => console.log('after open')}>
+ {menu}
+
+
+, mountNode);
````
diff --git a/docs/dropdown/index.en-us.md b/docs/dropdown/index.en-us.md
index 5e57b76fa6..6e4ef25fd7 100644
--- a/docs/dropdown/index.en-us.md
+++ b/docs/dropdown/index.en-us.md
@@ -13,6 +13,10 @@
You can storage operation command with dropdown component when there are too much command. There will be a drop-down menu after you click or hover the trigger element. Then choose a command and run it.
+### Note
+
+- Dropdown is accessible when using like `` (triggerType="focus" is deprecated). In our opinion, menu elements need to be confirmed by users before they are expanded when it comes to accessibility.
+
## API
### Dropdown
@@ -25,7 +29,7 @@ You can storage operation command with dropdown component when there are too muc
| defaultVisible | overlay display or not in default situation | Boolean | false |
| onVisibleChange | callback function when toggle visible of overlay
**signatures**:
Function(visible: Boolean, type: String, e: Object) => void
**params**:
_visible_: {Boolean} overlay display or not
_type_: {String} orign of trigger overlay toggle visible
_e_: {Object} DOM Event| Function | func.noop |
| trigger | trigger element | ReactNode | - |
-| triggerType | operation type of trigger overlay toggle visible
**options**:
'hover', 'click', 'focus' | Enum | 'hover' |
+| triggerType | operation type of trigger overlay toggle visible
**options**:
'hover', 'click' | Enum | 'hover' |
| disabled | overlay can not toggle visible if you set disabled attribute | Boolean | false |
| align | overlay position relative to trigger element, see details Overlay align | String | 'tl bl' |
| offset | overlay adjust position relative to trigger element | Array | [0, 0] |
diff --git a/docs/dropdown/index.md b/docs/dropdown/index.md
index 81f46fb304..768d77546f 100644
--- a/docs/dropdown/index.md
+++ b/docs/dropdown/index.md
@@ -13,6 +13,10 @@
当页面上的操作命令过多时,用此组件可以收纳操作元素。点击或移入触点,会出现一个下拉菜单。可在列表中进行选择,并执行相应的命令。
+### 使用注意
+
+- 若要使用无障碍的Dropdown,推荐使用`` (请勿使用triggerType="focus")。我们认为,菜单类元素需要由用户确认后再展开才是一种无障碍友好的实践。
+
## API
### Dropdown
@@ -26,7 +30,7 @@
| defaultVisible | 弹层默认是否显示 | Boolean | false |
| onVisibleChange | 弹层显示或隐藏时触发的回调函数
**签名**:
Function(visible: Boolean, type: String, e: Object) => void
**参数**:
_visible_: {Boolean} 弹层是否显示
_type_: {String} 触发弹层显示或隐藏的来源
_e_: {Object} DOM事件 | Function | func.noop |
| trigger | 触发弹层显示或者隐藏的元素 | ReactNode | - |
-| triggerType | 触发弹层显示或隐藏的操作类型
**可选值**:
'hover', 'click', 'focus' | Enum | 'hover' |
+| triggerType | 触发弹层显示或隐藏的操作类型,可以是 'click','hover',或者它们组成的数组,如 ['hover', 'click'] | String/Array | ['hover'] |
| disabled | 设置此属性,弹层无法显示或隐藏 | Boolean | false |
| align | 弹层相对于触发元素的定位, 详见 Overlay 的定位部分 | String | 'tl bl' |
| offset | 弹层相对于触发元素定位的微调 | Array | [0, 0] |
diff --git a/src/dropdown/dropdown.jsx b/src/dropdown/dropdown.jsx
new file mode 100644
index 0000000000..f00311a23f
--- /dev/null
+++ b/src/dropdown/dropdown.jsx
@@ -0,0 +1,168 @@
+import React, { Component, Children } from 'react';
+import PropTypes from 'prop-types';
+import Overlay from '../overlay';
+import { func } from '../util';
+
+const { noop, makeChain, bindCtx } = func;
+const Popup = Overlay.Popup;
+
+/**
+ * Dropdown
+ * @description 继承 Popup 的 API,除非特别说明
+ */
+export default class Dropdown extends Component {
+ static propTypes = {
+ prefix: PropTypes.string,
+ pure: PropTypes.bool,
+ rtl: PropTypes.bool,
+ className: PropTypes.string,
+ /**
+ * 弹层内容
+ */
+ children: PropTypes.node,
+ /**
+ * 弹层当前是否显示
+ */
+ visible: PropTypes.bool,
+ /**
+ * 弹层默认是否显示
+ */
+ defaultVisible: PropTypes.bool,
+ /**
+ * 弹层显示或隐藏时触发的回调函数
+ * @param {Boolean} visible 弹层是否显示
+ * @param {String} type 触发弹层显示或隐藏的来源
+ * @param {Object} e DOM事件
+ */
+ onVisibleChange: PropTypes.func,
+ /**
+ * 触发弹层显示或者隐藏的元素
+ */
+ trigger: PropTypes.node,
+ /**
+ * 触发弹层显示或隐藏的操作类型,可以是 'click','hover',或者它们组成的数组,如 ['hover', 'click']
+ */
+ triggerType: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
+ /**
+ * 设置此属性,弹层无法显示或隐藏
+ */
+ disabled: PropTypes.bool,
+ /**
+ * 弹层相对于触发元素的定位, 详见 Overlay 的定位部分
+ */
+ align: PropTypes.string,
+ /**
+ * 弹层相对于触发元素定位的微调
+ */
+ offset: PropTypes.array,
+ /**
+ * 弹层显示或隐藏的延时时间(以毫秒为单位),在 triggerType 被设置为 hover 时生效
+ */
+ delay: PropTypes.number,
+ /**
+ * 弹层打开时是否让其中的元素自动获取焦点
+ */
+ autoFocus: PropTypes.bool,
+ /**
+ * 是否显示遮罩
+ */
+ hasMask: PropTypes.bool,
+ /**
+ * 隐藏时是否保留子节点
+ */
+ cache: PropTypes.bool,
+ /**
+ * 配置动画的播放方式,支持 { in: 'enter-class', out: 'leave-class' } 的对象参数,如果设置为 false,则不播放动画
+ * @default { in: 'expandInDown', out: 'expandOutUp' }
+ */
+ animation: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
+ };
+ static defaultProps = {
+ prefix: 'next-',
+ pure: false,
+ defaultVisible: false,
+ onVisibleChange: noop,
+ triggerType: 'hover',
+ disabled: false,
+ align: 'tl bl',
+ offset: [0, 0],
+ delay: 200,
+ hasMask: false,
+ cache: false,
+ onPosition: noop,
+ };
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ visible:
+ 'visible' in props
+ ? props.visible
+ : props.defaultVisible || false,
+ autoFocus: 'autoFocus' in props ? props.autoFocus : false,
+ };
+
+ bindCtx(this, ['onTriggerKeyDown', 'onMenuClick', 'onVisibleChange']);
+ }
+
+ getVisible(props = this.props) {
+ return 'visible' in props ? props.visible : this.state.visible;
+ }
+
+ onMenuClick() {
+ this.onVisibleChange(false, 'fromContent');
+ }
+
+ onVisibleChange(visible, from) {
+ this.setState({ visible });
+
+ this.props.onVisibleChange(visible, from);
+ }
+
+ onTriggerKeyDown() {
+ let autoFocus = true;
+
+ if ('autoFocus' in this.props) {
+ autoFocus = this.props.autoFocus;
+ }
+
+ this.setState({
+ autoFocus,
+ });
+ }
+
+ render() {
+ let child = Children.only(this.props.children);
+ if (typeof child.type === 'function' && child.type.isNextMenu) {
+ child = React.cloneElement(child, {
+ onItemClick: makeChain(
+ this.onMenuClick,
+ child.props.onItemClick
+ ),
+ });
+ }
+
+ const { trigger, rtl } = this.props;
+ const newTrigger = React.cloneElement(trigger, {
+ onKeyDown: makeChain(
+ this.onTriggerKeyDown,
+ trigger.props.onKeyDown
+ ),
+ });
+
+ return (
+
+ {child}
+
+ );
+ }
+}
diff --git a/src/dropdown/index.jsx b/src/dropdown/index.jsx
index 5667f9d7f5..aec84fa0da 100644
--- a/src/dropdown/index.jsx
+++ b/src/dropdown/index.jsx
@@ -1,171 +1,22 @@
-import React, { Component, Children } from 'react';
-import PropTypes from 'prop-types';
-import Overlay from '../overlay';
import ConfigProvider from '../config-provider';
-import { func } from '../util';
-
-const { noop, makeChain, bindCtx } = func;
-const Popup = Overlay.Popup;
-
-/**
- * Dropdown
- * @description 继承 Popup 的 API,除非特别说明
- */
-class Dropdown extends Component {
- static propTypes = {
- prefix: PropTypes.string,
- pure: PropTypes.bool,
- rtl: PropTypes.bool,
- className: PropTypes.string,
- /**
- * 弹层内容
- */
- children: PropTypes.node,
- /**
- * 弹层当前是否显示
- */
- visible: PropTypes.bool,
- /**
- * 弹层默认是否显示
- */
- defaultVisible: PropTypes.bool,
- /**
- * 弹层显示或隐藏时触发的回调函数
- * @param {Boolean} visible 弹层是否显示
- * @param {String} type 触发弹层显示或隐藏的来源
- * @param {Object} e DOM事件
- */
- onVisibleChange: PropTypes.func,
- /**
- * 触发弹层显示或者隐藏的元素
- */
- trigger: PropTypes.node,
- /**
- * 触发弹层显示或隐藏的操作类型
- */
- triggerType: PropTypes.oneOf(['hover', 'click', 'focus']),
- /**
- * 设置此属性,弹层无法显示或隐藏
- */
- disabled: PropTypes.bool,
- /**
- * 弹层相对于触发元素的定位, 详见 Overlay 的定位部分
- */
- align: PropTypes.string,
- /**
- * 弹层相对于触发元素定位的微调
- */
- offset: PropTypes.array,
- /**
- * 弹层显示或隐藏的延时时间(以毫秒为单位),在 triggerType 被设置为 hover 时生效
- */
- delay: PropTypes.number,
- /**
- * 弹层打开时是否让其中的元素自动获取焦点
- */
- autoFocus: PropTypes.bool,
- /**
- * 是否显示遮罩
- */
- hasMask: PropTypes.bool,
- /**
- * 隐藏时是否保留子节点
- */
- cache: PropTypes.bool,
- /**
- * 配置动画的播放方式,支持 { in: 'enter-class', out: 'leave-class' } 的对象参数,如果设置为 false,则不播放动画
- * @default { in: 'expandInDown', out: 'expandOutUp' }
- */
- animation: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
- };
- static defaultProps = {
- prefix: 'next-',
- pure: false,
- defaultVisible: false,
- onVisibleChange: noop,
- triggerType: 'hover',
- disabled: false,
- align: 'tl bl',
- offset: [0, 0],
- delay: 200,
- hasMask: false,
- cache: false,
- onPosition: noop,
- };
-
- constructor(props) {
- super(props);
-
- this.state = {
- visible:
- 'visible' in props
- ? props.visible
- : props.defaultVisible || false,
- autoFocus: 'autoFocus' in props ? props.autoFocus : false,
- };
-
- bindCtx(this, ['onTriggerKeyDown', 'onMenuClick', 'onVisibleChange']);
- }
-
- getVisible(props = this.props) {
- return 'visible' in props ? props.visible : this.state.visible;
- }
-
- onMenuClick() {
- this.onVisibleChange(false, 'fromContent');
- }
-
- onVisibleChange(visible, from) {
- this.setState({ visible });
-
- this.props.onVisibleChange(visible, from);
- }
-
- onTriggerKeyDown() {
- let autoFocus = true;
-
- if ('autoFocus' in this.props) {
- autoFocus = this.props.autoFocus;
- }
-
- this.setState({
- autoFocus,
- });
- }
-
- render() {
- let child = Children.only(this.props.children);
- if (typeof child.type === 'function' && child.type.isNextMenu) {
- child = React.cloneElement(child, {
- onItemClick: makeChain(
- this.onMenuClick,
- child.props.onItemClick
- ),
- });
+import Dropdown from './dropdown';
+
+export default ConfigProvider.config(Dropdown, {
+ transform: /* istanbul ignore next */ (props, deprecated) => {
+ if ('triggerType' in props) {
+ const triggerType = Array.isArray(props.triggerType)
+ ? [...props.triggerType]
+ : [props.triggerType];
+
+ if (triggerType.indexOf('focus') > -1) {
+ deprecated(
+ 'triggerType[focus]',
+ 'triggerType[hover, click]',
+ 'Balloon'
+ );
+ }
}
- const { trigger, rtl } = this.props;
- const newTrigger = React.cloneElement(trigger, {
- onKeyDown: makeChain(
- this.onTriggerKeyDown,
- trigger.props.onKeyDown
- ),
- });
-
- return (
-
- {child}
-
- );
- }
-}
-
-export default ConfigProvider.config(Dropdown);
+ return props;
+ },
+});
diff --git a/test/dropdown/index-spec.js b/test/dropdown/index-spec.js
index 7d20a0403e..13772aa5ef 100644
--- a/test/dropdown/index-spec.js
+++ b/test/dropdown/index-spec.js
@@ -184,8 +184,8 @@ describe('Dropdown', () => {
document.querySelectorAll('.next-menu-item')[0]
);
- ReactTestUtils.Simulate.keyDown(document.activeElement, {
- keyCode: KEYCODE.DOWN,
+ ReactTestUtils.Simulate.keyDown(trigger, {
+ keyCode: KEYCODE.SPACE,
});
setTimeout(() => {
@@ -209,7 +209,6 @@ describe('Dropdown', () => {
ReactDOM.render(
Hello dropdown}
animation={false}
>
@@ -250,7 +249,7 @@ describe('Dropdown', () => {
ReactDOM.render(
Hello dropdown}
animation={false}
>