From 2c164770b6ac4cc9c8a9741042ac694e31cffc54 Mon Sep 17 00:00:00 2001
From: tangjinzhou <415800467@qq.com>
Date: Tue, 9 Jan 2018 14:21:15 +0800
Subject: [PATCH 1/7] add keydown

---
 components/_util/cloneElement.js |  8 ----
 components/_util/vnode.js        |  7 ++++
 components/menu/demo/antd.vue    | 12 +++---
 components/menu/src/DOMWrap.vue  |  1 +
 components/menu/src/Menu.vue     |  2 +-
 components/menu/src/MenuItem.vue |  1 +
 components/menu/src/MenuMixin.js | 65 +++++++-------------------------
 components/menu/src/SubMenu.vue  |  3 +-
 components/trigger/index.vue     |  1 -
 package.json                     |  4 +-
 10 files changed, 33 insertions(+), 71 deletions(-)
 delete mode 100644 components/_util/cloneElement.js

diff --git a/components/_util/cloneElement.js b/components/_util/cloneElement.js
deleted file mode 100644
index ec8b55c1f..000000000
--- a/components/_util/cloneElement.js
+++ /dev/null
@@ -1,8 +0,0 @@
-export default (node, nodeProps) => {
-  const { props, style, class: cls, attrs, key } = nodeProps
-  if (node.componentOptions) {
-    const propsData = node.componentOptions.propsData
-    Object.assign(propsData, nodeProps)
-  }
-  return node
-}
diff --git a/components/_util/vnode.js b/components/_util/vnode.js
index e3225d29a..8068ea46f 100644
--- a/components/_util/vnode.js
+++ b/components/_util/vnode.js
@@ -78,11 +78,18 @@ export function cloneElement (n, nodeProps, clone) {
   const { style = data.style,
     class: cls = data.class,
     attrs = data.attrs,
+    ref,
   } = nodeProps
   node.data = Object.assign(data, { style, attrs, class: cls, on: { ...(data.on || {}), ...on }})
   if (key !== undefined) {
     node.key = key
     node.data.key = key
   }
+  if (typeof ref === 'string') {
+    node.data.ref = ref
+  }
   return node
 }
+export function getComponentName (opts) {
+  return opts && (opts.Ctor.options.name || opts.tag)
+}
diff --git a/components/menu/demo/antd.vue b/components/menu/demo/antd.vue
index 841b2cfe4..e1083b02e 100644
--- a/components/menu/demo/antd.vue
+++ b/components/menu/demo/antd.vue
@@ -1,8 +1,6 @@
 <script>
-import { cloneElement } from '../../_util/vnode'
 import Clone from '../../_util/Clone'
 import Menu, { SubMenu, Item as MenuItem, Divider } from '../src/index'
-import { Icon } from 'antd'
 import animate from 'css-animation'
 
 function handleSelect (info) {
@@ -74,17 +72,16 @@ export default {
         </SubMenu>
       </SubMenu>
     </SubMenu>)
-
-    function onOpenChange (value) {
-      console.log('onOpenChange', value)
+    const onOpenChange = (value) => {
+      console.log('onOpenChange', value, this.$refs)
     }
     const commonMenu = () => (
       <Menu onSelect={handleSelect} onOpenChange={onOpenChange}>
-        <SubMenu key='1' title={<span>sub menu</span>}>
+        <SubMenu ref='test' key='1' title={<span>sub menu</span>}>
           <MenuItem key='1-1'>
           0-1
           </MenuItem>
-          <MenuItem key='1-2'>0-2</MenuItem>
+          <MenuItem key='1-2' disabled>0-2</MenuItem>
         </SubMenu>
         {nestSubMenu()}
         <MenuItem key='2'>1</MenuItem>
@@ -130,6 +127,7 @@ export default {
           <div style={{ margin: '20px', width: '400px' }}><Clone childProps={{
             mode: 'inline',
             defaultOpenKeys: ['1'],
+            defaultSelectedKeys: ['1-2', '4-1'],
             openAnimation: animation,
           }} >
             {commonMenu()}
diff --git a/components/menu/src/DOMWrap.vue b/components/menu/src/DOMWrap.vue
index fb18dc9a1..98f5cde0c 100644
--- a/components/menu/src/DOMWrap.vue
+++ b/components/menu/src/DOMWrap.vue
@@ -33,6 +33,7 @@ export default {
     const Tag = this.$props.tag
     const tagProps = {
       attr: { ...otherProps, ...this.$attrs },
+      on: this.$listeners,
     }
     return <Tag {...tagProps} class={this.class}>{this.$slots.default}</Tag>
   },
diff --git a/components/menu/src/Menu.vue b/components/menu/src/Menu.vue
index 621b2a040..1b841f446 100644
--- a/components/menu/src/Menu.vue
+++ b/components/menu/src/Menu.vue
@@ -163,7 +163,7 @@ const Menu = {
       const { sOpenKeys } = this.$data
       if (sOpenKeys.length) {
         lastOpen = this.getFlatInstanceArray().filter((c) => {
-          return c && sOpenKeys.indexOf(c.props.eventKey) !== -1
+          return c && sOpenKeys.indexOf(c.eventKey) !== -1
         })
       }
       return lastOpen[0]
diff --git a/components/menu/src/MenuItem.vue b/components/menu/src/MenuItem.vue
index 5df5783cc..33aa3cce6 100644
--- a/components/menu/src/MenuItem.vue
+++ b/components/menu/src/MenuItem.vue
@@ -14,6 +14,7 @@ const MenuItem = {
     selectedKeys: PropTypes.array,
     disabled: PropTypes.bool,
     title: PropTypes.string,
+    index: PropTypes.number,
     inlineIndent: PropTypes.number.def(24),
     level: PropTypes.number.def(1),
     mode: PropTypes.oneOf(['horizontal', 'vertical', 'vertical-left', 'vertical-right', 'inline']).def('vertical'),
diff --git a/components/menu/src/MenuMixin.js b/components/menu/src/MenuMixin.js
index 5b1cbe6ea..2b3909954 100644
--- a/components/menu/src/MenuMixin.js
+++ b/components/menu/src/MenuMixin.js
@@ -9,10 +9,8 @@ function allDisabled (arr) {
   if (!arr.length) {
     return true
   }
-
   return arr.every(c => {
-    const propsData = c.componentOptions.propsData || {}
-    return !!propsData.disabled
+    return !!c.disabled
   })
 }
 
@@ -53,10 +51,6 @@ export default {
       deep: true,
     },
   },
-
-  created () {
-    this.instanceArray = []
-  },
   methods: {
     getActiveKey (originalActiveKey) {
       let activeKey = originalActiveKey
@@ -86,23 +80,13 @@ export default {
       }
       return activeKey
     },
-    saveRef (index, subIndex, c) {
-      if (c) {
-        if (subIndex !== undefined) {
-          this.instanceArray[index] = this.instanceArray[index] || []
-          this.instanceArray[index][subIndex] = c
-        } else {
-          this.instanceArray[index] = c
-        }
-      }
-    },
     // all keyboard events callbacks run from here at first
-    onKeyDown (e, callback) {
+    onKeyDown (e) {
       const keyCode = e.keyCode
       let handled
       this.getFlatInstanceArray().forEach((obj) => {
-        if (obj && obj.$props.active) {
-          handled = this.$emit('keydown', e)
+        if (obj && obj.active) {
+          handled = obj.onKeyDown(e)
         }
       })
       if (handled) {
@@ -115,15 +99,11 @@ export default {
       if (activeItem) {
         e.preventDefault()
         this.setState({
-          sActiveKey: activeItem.$props.eventKey,
+          sActiveKey: activeItem.eventKey,
         }, () => {
           scrollIntoView(activeItem.$el, this.$el, {
             onlyScrollIfNeeded: true,
           })
-          // https://github.com/react-component/menu/commit/9899a9672f6f028ec3cdf773f1ecea5badd2d33e
-          if (typeof callback === 'function') {
-            callback(activeItem)
-          }
         })
         return 1
       } else if (activeItem === undefined) {
@@ -143,22 +123,13 @@ export default {
     },
 
     getFlatInstanceArray () {
-      let instanceArray = this.instanceArray
-      const hasInnerArray = instanceArray.some((a) => {
-        return Array.isArray(a)
-      })
-      if (hasInnerArray) {
-        instanceArray = []
-        this.instanceArray.forEach((a) => {
-          if (Array.isArray(a)) {
-            instanceArray.push.apply(instanceArray, a)
-          } else {
-            instanceArray.push(a)
-          }
-        })
-        this.instanceArray = instanceArray
+      let instance = []
+      try {
+        instance = this.$children[0].$children || []
+      } catch (error) {
+
       }
-      return instanceArray
+      return instance
     },
 
     renderCommonMenuItem (child, i, subIndex, extraProps) {
@@ -175,7 +146,6 @@ export default {
           renderMenuItem: this.renderMenuItem,
           rootPrefixCls: props.prefixCls,
           index: i,
-          // parentMenu: this,
           eventKey: key,
           active: !childProps.disabled && isActive,
           multiple: props.multiple,
@@ -195,11 +165,7 @@ export default {
           destroy: this.onDestroy,
           select: this.onSelect,
         },
-        // ref: childProps.disabled ? undefined : child.ref,
-        // ref: childProps.disabled ? undefined
-        //  : createChainedFunction(child.ref, saveRef.bind(this, i, subIndex)),
       }
-      // !childProps.disabled && this.saveRef(i, subIndex, child.ref)
       if (props.mode === 'inline') {
         newChildProps.props.triggerSubMenuAction = 'click'
       }
@@ -210,7 +176,6 @@ export default {
     },
 
     renderRoot (props, children = []) {
-      this.instanceArray = []
       const className = {
         [props.prefixCls]: true,
         [props.class]: true,
@@ -236,7 +201,7 @@ export default {
         domProps.attrs.tabIndex = '0'
         domProps.on.keydown = this.onKeyDown
       }
-      const newChildren = children.map(this.renderMenuItem)
+      const newChildren = children.map((c, i) => this.renderMenuItem(c, i))
       return (
         <DOMWrap
           {...domProps}
@@ -259,8 +224,7 @@ export default {
       // find current activeIndex
       let activeIndex = -1
       children.every((c, ci) => {
-        const propsData = c.componentOptions.propsData || {}
-        if (c && propsData.eventKey === sActiveKey) {
+        if (c && c.eventKey === sActiveKey) {
           activeIndex = ci
           return false
         }
@@ -275,8 +239,7 @@ export default {
       let i = start
       for (; ;) {
         const child = children[i]
-        const propsData = child.componentOptions.propsData || {}
-        if (!child || propsData.disabled) {
+        if (!child || child.disabled) {
           i = (i + 1 + len) % len
           // complete a loop
           if (i === start) {
diff --git a/components/menu/src/SubMenu.vue b/components/menu/src/SubMenu.vue
index fdb38b991..293adb56c 100644
--- a/components/menu/src/SubMenu.vue
+++ b/components/menu/src/SubMenu.vue
@@ -31,6 +31,7 @@ export default {
     multiple: PropTypes.bool,
     active: PropTypes.bool, // TODO: remove
     isRootMenu: PropTypes.bool,
+    index: PropTypes.number,
     // onItemHover: PropTypes.func,
     // onSelect: PropTypes.func,
     triggerSubMenuAction: PropTypes.string,
@@ -100,7 +101,7 @@ export default {
 
     onKeyDown (e) {
       const keyCode = e.keyCode
-      const menu = this.menuInstance
+      const menu = this.$refs.menuInstance
       const isOpen = this.isOpen()
 
       if (keyCode === KeyCode.ENTER) {
diff --git a/components/trigger/index.vue b/components/trigger/index.vue
index 4c08b339e..3313d0aba 100644
--- a/components/trigger/index.vue
+++ b/components/trigger/index.vue
@@ -258,7 +258,6 @@ export default {
     },
 
     getRootDomNode () {
-      console.log('this.$el.children', this.$el.children)
       return this.$el.children ? this.$el.children[0] : this.$el
     },
 
diff --git a/package.json b/package.json
index bd41b9c3a..fe8012171 100644
--- a/package.json
+++ b/package.json
@@ -67,16 +67,16 @@
     "style-loader": "^0.18.2",
     "stylelint": "^8.1.1",
     "stylelint-config-standard": "^17.0.0",
-    "vue": "^2.4.4",
     "vue-loader": "^13.0.5",
     "vue-router": "^3.0.1",
-    "vue-template-compiler": "^2.4.4",
+    "vue-template-compiler": "^2.5.13",
     "webpack": "^3.6.0",
     "webpack-dev-server": "^2.8.2"
   },
   "dependencies": {
     "add-dom-event-listener": "^1.0.2",
     "css-animation": "^1.4.1",
+    "dom-align": "^1.6.6",
     "dom-scroll-into-view": "^1.2.1",
     "eslint-plugin-vue": "^3.13.0",
     "lodash.clonedeep": "^4.5.0",

From 9a8eb4d27758c23f83f73d3a6bb74a4b6ba39e6b Mon Sep 17 00:00:00 2001
From: tangjinzhou <415800467@qq.com>
Date: Thu, 11 Jan 2018 18:53:51 +0800
Subject: [PATCH 2/7] add toolTip

---
 components/_util/vnode.js                     |  12 +
 components/button/button.vue                  |   1 +
 components/index.js                           |   2 +-
 components/style.js                           |   1 +
 .../tooltip/demo/arrow-point-at-center.vue    |  24 ++
 .../tooltip/demo/auto-adjust-overflow.vue     |  43 ++
 components/tooltip/demo/basic.vue             |  23 ++
 components/tooltip/demo/index.vue             | 111 +-----
 components/tooltip/demo/placement.vue         | 112 ++++++
 components/tooltip/index.js                   |   1 -
 components/tooltip/placements.js              |  88 +++++
 components/tooltip/src/Tooltip.vue            | 108 +++++
 components/tooltip/src/index.js               |   3 +
 components/tooltip/src/placements.js          |  83 ++++
 components/tooltip/tooltip.vue                | 368 ++++++++----------
 components/trigger/Popup.vue                  |  17 +-
 components/trigger/index.md                   |   8 +-
 components/trigger/index.vue                  |  24 +-
 components/trigger/utils.js                   |   4 +-
 examples/index.less                           |   4 +-
 examples/md.vue                               |   3 +-
 21 files changed, 698 insertions(+), 342 deletions(-)
 create mode 100644 components/tooltip/demo/arrow-point-at-center.vue
 create mode 100644 components/tooltip/demo/auto-adjust-overflow.vue
 create mode 100644 components/tooltip/demo/basic.vue
 create mode 100644 components/tooltip/demo/placement.vue
 create mode 100644 components/tooltip/placements.js
 create mode 100644 components/tooltip/src/Tooltip.vue
 create mode 100644 components/tooltip/src/index.js
 create mode 100644 components/tooltip/src/placements.js

diff --git a/components/_util/vnode.js b/components/_util/vnode.js
index 8068ea46f..ecf245b82 100644
--- a/components/_util/vnode.js
+++ b/components/_util/vnode.js
@@ -93,3 +93,15 @@ export function cloneElement (n, nodeProps, clone) {
 export function getComponentName (opts) {
   return opts && (opts.Ctor.options.name || opts.tag)
 }
+
+export function isValidElement (ele) {
+  return !!ele.tag
+}
+
+export function getClass (ele) {
+  return ele.data && (ele.data.class || ele.data.staticClass)
+}
+
+export function getStyle (ele) {
+  return ele.data && (ele.data.style || ele.data.staticStyle)
+}
diff --git a/components/button/button.vue b/components/button/button.vue
index 031d056ec..39b78a8d0 100644
--- a/components/button/button.vue
+++ b/components/button/button.vue
@@ -4,6 +4,7 @@ const rxTwoCNChar = /^[\u4e00-\u9fa5]{2}$/
 const isTwoCNChar = rxTwoCNChar.test.bind(rxTwoCNChar)
 export default {
   name: 'Button',
+  __ANT_BUTTON: true,
   components: { Icon },
   props: {
     prefixCls: {
diff --git a/components/index.js b/components/index.js
index d430782f9..a9981c3ec 100644
--- a/components/index.js
+++ b/components/index.js
@@ -10,7 +10,7 @@ export { default as Grid } from './grid'
 
 export { default as Rate } from './rate'
 
-export { default as ToolTip } from './tooltip'
+export { default as Tooltip } from './tooltip'
 
 export { default as Pagination } from './pagination'
 
diff --git a/components/style.js b/components/style.js
index ec801667d..fa65f1323 100644
--- a/components/style.js
+++ b/components/style.js
@@ -10,5 +10,6 @@ import './avatar/style'
 import './badge/style'
 import './tabs/style'
 import './input/style'
+import './tooltip/style'
 
 import './menu/style'
diff --git a/components/tooltip/demo/arrow-point-at-center.vue b/components/tooltip/demo/arrow-point-at-center.vue
new file mode 100644
index 000000000..643386649
--- /dev/null
+++ b/components/tooltip/demo/arrow-point-at-center.vue
@@ -0,0 +1,24 @@
+<template>
+<div>
+<md>
+## 箭头指向
+设置了 `arrowPointAtCenter` 后,箭头将指向目标元素的中心。
+</md>
+  <Tooltip placement="topLeft" title="Prompt Text">
+    <AntButton>Align edge / 边缘对齐</AntButton>
+  </Tooltip>
+  <Tooltip placement="topLeft" title="Prompt Text" arrowPointAtCenter>
+    <AntButton>Arrow points to center / 箭头指向中心</AntButton>
+  </Tooltip>
+</div>
+</template>
+
+<script>
+import { Tooltip, Button } from 'antd'
+export default {
+  components: {
+    Tooltip,
+    AntButton: Button,
+  },
+}
+</script>
diff --git a/components/tooltip/demo/auto-adjust-overflow.vue b/components/tooltip/demo/auto-adjust-overflow.vue
new file mode 100644
index 000000000..585815999
--- /dev/null
+++ b/components/tooltip/demo/auto-adjust-overflow.vue
@@ -0,0 +1,43 @@
+<template>
+<div>
+  <md>
+  ## 自动调整位置
+  气泡框不可见时自动调整位置。
+  </md>
+  <div :style="wrapStyles">
+    <Tooltip placement="left" title="Prompt Text" :getPopupContainer="getPopupContainer">
+      <AntButton>Adjust automatically / 自动调整</AntButton>
+    </Tooltip>
+    <br />
+    <Tooltip placement="left" title="Prompt Text" :getPopupContainer="getPopupContainer" :autoAdjustOverflow="false">
+      <AntButton>Ingore / 不处理</AntButton>
+    </Tooltip>
+  </div>
+</div>
+</template>
+
+<script>
+import { Tooltip, Button } from 'antd'
+const wrapStyles = {
+  overflow: 'hidden',
+  position: 'relative',
+  padding: '24px',
+  border: '1px solid #e9e9e9',
+}
+export default {
+  data () {
+    return {
+      wrapStyles,
+    }
+  },
+  methods: {
+    getPopupContainer (trigger) {
+      return trigger.parentElement
+    },
+  },
+  components: {
+    Tooltip,
+    AntButton: Button,
+  },
+}
+</script>
diff --git a/components/tooltip/demo/basic.vue b/components/tooltip/demo/basic.vue
new file mode 100644
index 000000000..b60af9327
--- /dev/null
+++ b/components/tooltip/demo/basic.vue
@@ -0,0 +1,23 @@
+<template>
+<div>
+<md>
+## 基本
+最简单的用法。
+</md>
+<Tooltip>
+  <template slot='title'>
+    prompt text
+  </template>
+  Tooltip will show when mouse enter.
+</Tooltip>
+</div>
+</template>
+
+<script>
+import { Tooltip } from 'antd'
+export default {
+  components: {
+    Tooltip,
+  },
+}
+</script>
diff --git a/components/tooltip/demo/index.vue b/components/tooltip/demo/index.vue
index 178db3bff..b8c54ed5b 100644
--- a/components/tooltip/demo/index.vue
+++ b/components/tooltip/demo/index.vue
@@ -1,97 +1,26 @@
 <template>
   <div>
-    <tool-tip
-      placement="top"
-      :title="showText"
-      :autoAdjustOverflow="autoAdjustOverflow"
-    >
-      <h1 @click="boom" class="test">撞到边缘翻转位置 & 点击更新</h1>
-    </tool-tip>
-    <ant-button @click="reverse" type="primary">{{autoAdjustOverflow ? '启用' : '关闭'}}自动调整中</ant-button>
-    <div class="box">
-      <h2>切换arrowPointAtCenter模式</h2>
-      <ant-button @click="change">{{arrowPointAtCenter}}</ant-button>
-      <table>
-        <tr v-for="(tr, index) in table" :key="index">
-          <td v-for="(td, i) in tr" :key="i">
-            <tool-tip
-              v-if="td"
-              :placement="td"
-              :title="td"
-              :arrowPointAtCenter="arrowPointAtCenter"
-            >
-              <AntButton type="primary">{{td}}</AntButton>
-            </tool-tip>
-          </td>
-        </tr>
-      </table>
-    </div>
-    <div>
-      <p>
-        <tool-tip :arrowPointAtCenter="true" title="Consider using the NamedModulesPlugin for module names." placement="topLeft">
-          <ant-button>arrowPointAtCenter arrowPointAtCenter arrowPointAtCenter</ant-button>
-        </tool-tip>
-      </p>
-    </div>
+    <h1>Basic</h1>
+    <Basic />
+    <h1>ArrowCenter</h1>
+    <ArrowCenter />
+    <h1>AutoAdjust</h1>
+    <AutoAdjust />
+    <h1>Placement</h1>
+    <Placement />
   </div>
 </template>
-
 <script>
-  import { ToolTip, Button } from 'antd'
-  import 'antd/button/style'
-	export default {
-		name: 'tooltip-basic',
-		data() {
-			return {
-        show: true,
-        showText: '你好啊,233',
-        table: [
-          ['', 'topLeft', 'top', 'topRight', ''],
-          ['leftTop', '', '', '', 'rightTop'],
-          ['left', '', '', '', 'right'],
-          ['leftBottom', '', '', '', 'rightBottom'],
-          ['', 'bottomLeft', 'bottom', 'bottomRight', ''],
-        ],
-        arrowPointAtCenter: false,
-        autoAdjustOverflow: true,
-      }
-		},
-    methods: {
-		  boom() {
-		    if (this.showText.length % 20) {
-          this.showText += '3'
-        } else {
-		      this.showText += ' '
-        }
-      },
-      change() {
-		    this.arrowPointAtCenter = !this.arrowPointAtCenter
-      },
-      reverse() {
-		    this.autoAdjustOverflow = !this.autoAdjustOverflow
-      }
-    },
-    components: {
-		  ToolTip,
-      AntButton: Button,
-    }
-	}
+import Basic from './basic'
+import ArrowCenter from './arrow-point-at-center'
+import AutoAdjust from './auto-adjust-overflow'
+import Placement from './placement'
+export default {
+  components: {
+    Basic,
+    ArrowCenter,
+    AutoAdjust,
+    Placement,
+  },
+}
 </script>
-<style scoped lang="less">
-  .test {
-    margin: 20px;
-    display: inline-block;
-  }
-  .box {
-    margin: 100px;
-  }
-  table {
-    td {
-      padding: 20px;
-    }
-    p {
-      text-align: center;
-      vertical-align: middle;
-    }
-  }
-</style>
diff --git a/components/tooltip/demo/placement.vue b/components/tooltip/demo/placement.vue
new file mode 100644
index 000000000..36aec0438
--- /dev/null
+++ b/components/tooltip/demo/placement.vue
@@ -0,0 +1,112 @@
+<template>
+<div id="components-tooltip-demo-placement">
+<md>
+## 位置
+位置有 12 个方向。
+</md>
+  <div :style="{ marginLeft: `${buttonWidth}px`, whiteSpace: 'nowrap' }">
+    <Tooltip placement="topLeft">
+      <template slot="title">
+        <span>prompt text</span>
+      </template>
+      <AntButton>TL</AntButton>
+    </Tooltip>
+    <Tooltip placement="top">
+      <template slot="title">
+        <span>prompt text</span>
+      </template>
+      <AntButton>Top</AntButton>
+    </Tooltip>
+    <Tooltip placement="topRight">
+      <template slot="title">
+        <span>prompt text</span>
+      </template>
+      <AntButton>TR</AntButton>
+    </Tooltip>
+  </div>
+  <div :style="{ width: `${buttonWidth}px`, float: 'left' }">
+    <Tooltip placement="leftTop">
+      <template slot="title">
+        <span>prompt text</span>
+      </template>
+      <AntButton>LT</AntButton>
+    </Tooltip>
+    <Tooltip placement="left">
+      <template slot="title">
+        <span>prompt text</span>
+      </template>
+      <AntButton>Left</AntButton>
+    </Tooltip>
+    <Tooltip placement="leftBottom">
+      <template slot="title">
+        <span>prompt text</span>
+      </template>
+      <AntButton>LB</AntButton>
+    </Tooltip>
+  </div>
+  <div :style="{ width: `${buttonWidth}px`, marginLeft: `${buttonWidth * 4 + 24 }px`}">
+    <Tooltip placement="rightTop">
+      <template slot="title">
+        <span>prompt text</span>
+      </template>
+      <AntButton>RT</AntButton>
+    </Tooltip>
+    <Tooltip placement="right">
+      <template slot="title">
+        <span>prompt text</span>
+      </template>
+      <AntButton>Right</AntButton>
+    </Tooltip>
+    <Tooltip placement="rightBottom">
+      <template slot="title">
+        <span>prompt text</span>
+      </template>
+      <AntButton>RB</AntButton>
+    </Tooltip>
+  </div>
+  <div :style="{ marginLeft: `${buttonWidth}px`, clear: 'both', whiteSpace: 'nowrap' }">
+    <Tooltip placement="bottomLeft">
+      <template slot="title">
+        <span>prompt text</span>
+      </template>
+      <AntButton>BL</AntButton>
+    </Tooltip>
+    <Tooltip placement="bottom">
+      <template slot="title">
+        <span>prompt text</span>
+      </template>
+      <AntButton>Bottom</AntButton>
+    </Tooltip>
+    <Tooltip placement="bottomRight">
+      <template slot="title">
+        <span>prompt text</span>
+      </template>
+      <AntButton>BR</AntButton>
+    </Tooltip>
+  </div>
+</div>
+</template>
+
+<script>
+import { Tooltip, Button } from 'antd'
+export default {
+  data () {
+    return {
+      buttonWidth: 70,
+    }
+  },
+  components: {
+    Tooltip,
+    AntButton: Button,
+  },
+}
+</script>
+<style>
+#components-tooltip-demo-placement .ant-btn {
+  width: 70px;
+  text-align: center;
+  padding: 0;
+  margin-right: 8px;
+  margin-bottom: 8px;
+}
+</style>
diff --git a/components/tooltip/index.js b/components/tooltip/index.js
index b93dd566a..4b5f7d8bc 100644
--- a/components/tooltip/index.js
+++ b/components/tooltip/index.js
@@ -1,4 +1,3 @@
 import ToolTip from './tooltip.vue'
-import './style'
 export default ToolTip
 
diff --git a/components/tooltip/placements.js b/components/tooltip/placements.js
new file mode 100644
index 000000000..f3b6bd8e8
--- /dev/null
+++ b/components/tooltip/placements.js
@@ -0,0 +1,88 @@
+import { placements as rcPlacements } from './src/placements'
+
+const autoAdjustOverflowEnabled = {
+  adjustX: 1,
+  adjustY: 1,
+}
+
+const autoAdjustOverflowDisabled = {
+  adjustX: 0,
+  adjustY: 0,
+}
+
+const targetOffset = [0, 0]
+
+export function getOverflowOptions (autoAdjustOverflow) {
+  if (typeof autoAdjustOverflow === 'boolean') {
+    return autoAdjustOverflow ? autoAdjustOverflowEnabled : autoAdjustOverflowDisabled
+  }
+  return {
+    ...autoAdjustOverflowDisabled,
+    ...autoAdjustOverflow,
+  }
+}
+
+export default function getPlacements (config) {
+  const { arrowWidth = 5, horizontalArrowShift = 16, verticalArrowShift = 12, autoAdjustOverflow = true } = config
+  const placementMap = {
+    left: {
+      points: ['cr', 'cl'],
+      offset: [-4, 0],
+    },
+    right: {
+      points: ['cl', 'cr'],
+      offset: [4, 0],
+    },
+    top: {
+      points: ['bc', 'tc'],
+      offset: [0, -4],
+    },
+    bottom: {
+      points: ['tc', 'bc'],
+      offset: [0, 4],
+    },
+    topLeft: {
+      points: ['bl', 'tc'],
+      offset: [-(horizontalArrowShift + arrowWidth), -4],
+    },
+    leftTop: {
+      points: ['tr', 'cl'],
+      offset: [-4, -(verticalArrowShift + arrowWidth)],
+    },
+    topRight: {
+      points: ['br', 'tc'],
+      offset: [horizontalArrowShift + arrowWidth, -4],
+    },
+    rightTop: {
+      points: ['tl', 'cr'],
+      offset: [4, -(verticalArrowShift + arrowWidth)],
+    },
+    bottomRight: {
+      points: ['tr', 'bc'],
+      offset: [horizontalArrowShift + arrowWidth, 4],
+    },
+    rightBottom: {
+      points: ['bl', 'cr'],
+      offset: [4, verticalArrowShift + arrowWidth],
+    },
+    bottomLeft: {
+      points: ['tl', 'bc'],
+      offset: [-(horizontalArrowShift + arrowWidth), 4],
+    },
+    leftBottom: {
+      points: ['br', 'cl'],
+      offset: [-4, verticalArrowShift + arrowWidth],
+    },
+  }
+  Object.keys(placementMap).forEach(key => {
+    placementMap[key] = config.arrowPointAtCenter ? {
+      ...placementMap[key],
+      overflow: getOverflowOptions(autoAdjustOverflow),
+      targetOffset,
+    } : {
+      ...rcPlacements[key],
+      overflow: getOverflowOptions(autoAdjustOverflow),
+    }
+  })
+  return placementMap
+}
diff --git a/components/tooltip/src/Tooltip.vue b/components/tooltip/src/Tooltip.vue
new file mode 100644
index 000000000..5d13ae671
--- /dev/null
+++ b/components/tooltip/src/Tooltip.vue
@@ -0,0 +1,108 @@
+<script>
+import PropTypes from '../../_util/vue-types'
+import Trigger from '../../trigger'
+import { placements } from './placements'
+import hasProp from '../../_util/hasProp'
+export default {
+  props: {
+    trigger: PropTypes.any.def(['hover']),
+    defaultVisible: PropTypes.bool,
+    visible: PropTypes.bool,
+    placement: PropTypes.string.def('right'),
+    transitionName: PropTypes.oneOfType([
+      PropTypes.string,
+      PropTypes.object,
+    ]),
+    animation: PropTypes.any,
+    // onVisibleChange: PropTypes.func,
+    afterVisibleChange: PropTypes.func.def(() => {}),
+    overlay: PropTypes.any,
+    overlayStyle: PropTypes.object,
+    overlayClassName: PropTypes.string,
+    prefixCls: PropTypes.string.def('rc-tooltip'),
+    mouseEnterDelay: PropTypes.number.def(0),
+    mouseLeaveDelay: PropTypes.number.def(0.1),
+    getTooltipContainer: PropTypes.func,
+    destroyTooltipOnHide: PropTypes.bool.def(false),
+    align: PropTypes.object.def({}),
+    arrowContent: PropTypes.any.def(null),
+    tipId: PropTypes.string,
+    builtinPlacements: PropTypes.object,
+  },
+  methods: {
+    getPopupElement (h) {
+      const { arrowContent, overlay, prefixCls, tipId } = this.$props
+      return ([
+        <div class={`${prefixCls}-arrow`} key='arrow'>
+          {this.$slots.arrowContent}
+          {typeof arrowContent === 'function' ? arrowContent(h) : arrowContent}
+        </div>,
+        <div class={`${prefixCls}-inner`} key='content' id={tipId}>
+          {typeof overlay === 'function' ? overlay(h) : overlay}
+          {this.$slots.overlay}
+        </div>,
+      ])
+    },
+
+    getPopupDomNode () {
+      return this.$refs.trigger.getPopupDomNode()
+    },
+    onVisibleChange (val) {
+      this.$emit('visibleChange', val)
+    },
+    onPopupAlign () {
+      this.$emit('popupAlign', ...arguments)
+    },
+  },
+  render (h) {
+    const {
+      overlayClassName, trigger,
+      mouseEnterDelay, mouseLeaveDelay,
+      overlayStyle, prefixCls,
+      afterVisibleChange,
+      transitionName, animation,
+      placement, align,
+      destroyTooltipOnHide,
+      defaultVisible, getTooltipContainer,
+      ...restProps
+    } = this.$props
+    const extraProps = { ...restProps }
+    if (hasProp(this, 'visible')) {
+      extraProps.popupVisible = this.$props.visible
+    }
+    const triggerProps = {
+      props: {
+        popupClassName: overlayClassName,
+        prefixCls: prefixCls,
+        action: trigger,
+        builtinPlacements: placements,
+        popupPlacement: placement,
+        popupAlign: align,
+        getPopupContainer: getTooltipContainer,
+        // onPopupVisibleChange: onVisibleChange,
+        afterPopupVisibleChange: afterVisibleChange,
+        popupTransitionName: transitionName,
+        popupAnimation: animation,
+        defaultPopupVisible: defaultVisible,
+        destroyPopupOnHide: destroyTooltipOnHide,
+        mouseLeaveDelay: mouseLeaveDelay,
+        popupStyle: overlayStyle,
+        mouseEnterDelay: mouseEnterDelay,
+        ...extraProps,
+      },
+      on: {
+        popupVisibleChange: this.onVisibleChange,
+        popupAlign: this.onPopupAlign,
+      },
+      ref: 'trigger',
+    }
+    return (<Trigger {...triggerProps}>
+      <template slot='popup'>
+        {this.getPopupElement(h)}
+      </template>
+      {this.$slots.default}
+    </Trigger>)
+  },
+}
+
+</script>
diff --git a/components/tooltip/src/index.js b/components/tooltip/src/index.js
new file mode 100644
index 000000000..331785904
--- /dev/null
+++ b/components/tooltip/src/index.js
@@ -0,0 +1,3 @@
+import Tooltip from './Tooltip'
+
+export default Tooltip
diff --git a/components/tooltip/src/placements.js b/components/tooltip/src/placements.js
new file mode 100644
index 000000000..80917822e
--- /dev/null
+++ b/components/tooltip/src/placements.js
@@ -0,0 +1,83 @@
+const autoAdjustOverflow = {
+  adjustX: 1,
+  adjustY: 1,
+}
+
+const targetOffset = [0, 0]
+
+export const placements = {
+  left: {
+    points: ['cr', 'cl'],
+    overflow: autoAdjustOverflow,
+    offset: [-4, 0],
+    targetOffset,
+  },
+  right: {
+    points: ['cl', 'cr'],
+    overflow: autoAdjustOverflow,
+    offset: [4, 0],
+    targetOffset,
+  },
+  top: {
+    points: ['bc', 'tc'],
+    overflow: autoAdjustOverflow,
+    offset: [0, -4],
+    targetOffset,
+  },
+  bottom: {
+    points: ['tc', 'bc'],
+    overflow: autoAdjustOverflow,
+    offset: [0, 4],
+    targetOffset,
+  },
+  topLeft: {
+    points: ['bl', 'tl'],
+    overflow: autoAdjustOverflow,
+    offset: [0, -4],
+    targetOffset,
+  },
+  leftTop: {
+    points: ['tr', 'tl'],
+    overflow: autoAdjustOverflow,
+    offset: [-4, 0],
+    targetOffset,
+  },
+  topRight: {
+    points: ['br', 'tr'],
+    overflow: autoAdjustOverflow,
+    offset: [0, -4],
+    targetOffset,
+  },
+  rightTop: {
+    points: ['tl', 'tr'],
+    overflow: autoAdjustOverflow,
+    offset: [4, 0],
+    targetOffset,
+  },
+  bottomRight: {
+    points: ['tr', 'br'],
+    overflow: autoAdjustOverflow,
+    offset: [0, 4],
+    targetOffset,
+  },
+  rightBottom: {
+    points: ['bl', 'br'],
+    overflow: autoAdjustOverflow,
+    offset: [4, 0],
+    targetOffset,
+  },
+  bottomLeft: {
+    points: ['tl', 'bl'],
+    overflow: autoAdjustOverflow,
+    offset: [0, 4],
+    targetOffset,
+  },
+  leftBottom: {
+    points: ['br', 'bl'],
+    overflow: autoAdjustOverflow,
+    offset: [-4, 0],
+    targetOffset,
+  },
+}
+
+export default placements
diff --git a/components/tooltip/tooltip.vue b/components/tooltip/tooltip.vue
index 97065950d..6c19912d2 100644
--- a/components/tooltip/tooltip.vue
+++ b/components/tooltip/tooltip.vue
@@ -1,121 +1,137 @@
 <script>
-import Vue from 'vue'
+import { cloneElement, isValidElement, getClass, getStyle } from '../_util/vnode'
+import RcTooltip from './src/tooltip'
+import getPlacements from './placements'
+import PropTypes from '../_util/vue-types'
+import hasProp from '../_util/hasProp'
+
+const splitObject = (obj, keys) => {
+  const picked = {}
+  const omited = { ...obj }
+  keys.forEach(key => {
+    if (obj && key in obj) {
+      picked[key] = obj[key]
+      delete omited[key]
+    }
+  })
+  return { picked, omited }
+}
+
 export default {
-  name: 'ToolTip',
+  name: 'Tooltip',
   props: {
-    title: String,
-    prefixCls: {
-      default: 'ant-tooltip',
-    },
-    placement: {
-      default: 'top',
-      validator: val => ['top', 'left', 'right', 'bottom', 'topLeft', 'topRight', 'bottomLeft', 'bottomRight', 'leftTop', 'leftBottom', 'rightTop', 'rightBottom'].includes(val),
-    },
-    transitionName: {
-      default: 'zoom-big-fast',
-    },
-    mouseEnterDelay: {
-      default: 0.1,
-    },
-    mouseLeaveDelay: {
-      default: 0.1,
-    },
-    arrowPointAtCenter: {
-      default: false,
-    },
-    autoAdjustOverflow: {
-      default: true,
-    },
+    trigger: PropTypes.oneOf(['hover', 'focus', 'click']).def(['hover']),
+    visible: PropTypes.bool,
+    title: PropTypes.any,
+    placement: PropTypes.oneOf(['top', 'left', 'right', 'bottom',
+      'topLeft', 'topRight', 'bottomLeft', 'bottomRight',
+      'leftTop', 'leftBottom', 'rightTop', 'rightBottom']).def('top'),
+    transitionName: PropTypes.string.def('zoom-big-fast'),
+    // onVisibleChange: PropTypes.func,
+    overlayStyle: PropTypes.object,
+    overlayClassName: PropTypes.string,
+    prefixCls: PropTypes.string.def('ant-tooltip'),
+    mouseEnterDelay: PropTypes.number.def(0.1),
+    mouseLeaveDelay: PropTypes.number.def(0.1),
+    getTooltipContainer: PropTypes.func,
+    getPopupContainer: PropTypes.func,
+    arrowPointAtCenter: PropTypes.bool.def(false),
+    autoAdjustOverflow: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]).def(true),
   },
   data () {
     return {
-      vnode: null,
-      visible: false,
-      left: 0,
-      top: 0,
-      realPlacement: this.placement,
-      t1: null,
-      t2: null,
+      sVisible: !!this.$props.visible,
     }
   },
-  computed: {
-    classes () {
-      const { prefixCls } = this
-      return {
-        [`${prefixCls}`]: true,
-      }
+  watch: {
+    visible (val) {
+      this.sVisible = val
     },
   },
   methods: {
-    checkPosition (popup, text, placement) {
-      const { top, left, bottom, right } = text
-      const reg = /(top|bottom|left|right)(.*)/
-      const [, abstractPos, suffix] = placement.match(reg)
-      let ret = placement
-      // we can change the position many times
-      if (abstractPos === 'left' && left < popup.width) ret = 'right' + suffix
-      if (abstractPos === 'right' && document.documentElement.clientWidth - right < popup.width) ret = 'left' + suffix
-      if (abstractPos === 'top' && top < popup.height) ret = 'bottom' + suffix
-      if (abstractPos === 'bottom' && document.documentElement.clientHeight - bottom < popup.height) ret = 'left' + suffix
-      return ret
-    },
-    mountNode (callback) {
-      if (this.vnode) {
-        callback()
-        return
+    onVisibleChange (visible) {
+      if (!hasProp(this, 'visible')) {
+        this.sVisible = this.isNoTitle() ? false : visible
       }
-      const div = document.createElement('div')
-      document.body.appendChild(div)
-      const that = this
-      const vnode = new Vue({
-        data () {
-          return {
-            left: 0,
-            top: 0,
-          }
-        },
-        methods: {
-          hideSelf (e) {
-            if (that.t1) {
-              clearTimeout(that.t1)
-              that.t1 = null
-            }
-            if (that.mouseLeaveDelay) {
-              that.t2 = window.setTimeout(() => {
-                if (e.relatedTarget === that.$el) {
-                  return
-                }
-                that.visible = false
-              }, +that.mouseLeaveDelay * 1e3)
-            }
-          },
-        },
-        render (h) {
-          return (
-            <transition name={that.transitionName}>
-              <div
-                v-show={that.visible}
-                class={`ant-tooltip ant-tooltip-placement-${that.realPlacement}`}
-                style={{ left: this.left + 'px', top: this.top + 'px' }}
-                onMouseleave={this.hideSelf}
-              >
-                <div class='ant-tooltip-content'>
-                  <div class='ant-tooltip-arrow'/>
-                  <div class='ant-tooltip-inner'>
-                    <span>{that.title}</span>
-                  </div>
-                </div>
-              </div>
-            </transition>
-          )
-        },
-      }).$mount(div)
-      this.$nextTick(() => {
-        this.vnode = vnode
-        callback()
+      if (!this.isNoTitle()) {
+        this.$emit('visibleChange', visible)
+      }
+    },
+
+    getPopupDomNode () {
+      return this.$refs.tooltip.getPopupDomNode()
+    },
+
+    getPlacements () {
+      const { builtinPlacements, arrowPointAtCenter, autoAdjustOverflow } = this.$props
+      return builtinPlacements || getPlacements({
+        arrowPointAtCenter,
+        verticalArrowShift: 8,
+        autoAdjustOverflow,
       })
     },
-    onPopupAlign: (placement, domNode, target, align) => {
+
+    isHoverTrigger () {
+      const { trigger } = this.$props
+      if (!trigger || trigger === 'hover') {
+        return true
+      }
+      if (Array.isArray(trigger)) {
+        return trigger.indexOf('hover') >= 0
+      }
+      return false
+    },
+
+    // Fix Tooltip won't hide at disabled button
+    // mouse events don't trigger at disabled button in Chrome
+    // https://github.com/react-component/tooltip/issues/18
+    getDisabledCompatibleChildren (ele) {
+      const isAntBtn = ele.componentOptions && ele.componentOptions.Ctor.options.__ANT_BUTTON
+      if (((isAntBtn && ele.componentOptions.propsData.disabled) || (ele.tag === 'button' && ele.data && ele.data.attrs.disabled !== false)) && this.isHoverTrigger()) {
+      // Pick some layout related style properties up to span
+      // Prevent layout bugs like https://github.com/ant-design/ant-design/issues/5254
+        const { picked, omited } = splitObject(
+          getStyle(ele),
+          ['position', 'left', 'right', 'top', 'bottom', 'float', 'display', 'zIndex'],
+        )
+        const spanStyle = {
+          display: 'inline-block', // default inline-block is important
+          ...picked,
+          cursor: 'not-allowed',
+        }
+        const buttonStyle = {
+          ...omited,
+          pointerEvents: 'none',
+        }
+        const spanCls = getClass(ele)
+        const child = cloneElement(ele, {
+          style: buttonStyle,
+          class: null,
+        })
+        return (
+          <span style={spanStyle} class={spanCls}>
+            {child}
+          </span>
+        )
+      }
+      return ele
+    },
+
+    isNoTitle () {
+      const { $slots, title } = this
+      return !$slots.title && !title
+    },
+
+    // 动态设置动画点
+    onPopupAlign (domNode, align) {
+      const placements = this.getPlacements()
+      // 当前返回的位置
+      const placement = Object.keys(placements).filter(
+        key => (
+          placements[key].points[0] === align.points[0] &&
+        placements[key].points[1] === align.points[1]
+        ),
+      )[0]
       if (!placement) {
         return
       }
@@ -135,118 +151,46 @@ export default {
       } else if (placement.indexOf('right') >= 0 || placement.indexOf('Left') >= 0) {
         transformOrigin.left = `${-align.offset[0]}px`
       }
-      target.style.transformOrigin = `${transformOrigin.left} ${transformOrigin.top}`
-    },
-    addEventHandle (old, fn) {
-      if (!old) {
-        return fn
-      } else if (Array.isArray(old)) {
-        return old.indexOf(fn) > -1 ? old : old.concat(fn)
-      } else {
-        return old === fn ? old : [old, fn]
-      }
-    },
-    computeOffset (popup, text, placement, scale) {
-      let { width, height, top, left } = text
-      //  you cant change the properties of DOMRect
-      top += window.scrollY
-      left += window.scrollX
-      // FIXME: we can get the numbers from scale, but that's not what we really want
-      const p = { width: popup.width / scale, height: popup.height / scale }
-      const ret = { left, top }
-
-      if (/top/.test(placement)) ret.top -= p.height
-      if (/bottom/.test(placement)) ret.top += height
-      if (/left/.test(placement)) ret.left -= p.width
-      if (/right/.test(placement)) ret.left += width
-
-      // FIXME: magic number 20 & 14 comes from the offset of triangle
-      if (/Left/.test(placement)) {
-        if (this.arrowPointAtCenter) ret.left += width / 2 - 20
-      } else if (/Right/.test(placement)) {
-        ret.left += (width - p.width)
-        if (this.arrowPointAtCenter) ret.left -= width / 2 - 20
-      } else if (/(top)|(bottom)/.test(placement)) {
-        ret.left += (width - p.width) / 2
-      }
-      if (/Top/.test(placement)) {
-        if (this.arrowPointAtCenter) ret.top += height / 2 - 14
-      } else if (/Bottom/.test(placement)) {
-        ret.top += (height - p.height)
-        if (this.arrowPointAtCenter) ret.top -= height / 2 - 14
-      } else if (/(left)|(right)/.test(placement)) {
-        ret.top += (height - p.height) / 2
-      }
-      return ret
-    },
-    showNode () {
-      this.mountNode(() => {
-        this.visible = true
-        this.$nextTick(() => {
-          const popup = this.vnode.$el.getBoundingClientRect()
-          const [, scale = 1] = window.getComputedStyle(this.vnode.$el).transform.match(/matrix\((.*?),/) || []
-          const content = this.$el.getBoundingClientRect()
-          const place = this.autoAdjustOverflow ? this.checkPosition(popup, content, this.placement, scale) : this.placement
-          this.realPlacement = place
-          const { left, top } = this.computeOffset(popup, content, place, scale)
-          this.vnode.left = left
-          this.vnode.top = top
-        })
-        this.onPopupAlign(this.realPlacement, this.$el, this.vnode.$el, { offset: [0, 0] })
-      })
-    },
-    hideNode (e) {
-      if (!this.vnode) return
-      if (e.relatedTarget === this.vnode.$el) {
-        return
-      }
-      this.visible = false
-    },
-    checkShow (e) {
-      if (this.t2) {
-        clearTimeout(this.t2)
-        this.t2 = null
-      }
-      if (this.mouseEnterDelay) {
-        this.t1 = window.setTimeout(() => {
-          this.showNode(e)
-        }, +this.mouseEnterDelay * 1e3)
-      }
-    },
-    checkHide (e) {
-      if (this.t1) {
-        clearTimeout(this.t1)
-        this.t1 = null
-      }
-      if (this.mouseLeaveDelay) {
-        this.t2 = window.setTimeout(() => {
-          this.hideNode(e)
-        }, +this.mouseLeaveDelay * 1e3)
-      }
+      domNode.style.transformOrigin = `${transformOrigin.left} ${transformOrigin.top}`
     },
   },
+
   render (h) {
-    const inner = this.$slots.default[0]
-    inner.data = inner.data || {}
-    inner.data.on = inner.data.on || {}
-    inner.data.on.mouseenter = this.addEventHandle(inner.data.on.mouseenter, this.checkShow)
-    inner.data.on.mouseleave = this.addEventHandle(inner.data.on.mouseleave, this.checkHide)
+    const { $props, $data, $slots } = this
+    const { title, prefixCls, openClassName, getPopupContainer, getTooltipContainer } = $props
+    const children = ($slots.default || []).filter(c => c.tag || c.text.trim() !== '')[0]
+    let sVisible = $data.sVisible
+    // Hide tooltip when there is no title
+    if (!hasProp(this, 'visible') && this.isNoTitle()) {
+      sVisible = false
+    }
 
-    return this.$slots.default[0]
-  },
-  updated () {
-    if (!this.vnode) return
-    const popup = this.vnode.$el.getBoundingClientRect()
-    const [, scale = 1] = window.getComputedStyle(this.vnode.$el).transform.match(/matrix\((.*?),/) || []
-    const content = this.$el.getBoundingClientRect()
-    const { left, top } = this.computeOffset(popup, content, this.realPlacement, scale)
-    this.vnode.left = left
-    this.vnode.top = top
-  },
-  beforeDestroy () {
-    if (!this.vnode) return
-    this.vnode.$el.remove()
-    this.vnode.$destroy()
+    const child = this.getDisabledCompatibleChildren(isValidElement(children) ? children : <span>{children}</span>)
+    const childCls = {
+      [openClassName || `${prefixCls}-open`]: true,
+    }
+    const tooltipProps = {
+      props: {
+        ...$props,
+        getTooltipContainer: getPopupContainer || getTooltipContainer,
+        builtinPlacements: this.getPlacements(),
+        visible: sVisible,
+      },
+      ref: 'tooltip',
+      on: {
+        visibleChange: this.onVisibleChange,
+        popupAlign: this.onPopupAlign,
+      },
+    }
+    return (
+      <RcTooltip {...tooltipProps}>
+        <template slot='overlay'>
+          {typeof title === 'function' ? title(h) : title}
+          {$slots.title}
+        </template>
+        {sVisible ? cloneElement(child, { class: childCls }) : child}
+      </RcTooltip>
+    )
   },
 }
 </script>
diff --git a/components/trigger/Popup.vue b/components/trigger/Popup.vue
index 1e68745ad..38a314044 100644
--- a/components/trigger/Popup.vue
+++ b/components/trigger/Popup.vue
@@ -56,11 +56,9 @@ export default {
     onAlign (popupDomNode, align) {
       const props = this.$props
       const currentAlignClassName = props.getClassNameFromAlign(align)
-      // FIX: https://github.com/react-component/trigger/issues/56
-      // FIX: https://github.com/react-component/tooltip/issues/79
       if (this.currentAlignClassName !== currentAlignClassName) {
+        popupDomNode.className = popupDomNode.className.replace(this.currentAlignClassName, currentAlignClassName)
         this.currentAlignClassName = currentAlignClassName
-        popupDomNode.className = this.getClassName(currentAlignClassName)
       }
       this.$emit('align', popupDomNode, align)
     },
@@ -101,14 +99,6 @@ export default {
     onMouseLeave (e) {
       this.$emit('mouseleave', e)
     },
-    beforeEnter (el) {
-      try {
-        // this.$refs.alignInstance && this.$refs.alignInstance.forceAlign()
-      } catch (error) {
-
-      }
-      this.$refs.alignInstance && this.$refs.alignInstance.forceAlign()
-    },
     afterLeave (el) {
       if (this.destroyPopupOnHide) {
         this.destroyPopup = true
@@ -117,8 +107,8 @@ export default {
     getPopupElement () {
       const { $props: props, onMouseEnter, onMouseLeave, $slots } = this
       const { align, visible, prefixCls, animation } = props
-      const className = this.getClassName(this.currentAlignClassName ||
-      props.getClassNameFromAlign(align))
+      this.currentAlignClassName = this.currentAlignClassName || props.getClassNameFromAlign(align)
+      const className = this.getClassName(this.currentAlignClassName)
       // const hiddenClassName = `${prefixCls}-hidden`
       if (!visible) {
         this.currentAlignClassName = null
@@ -144,7 +134,6 @@ export default {
       }
       return (<transition
         {...transitionProps}
-        onBeforeEnter={this.beforeEnter}
         onAfterLeave={this.afterLeave}
       >
         <Align
diff --git a/components/trigger/index.md b/components/trigger/index.md
index 6887d36f5..3d27fb69e 100644
--- a/components/trigger/index.md
+++ b/components/trigger/index.md
@@ -139,14 +139,14 @@
           <td>builtin placement align map. used by placement prop</td>
         </tr>
         <tr>
-          <td>popupVisibleChange</td>
-          <td>$emit(visible)</td>
+          <td>onPopupVisibleChange</td>
+          <td>function</td>
           <td></td>
           <td>call when popup visible is changed</td>
         </tr>
         <tr>
-          <td>popupAlign</td>
-          <td>$emit(popupDomNode, align)</td>
+          <td>onPopupAlign</td>
+          <td>function</td>
           <td></td>
           <td>callback when popup node is aligned</td>
         </tr>
diff --git a/components/trigger/index.vue b/components/trigger/index.vue
index 3313d0aba..a56b5310c 100644
--- a/components/trigger/index.vue
+++ b/components/trigger/index.vue
@@ -5,7 +5,7 @@ import hasProp from '../_util/hasProp'
 import addEventListener from '../_util/Dom/addEventListener'
 import warning from '../_util/warning'
 import Popup from './Popup'
-import { getAlignFromPlacement, getPopupClassNameFromAlign } from './utils'
+import { getAlignFromPlacement, getPopupClassNameFromAlign, noop } from './utils'
 import StateMixin from '../_util/StateMixin'
 import { cloneElement, cloneVNode } from '../_util/vnode'
 
@@ -27,8 +27,8 @@ export default {
     showAction: PropTypes.any.def([]),
     hideAction: PropTypes.any.def([]),
     getPopupClassNameFromAlign: PropTypes.any.def(returnEmptyString),
-    // onPopupVisibleChange: PropTypes.func,
-    // afterPopupVisibleChange: PropTypes.func,
+    // onPopupVisibleChange: PropTypes.func.def(noop),
+    afterPopupVisibleChange: PropTypes.func.def(noop),
     popup: PropTypes.any,
     popupStyle: PropTypes.object.def({}),
     prefixCls: PropTypes.string.def('rc-trigger-popup'),
@@ -51,7 +51,7 @@ export default {
     destroyPopupOnHide: PropTypes.bool.def(false),
     mask: PropTypes.bool.def(false),
     maskClosable: PropTypes.bool.def(true),
-    // onPopupAlign: PropTypes.func,
+    // onPopupAlign: PropTypes.func.def(noop),
     popupAlign: PropTypes.object.def({}),
     popupVisible: PropTypes.bool,
     defaultPopupVisible: PropTypes.bool.def(false),
@@ -96,7 +96,7 @@ export default {
     },
     sPopupVisible (val) {
       this.$nextTick(() => {
-        this.$emit('afterPopupVisibleChange', val)
+        this.afterPopupVisibleChange(val)
       })
     },
   },
@@ -261,7 +261,7 @@ export default {
       return this.$el.children ? this.$el.children[0] : this.$el
     },
 
-    getPopupClassFromAlign (align) {
+    handleGetPopupClassFromAlign (align) {
       const className = []
       const props = this.$props
       const { popupPlacement, builtinPlacements, prefixCls } = props
@@ -285,7 +285,7 @@ export default {
     onPopupAlign () {
       this.$emit('popupAlign', ...arguments)
     },
-    getComponent () {
+    getComponent (h) {
       const mouseProps = {}
       if (this.isMouseEnterToShow()) {
         mouseProps.mouseenter = this.onPopupMouseenter
@@ -295,7 +295,7 @@ export default {
       }
       const { prefixCls, destroyPopupOnHide, sPopupVisible,
         popupStyle, popupClassName, action, onPopupAlign,
-        popupAnimation, getPopupClassFromAlign, getRootDomNode,
+        popupAnimation, handleGetPopupClassFromAlign, getRootDomNode,
         mask, zIndex, popupTransitionName, getPopupAlign,
         maskAnimation, maskTransitionName, popup, $slots, getContainer } = this
       const popupProps = {
@@ -306,7 +306,7 @@ export default {
           action,
           align: getPopupAlign(),
           animation: popupAnimation,
-          getClassNameFromAlign: getPopupClassFromAlign,
+          getClassNameFromAlign: handleGetPopupClassFromAlign,
           getRootDomNode,
           mask,
           zIndex,
@@ -328,7 +328,7 @@ export default {
           {...popupProps}
           ref='popup'
         >
-          {typeof popup === 'function' ? popup() : popup}
+          {typeof popup === 'function' ? popup(h) : popup}
           {popup === undefined ? $slots.popup : null}
         </Popup>
       )
@@ -472,7 +472,7 @@ export default {
       this.setPopupVisible(false)
     },
   },
-  render () {
+  render (h) {
     const children = this.$slots.default
     if (children.length > 1) {
       warning(false, 'Trigger $slots.default.length > 1, just support only one default', true)
@@ -521,7 +521,7 @@ export default {
     const trigger = cloneElement(cloneVNode(child), newChildProps)
     const { sPopupVisible, forceRender } = this
     if (sPopupVisible || forceRender || this._component) {
-      this._component = this.getComponent()
+      this._component = this.getComponent(h)
     } else {
       this._component = null
     }
diff --git a/components/trigger/utils.js b/components/trigger/utils.js
index e2af70378..eddd8178f 100644
--- a/components/trigger/utils.js
+++ b/components/trigger/utils.js
@@ -21,7 +21,5 @@ export function getPopupClassNameFromAlign (builtinPlacements, prefixCls, align)
   }
   return ''
 }
-
-export function saveRef (name, component) {
-  this[name] = component
+export function noop () {
 }
diff --git a/examples/index.less b/examples/index.less
index 89a9f27d5..ac6ec4a44 100644
--- a/examples/index.less
+++ b/examples/index.less
@@ -1,3 +1,3 @@
-.icon-test{
-  font-size: 35px;
+#app {
+  padding: 50px;
 }
diff --git a/examples/md.vue b/examples/md.vue
index 9dcad410f..c907d5dda 100644
--- a/examples/md.vue
+++ b/examples/md.vue
@@ -1,5 +1,5 @@
 <template>
-  <div v-html="marked($slots.default[0].text.trim() || '')" />
+  <div style="padding: 10px 0;" v-html="marked($slots.default[0].text.trim() || '')" />
 </template>
 <script>
 import marked from 'marked'
@@ -16,7 +16,6 @@ marked.setOptions({
 export default {
   name: 'md',
   data () {
-    console.log(this.$slots.default)
     return {
       marked,
     }

From 0d217db98410bd7f03dac5cadc3c84dfd2c41576 Mon Sep 17 00:00:00 2001
From: tangjinzhou <415800467@qq.com>
Date: Thu, 11 Jan 2018 18:58:58 +0800
Subject: [PATCH 3/7] fix md

---
 components/trigger/index.md | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/components/trigger/index.md b/components/trigger/index.md
index 3d27fb69e..69f9cb643 100644
--- a/components/trigger/index.md
+++ b/components/trigger/index.md
@@ -139,14 +139,14 @@
           <td>builtin placement align map. used by placement prop</td>
         </tr>
         <tr>
-          <td>onPopupVisibleChange</td>
-          <td>function</td>
+          <td>popupVisibleChange</td>
+          <td>$emit</td>
           <td></td>
           <td>call when popup visible is changed</td>
         </tr>
         <tr>
-          <td>onPopupAlign</td>
-          <td>function</td>
+          <td>popupAlign</td>
+          <td>$emit</td>
           <td></td>
           <td>callback when popup node is aligned</td>
         </tr>

From 21d84837b5dc572da8f63123a22d3810000b6095 Mon Sep 17 00:00:00 2001
From: tangjinzhou <415800467@qq.com>
Date: Thu, 11 Jan 2018 19:00:12 +0800
Subject: [PATCH 4/7] fix md

---
 contributors.md | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/contributors.md b/contributors.md
index 01194ee13..bedada84f 100644
--- a/contributors.md
+++ b/contributors.md
@@ -7,6 +7,9 @@ Checkbox  |  done
 Radio  |  done
 Tabs | done
 Tag | done
+ToolTip | done
+Popconfirm
+Popover
 Carousel
 Mention
 Input | done |select完成后补全demo
@@ -22,7 +25,6 @@ TimePicker
 ##万
 Grid
 Col
-ToolTip
 Affix
 Alert
 BackTop
@@ -32,8 +34,6 @@ message
 Menu
 Modal
 notification
-Popconfirm
-Popover
 Anchor
 Tree
 TreeSelect

From 728e26548fca7702e65626367a8cce61dee04859 Mon Sep 17 00:00:00 2001
From: tangjinzhou <415800467@qq.com>
Date: Fri, 12 Jan 2018 16:10:41 +0800
Subject: [PATCH 5/7] add popover

---
 components/_util/BaseMixin.js                 |  16 ++
 components/_util/props-util.js                |  22 ++
 components/align/Align.vue                    |   3 +-
 components/align/demo/simple.vue              |   4 +-
 components/badge/ScrollNumber.vue             |   4 +-
 components/checkbox/Checkbox.vue              |   2 +-
 components/checkbox/Group.vue                 |   2 +-
 components/index.js                           |   2 +
 components/input/Input.vue                    |   2 +-
 components/input/TextArea.vue                 |   2 +-
 components/menu/src/Menu.vue                  |  14 +-
 components/menu/src/MenuItem.vue              |  37 ++-
 components/menu/src/MenuMixin.js              |   2 +-
 components/menu/src/SubMenu.vue               |  52 ++---
 components/menu/src/SubPopupMenu.vue          |  14 +-
 components/popconfirm/index.vue               |   0
 components/popconfirm/index.zh-CN.md          |  34 +++
 components/popconfirm/style/index.js          |   5 +
 .../popover/demo/arrow-point-at-center.vue    |  42 ++++
 components/popover/demo/basic.vue             |  27 +++
 components/popover/demo/control.vue           |  39 ++++
 components/popover/demo/index.vue             |  30 +++
 components/popover/demo/placement.vue         | 184 +++++++++++++++
 components/popover/demo/triggerType.vue       |  45 ++++
 components/popover/index.vue                  |  67 ++++++
 components/popover/index.zh-CN.md             |  27 +++
 components/popover/style/index.js             |   2 +
 components/popover/style/index.less           | 221 ++++++++++++++++++
 components/radio/Radio.vue                    |   2 +-
 components/rate/Rate.vue                      |   2 +-
 components/style.js                           |   1 +
 components/tabs/Tabs.vue                      |   2 +-
 components/tabs/index.vue                     |   2 +-
 components/tooltip/abstractTooltipProps.js    |  19 ++
 components/tooltip/index.zh-CN.md             |  45 ++++
 components/tooltip/src/Tooltip.vue            |  17 +-
 components/tooltip/tooltip.vue                |  30 +--
 components/trigger/Popup.vue                  |  16 +-
 components/trigger/PopupInner.vue             |  18 +-
 components/trigger/index.vue                  |  15 +-
 contributors.md                               |   2 +-
 41 files changed, 923 insertions(+), 149 deletions(-)
 create mode 100644 components/_util/BaseMixin.js
 create mode 100644 components/_util/props-util.js
 create mode 100644 components/popconfirm/index.vue
 create mode 100644 components/popconfirm/index.zh-CN.md
 create mode 100644 components/popconfirm/style/index.js
 create mode 100644 components/popover/demo/arrow-point-at-center.vue
 create mode 100644 components/popover/demo/basic.vue
 create mode 100644 components/popover/demo/control.vue
 create mode 100644 components/popover/demo/index.vue
 create mode 100644 components/popover/demo/placement.vue
 create mode 100644 components/popover/demo/triggerType.vue
 create mode 100644 components/popover/index.vue
 create mode 100644 components/popover/index.zh-CN.md
 create mode 100644 components/popover/style/index.js
 create mode 100644 components/popover/style/index.less
 create mode 100644 components/tooltip/abstractTooltipProps.js
 create mode 100644 components/tooltip/index.zh-CN.md

diff --git a/components/_util/BaseMixin.js b/components/_util/BaseMixin.js
new file mode 100644
index 000000000..66d6bfa94
--- /dev/null
+++ b/components/_util/BaseMixin.js
@@ -0,0 +1,16 @@
+export default {
+  methods: {
+    setState (state, callback) {
+      Object.assign(this.$data, state)
+      this.$nextTick(() => {
+        callback && callback()
+      })
+    },
+    __emit () { // 直接调用listeners,底层组件不需要vueTool记录events
+      const args = [].slice.call(arguments, 0)
+      if (args.length && this.$listeners[args[0]]) {
+        this.$listeners[args[0]](...args.slice(1))
+      }
+    },
+  },
+}
diff --git a/components/_util/props-util.js b/components/_util/props-util.js
new file mode 100644
index 000000000..d98722fc2
--- /dev/null
+++ b/components/_util/props-util.js
@@ -0,0 +1,22 @@
+const hasProp = (instance, prop) => {
+  const $options = instance.$options || {}
+  const propsData = $options.propsData || {}
+  return prop in propsData
+}
+const filterProps = (props, propsData = {}) => {
+  const res = {}
+  Object.keys(props).forEach((k) => {
+    if (k in propsData || props[k] !== undefined) {
+      res[k] = props[k]
+    }
+  })
+  return res
+}
+
+const getOptionProps = (instance) => {
+  const { $options = {}, $props = {}} = instance
+  return filterProps($props, $options.propsData)
+}
+
+export { hasProp, filterProps, getOptionProps }
+export default hasProp
diff --git a/components/align/Align.vue b/components/align/Align.vue
index 36f1d9e6a..f4cdf0bb1 100644
--- a/components/align/Align.vue
+++ b/components/align/Align.vue
@@ -102,7 +102,8 @@ export default {
       const props = this.$props
       if (!props.disabled) {
         const source = this.$el
-        this.$emit('align', source, align(source, props.target(), props.align))
+        // this.$emit('align', source, align(source, props.target(), props.align))
+        this.$listeners.align && this.$listeners.align(source, align(source, props.target(), props.align))
       }
     },
   },
diff --git a/components/align/demo/simple.vue b/components/align/demo/simple.vue
index 84b11f40f..7c5c991e2 100644
--- a/components/align/demo/simple.vue
+++ b/components/align/demo/simple.vue
@@ -1,9 +1,9 @@
 <script>
 import Align from '../index'
-import StateMixin from '../../_util/StateMixin'
+import BaseMixin from '../../_util/BaseMixin'
 
 export default {
-  mixins: [StateMixin],
+  mixins: [BaseMixin],
   data () {
     return {
       monitor: true,
diff --git a/components/badge/ScrollNumber.vue b/components/badge/ScrollNumber.vue
index 3bd878912..b81832e98 100644
--- a/components/badge/ScrollNumber.vue
+++ b/components/badge/ScrollNumber.vue
@@ -1,5 +1,5 @@
 <script>
-import StateMixin from '../_util/StateMixin'
+import BaseMixin from '../_util/BaseMixin'
 
 function getNumberArray (num) {
   return num
@@ -20,7 +20,7 @@ export default {
       default: () => ({}),
     },
   },
-  mixins: [StateMixin],
+  mixins: [BaseMixin],
   data () {
     const { count } = this
     return {
diff --git a/components/checkbox/Checkbox.vue b/components/checkbox/Checkbox.vue
index 7606af73c..047e7cc45 100644
--- a/components/checkbox/Checkbox.vue
+++ b/components/checkbox/Checkbox.vue
@@ -13,7 +13,7 @@
   </label>
 </template>
 <script>
-import hasProp from '../_util/hasProp'
+import hasProp from '../_util/props-util'
 export default {
   name: 'Checkbox',
   props: {
diff --git a/components/checkbox/Group.vue b/components/checkbox/Group.vue
index b05228124..62de663fe 100644
--- a/components/checkbox/Group.vue
+++ b/components/checkbox/Group.vue
@@ -8,7 +8,7 @@
 </template>
 <script>
 import Checkbox from './Checkbox.vue'
-import hasProp from '../_util/hasProp'
+import hasProp from '../_util/props-util'
 export default {
   name: 'CheckboxGroup',
   props: {
diff --git a/components/index.js b/components/index.js
index a9981c3ec..3b8f554d7 100644
--- a/components/index.js
+++ b/components/index.js
@@ -27,3 +27,5 @@ export { default as Badge } from './badge'
 export { default as Tabs } from './tabs'
 
 export { default as Input } from './input'
+
+export { default as Popover } from './popover'
diff --git a/components/input/Input.vue b/components/input/Input.vue
index 9949af8af..8d4e9f494 100644
--- a/components/input/Input.vue
+++ b/components/input/Input.vue
@@ -2,7 +2,7 @@
 import TextArea from './TextArea'
 import omit from 'omit.js'
 import inputProps from './inputProps'
-import hasProp from '../_util/hasProp'
+import hasProp from '../_util/props-util'
 
 function fixControlledValue (value) {
   if (typeof value === 'undefined' || value === null) {
diff --git a/components/input/TextArea.vue b/components/input/TextArea.vue
index 3ff8d72f5..93118e055 100644
--- a/components/input/TextArea.vue
+++ b/components/input/TextArea.vue
@@ -2,7 +2,7 @@
 import omit from 'omit.js'
 import inputProps from './inputProps'
 import calculateNodeHeight from './calculateNodeHeight'
-import hasProp from '../_util/hasProp'
+import hasProp from '../_util/props-util'
 
 function onNextFrame (cb) {
   if (window.requestAnimationFrame) {
diff --git a/components/menu/src/Menu.vue b/components/menu/src/Menu.vue
index 1b841f446..739d23992 100644
--- a/components/menu/src/Menu.vue
+++ b/components/menu/src/Menu.vue
@@ -1,8 +1,8 @@
 <script>
 import PropTypes from '../../_util/vue-types'
 import MenuMixin from './MenuMixin'
-import StateMixin from '../../_util/StateMixin'
-import hasProp from '../../_util/hasProp'
+import BaseMixin from '../../_util/BaseMixin'
+import hasProp from '../../_util/props-util'
 import commonPropsType from './commonPropsType'
 
 const Menu = {
@@ -14,7 +14,7 @@ const Menu = {
     selectable: PropTypes.bool.def(true),
     ...commonPropsType,
   },
-  mixins: [StateMixin, MenuMixin],
+  mixins: [BaseMixin, MenuMixin],
 
   data () {
     const props = this.$props
@@ -79,7 +79,7 @@ const Menu = {
             sSelectedKeys,
           })
         }
-        this.$emit('select', {
+        this.__emit('select', {
           ...selectInfo,
           sSelectedKeys,
         })
@@ -87,7 +87,7 @@ const Menu = {
     },
 
     onClick (e) {
-      this.$emit('click', e)
+      this.__emit('click', e)
     },
 
     onOpenChange (e_) {
@@ -119,7 +119,7 @@ const Menu = {
         if (!hasProp(this, 'openKeys')) {
           this.setState({ sOpenKeys })
         }
-        this.$emit('openChange', sOpenKeys)
+        this.__emit('openChange', sOpenKeys)
       }
     },
 
@@ -137,7 +137,7 @@ const Menu = {
             sSelectedKeys,
           })
         }
-        this.$emit('deselect', {
+        this.__emit('deselect', {
           ...selectInfo,
           sSelectedKeys,
         })
diff --git a/components/menu/src/MenuItem.vue b/components/menu/src/MenuItem.vue
index 33aa3cce6..8b7c4d27f 100644
--- a/components/menu/src/MenuItem.vue
+++ b/components/menu/src/MenuItem.vue
@@ -2,7 +2,7 @@
 import PropTypes from '../../_util/vue-types'
 import KeyCode from '../../_util/KeyCode'
 import { noop } from './util'
-import StateMixin from '../../_util/StateMixin'
+import BaseMixin from '../../_util/BaseMixin'
 
 const MenuItem = {
   name: 'MenuItem',
@@ -18,41 +18,40 @@ const MenuItem = {
     inlineIndent: PropTypes.number.def(24),
     level: PropTypes.number.def(1),
     mode: PropTypes.oneOf(['horizontal', 'vertical', 'vertical-left', 'vertical-right', 'inline']).def('vertical'),
-    // onItemHover: PropTypes.func,
-    // onSelect: PropTypes.func,
-    // onClick: PropTypes.func,
-    // onDeselect: PropTypes.func,
     parentMenu: PropTypes.object,
-    // onDestroy: PropTypes.func,
-    // onMouseEnter: PropTypes.func,
-    // onMouseLeave: PropTypes.func,
     clearSubMenuTimers: PropTypes.func.def(noop),
   },
   inject: {
     parentMenuContext: { default: undefined },
   },
-  mixins: [StateMixin],
+  mixins: [BaseMixin],
   isMenuItem: true,
   beforeDestroy () {
     const props = this.$props
-    this.$emit('destroy', props.eventKey)
+    this.__emit('destroy', props.eventKey)
   },
   methods: {
+    __emit () { // 直接调用listeners,底层组件不需要vueTool记录events
+      const args = [].slice.call(arguments, 0)
+      if (args.length && this.$listeners[args[0]]) {
+        this.$listeners[args[0]](...args.slice(1))
+      }
+    },
     onKeyDown (e) {
       const keyCode = e.keyCode
       if (keyCode === KeyCode.ENTER) {
-        this.$emit('click', e)
+        this.__emit('click', e)
         return true
       }
     },
 
     onMouseLeave (e) {
       const { eventKey } = this.$props
-      this.$emit('itemHover', {
+      this.__emit('itemHover', {
         key: eventKey,
         hover: false,
       })
-      this.$emit('mouseLeave', {
+      this.__emit('mouseleave', {
         key: eventKey,
         domEvent: e,
       })
@@ -63,11 +62,11 @@ const MenuItem = {
       if (parentMenuContext && parentMenuContext.subMenuInstance) {
         parentMenuContext.subMenuInstance.clearSubMenuTimers()
       }
-      this.$emit('itemHover', {
+      this.__emit('itemHover', {
         key: eventKey,
         hover: true,
       })
-      this.$emit('mouseEnter', {
+      this.__emit('mouseenter', {
         key: eventKey,
         domEvent: e,
       })
@@ -82,15 +81,15 @@ const MenuItem = {
         item: this,
         domEvent: e,
       }
-      this.$emit('click', info)
+      this.__emit('click', info)
       if (multiple) {
         if (selected) {
-          this.$emit('deselect', info)
+          this.__emit('deselect', info)
         } else {
-          this.$emit('select', info)
+          this.__emit('select', info)
         }
       } else if (!selected) {
-        this.$emit('select', info)
+        this.__emit('select', info)
       }
     },
 
diff --git a/components/menu/src/MenuMixin.js b/components/menu/src/MenuMixin.js
index 2b3909954..8250e105b 100644
--- a/components/menu/src/MenuMixin.js
+++ b/components/menu/src/MenuMixin.js
@@ -1,4 +1,4 @@
-import hasProp from '../../_util/hasProp'
+import hasProp from '../../_util/props-util'
 import KeyCode from '../../_util/KeyCode'
 import scrollIntoView from 'dom-scroll-into-view'
 import { getKeyFromChildrenIndex, loopMenuItem } from './util'
diff --git a/components/menu/src/SubMenu.vue b/components/menu/src/SubMenu.vue
index 293adb56c..071999cac 100644
--- a/components/menu/src/SubMenu.vue
+++ b/components/menu/src/SubMenu.vue
@@ -5,7 +5,7 @@ import KeyCode from '../../_util/KeyCode'
 import SubPopupMenu from './SubPopupMenu'
 import placements from './placements'
 import { loopMenuItemRecusively, noop } from './util'
-import StateMixin from '../../_util/StateMixin'
+import BaseMixin from '../../_util/BaseMixin'
 
 let guid = 0
 
@@ -45,18 +45,11 @@ export default {
     subMenuCloseDelay: PropTypes.number.def(0.1),
     level: PropTypes.number.def(1),
     inlineIndent: PropTypes.number.def(24),
-    // onDeselect: PropTypes.func,
-    // onDestroy: PropTypes.func,
-    // onMouseEnter: PropTypes.func,
-    // onMouseLeave: PropTypes.func,
-    // onTitleMouseEnter: PropTypes.func,
-    // onTitleMouseLeave: PropTypes.func,
-    // onTitleClick: PropTypes.func,
   },
   inject: {
     parentMenuContext: { default: undefined },
   },
-  mixins: [StateMixin],
+  mixins: [BaseMixin],
   isSubMenu: true,
   data () {
     return {
@@ -73,7 +66,7 @@ export default {
 
   beforeDestroy () {
     const { eventKey, parentMenuContext } = this
-    this.$emit('destroy', eventKey)
+    this.__emit('destroy', eventKey)
     if (parentMenuContext.subMenuInstance === this) {
       this.clearSubMenuTimers()
     }
@@ -95,9 +88,6 @@ export default {
         popupMenu.style.minWidth = `${this.subMenuTitle.offsetWidth}px`
       }, 0)
     },
-    onDestroy (key) {
-      this.$emit('destroy', key)
-    },
 
     onKeyDown (e) {
       const keyCode = e.keyCode
@@ -142,10 +132,6 @@ export default {
       }
     },
 
-    onOpenChange (e) {
-      this.$emit('openChange', e)
-    },
-
     onPopupVisibleChange (visible) {
       this.triggerOpenChange(visible, visible ? 'mouseenter' : 'mouseleave')
     },
@@ -156,7 +142,7 @@ export default {
       this.setState({
         defaultActiveFirst: false,
       })
-      this.$emit('mouseenter', {
+      this.__emit('mouseenter', {
         key,
         domEvent: e,
       })
@@ -170,7 +156,7 @@ export default {
       parentMenuContext.subMenuInstance = this
       parentMenuContext.subMenuLeaveFn = () => {
       // trigger mouseleave
-        this.$emit('mouseleave', {
+        this.__emit('mouseleave', {
           key: eventKey,
           domEvent: e,
         })
@@ -182,11 +168,11 @@ export default {
     onTitleMouseEnter (domEvent) {
       const { eventKey: key } = this.$props
       this.clearSubMenuTitleLeaveTimer()
-      this.$emit('itemHover', {
+      this.__emit('itemHover', {
         key,
         hover: true,
       })
-      this.$emit('titleMouseenter', {
+      this.__emit('titleMouseenter', {
         key,
         domEvent,
       })
@@ -196,11 +182,11 @@ export default {
       const { eventKey, parentMenuContext } = this
       parentMenuContext.subMenuInstance = this
       parentMenuContext.subMenuTitleLeaveFn = () => {
-        this.$emit('itemHover', {
+        this.__emit('itemHover', {
           key: eventKey,
           hover: false,
         })
-        this.$emit('titleMouseleave', {
+        this.__emit('titleMouseleave', {
           key: eventKey,
           domEvent: e,
         })
@@ -211,7 +197,7 @@ export default {
     onTitleClick (e) {
       const { triggerSubMenuAction, eventKey } = this.$props
 
-      this.$emit('itemClick', {
+      this.__emit('itemClick', {
         key: eventKey,
         domEvent: e,
         test: 111,
@@ -226,15 +212,7 @@ export default {
     },
 
     onSubMenuClick (info) {
-      this.$emit('click', this.addKeyPath(info))
-    },
-
-    onSelect (info) {
-      this.$emit('select', info)
-    },
-
-    onDeselect (info) {
-      this.$emit('deselect', info)
+      this.__emit('click', this.addKeyPath(info))
     },
 
     getPrefixCls () {
@@ -266,7 +244,7 @@ export default {
 
     triggerOpenChange (open, type) {
       const key = this.$props.eventKey
-      this.onOpenChange({
+      this.__emit('openChange', {
         key,
         item: this,
         trigger: type,
@@ -309,6 +287,7 @@ export default {
     renderChildren (children, vShow) {
       const props = this.$props
       const isOpen = this.isOpen()
+      const { select, deselect, destroy, openChange } = this.$listeners
       const subPopupMenuProps = {
         props: {
           mode: props.mode === 'horizontal' ? 'vertical' : props.mode,
@@ -332,10 +311,7 @@ export default {
         },
         on: {
           click: this.onSubMenuClick,
-          select: this.onSelect,
-          deselect: this.onDeselect,
-          destroy: this.onDestroy,
-          openChange: this.onOpenChange,
+          select, deselect, destroy, openChange,
         },
         id: this._menuId,
         ref: 'menuInstance',
diff --git a/components/menu/src/SubPopupMenu.vue b/components/menu/src/SubPopupMenu.vue
index 03072fbc5..fda6314b3 100644
--- a/components/menu/src/SubPopupMenu.vue
+++ b/components/menu/src/SubPopupMenu.vue
@@ -1,7 +1,7 @@
 <script>
 import PropTypes from '../../_util/vue-types'
 import MenuMixin from './MenuMixin'
-import StateMixin from '../../_util/StateMixin'
+import BaseMixin from '../../_util/BaseMixin'
 import commonPropsType from './commonPropsType'
 import { noop } from './util'
 export default {
@@ -10,26 +10,26 @@ export default {
     clearSubMenuTimers: PropTypes.func.def(noop),
   },
 
-  mixins: [MenuMixin, StateMixin],
+  mixins: [MenuMixin, BaseMixin],
   methods: {
     onDeselect (selectInfo) {
-      this.$emit('deselect', selectInfo)
+      this.__emit('deselect', selectInfo)
     },
 
     onSelect (selectInfo) {
-      this.$emit('select', selectInfo)
+      this.__emit('select', selectInfo)
     },
 
     onClick (e) {
-      this.$emit('click', e)
+      this.__emit('click', e)
     },
 
     onOpenChange (e) {
-      this.$emit('openChange', e)
+      this.__emit('openChange', e)
     },
 
     onDestroy (key) {
-      this.$emit('destroy', key)
+      this.__emit('destroy', key)
     },
 
     getOpenTransitionName () {
diff --git a/components/popconfirm/index.vue b/components/popconfirm/index.vue
new file mode 100644
index 000000000..e69de29bb
diff --git a/components/popconfirm/index.zh-CN.md b/components/popconfirm/index.zh-CN.md
new file mode 100644
index 000000000..bbdf06f3a
--- /dev/null
+++ b/components/popconfirm/index.zh-CN.md
@@ -0,0 +1,34 @@
+---
+category: Components
+subtitle: 气泡确认框
+type: Feedback
+title: Popconfirm
+---
+
+点击元素,弹出气泡式的确认框。
+
+## 何时使用
+
+目标元素的操作需要用户进一步的确认时,在目标元素附近弹出浮层提示,询问用户。
+
+和 `confirm` 弹出的全屏居中模态对话框相比,交互形式更轻量。
+
+## API
+
+| 参数 | 说明 | 类型 | 默认值 |
+| --- | --- | --- | --- |
+| cancelText | 取消按钮文字 | string | 取消 |
+| okText | 确认按钮文字 | string | 确定 |
+| okType | 确认按钮类型 | string | primary |
+| title | 确认框的描述 | string\|function\|slot | 无 |
+
+### 事件
+| 事件名称 | 说明 | 回调参数 |
+| cancel | 点击取消时触发 | (e) |
+| confirm | 点击确认时触发 | (e) |
+
+更多属性请参考 [Tooltip](/components/tooltip/#API)。
+
+## 注意
+
+请确保 `Popconfirm` 的子元素能接受 `mouseenter`、`mouseleave`、`focus`、`click` 事件。
diff --git a/components/popconfirm/style/index.js b/components/popconfirm/style/index.js
new file mode 100644
index 000000000..a177f207f
--- /dev/null
+++ b/components/popconfirm/style/index.js
@@ -0,0 +1,5 @@
+import '../../style/index.less'
+
+// style dependencies
+import '../../popover/style'
+import '../../button/style'
diff --git a/components/popover/demo/arrow-point-at-center.vue b/components/popover/demo/arrow-point-at-center.vue
new file mode 100644
index 000000000..096f076f5
--- /dev/null
+++ b/components/popover/demo/arrow-point-at-center.vue
@@ -0,0 +1,42 @@
+<template>
+<div>
+<md>
+## 箭头指向
+设置了 `arrowPointAtCenter` 后,箭头将指向目标元素的中心。
+</md>
+  <Popover placement="topLeft">
+    <template slot="content">
+      <div>
+        <p>Content</p>
+        <p>Content</p>
+      </div>
+    </template>
+    <template slot="title">
+      <span>Title</span>
+    </template>
+    <AntButton>Align edge / 边缘对齐</AntButton>
+  </Popover>
+  <Popover placement="topLeft" arrowPointAtCenter>
+    <template slot="content">
+      <div>
+        <p>Content</p>
+        <p>Content</p>
+      </div>
+    </template>
+    <template slot="title">
+      <span>Title</span>
+    </template>
+    <AntButton>Arrow points to center / 箭头指向中心</AntButton>
+  </Popover>
+</div>
+</template>
+
+<script>
+import { Popover, Button } from 'antd'
+export default {
+  components: {
+    Popover,
+    AntButton: Button,
+  },
+}
+</script>
diff --git a/components/popover/demo/basic.vue b/components/popover/demo/basic.vue
new file mode 100644
index 000000000..20cb76fd3
--- /dev/null
+++ b/components/popover/demo/basic.vue
@@ -0,0 +1,27 @@
+<template>
+<div>
+<md>
+## 基本
+最简单的用法,浮层的大小由内容区域决定。
+</md>
+<Popover title="Title">
+    <template slot="content">
+      <div>
+        <p>Content</p>
+        <p>Content</p>
+      </div>
+    </template>
+    <AntButton type="primary">Hover me</AntButton>
+  </Popover>
+</div>
+</template>
+
+<script>
+import { Popover, Button } from 'antd'
+export default {
+  components: {
+    Popover,
+    AntButton: Button,
+  },
+}
+</script>
diff --git a/components/popover/demo/control.vue b/components/popover/demo/control.vue
new file mode 100644
index 000000000..8d8fa9b47
--- /dev/null
+++ b/components/popover/demo/control.vue
@@ -0,0 +1,39 @@
+<template>
+<div>
+  <md>
+  ## 从浮层内关闭
+  使用 `visible` 属性控制浮层显示。
+  </md>
+  <Popover
+    title="Title"
+    trigger="click"
+    v-model="visible"
+  >
+    <template slot="content">
+      <a @click="hide">Close</a>
+    </template>
+    <AntButton type="primary">Click me</AntButton>
+  </Popover>
+</div>
+</template>
+
+<script>
+import { Popover, Button } from 'antd'
+export default {
+  data () {
+    return {
+      visible: false,
+    }
+  },
+  methods: {
+    hide () {
+      console.log(111)
+      this.visible = false
+    },
+  },
+  components: {
+    Popover,
+    AntButton: Button,
+  },
+}
+</script>
diff --git a/components/popover/demo/index.vue b/components/popover/demo/index.vue
new file mode 100644
index 000000000..d6c50e639
--- /dev/null
+++ b/components/popover/demo/index.vue
@@ -0,0 +1,30 @@
+<template>
+  <div>
+    <h1>Basic</h1>
+    <Basic />
+    <h1>ArrowCenter</h1>
+    <ArrowCenter />
+    <h1>Control</h1>
+    <Control />
+    <h1>Placement</h1>
+    <Placement />
+    <h1>TriggerType</h1>
+    <TriggerType />
+  </div>
+</template>
+<script>
+import Basic from './basic'
+import ArrowCenter from './arrow-point-at-center'
+import Control from './control'
+import Placement from './placement'
+import TriggerType from './triggerType'
+export default {
+  components: {
+    Basic,
+    ArrowCenter,
+    Control,
+    Placement,
+    TriggerType,
+  },
+}
+</script>
diff --git a/components/popover/demo/placement.vue b/components/popover/demo/placement.vue
new file mode 100644
index 000000000..2c80b16c6
--- /dev/null
+++ b/components/popover/demo/placement.vue
@@ -0,0 +1,184 @@
+<template>
+<div id="components-popover-demo-placement">
+<md>
+## 位置
+位置有 12 个方向。
+</md>
+  <div :style="{ marginLeft: `${buttonWidth}px`, whiteSpace: 'nowrap' }">
+    <Popover placement="topLeft">
+      <template slot="content">
+        <div>
+          <p>Content</p>
+          <p>Content</p>
+        </div>
+      </template>
+      <template slot="title">
+        <span>Title</span>
+      </template>
+      <AntButton>TL</AntButton>
+    </Popover>
+    <Popover placement="top">
+      <template slot="content">
+        <div>
+          <p>Content</p>
+          <p>Content</p>
+        </div>
+      </template>
+      <template slot="title">
+        <span>Title</span>
+      </template>
+      <AntButton>Top</AntButton>
+    </Popover>
+    <Popover placement="topRight">
+      <template slot="content">
+        <div>
+          <p>Content</p>
+          <p>Content</p>
+        </div>
+      </template>
+      <template slot="title">
+        <span>Title</span>
+      </template>
+      <AntButton>TR</AntButton>
+    </Popover>
+  </div>
+  <div :style="{ width: `${buttonWidth}px`, float: 'left' }">
+    <Popover placement="leftTop">
+      <template slot="content">
+        <div>
+          <p>Content</p>
+          <p>Content</p>
+        </div>
+      </template>
+      <template slot="title">
+        <span>Title</span>
+      </template>
+      <AntButton>LT</AntButton>
+    </Popover>
+    <Popover placement="left">
+      <template slot="content">
+        <div>
+          <p>Content</p>
+          <p>Content</p>
+        </div>
+      </template>
+      <template slot="title">
+        <span>Title</span>
+      </template>
+      <AntButton>Left</AntButton>
+    </Popover>
+    <Popover placement="leftBottom">
+      <template slot="content">
+        <div>
+          <p>Content</p>
+          <p>Content</p>
+        </div>
+      </template>
+      <template slot="title">
+        <span>Title</span>
+      </template>
+      <AntButton>LB</AntButton>
+    </Popover>
+  </div>
+  <div :style="{ width: `${buttonWidth}px`, marginLeft: `${buttonWidth * 4 + 24 }px`}">
+    <Popover placement="rightTop">
+      <template slot="content">
+        <div>
+          <p>Content</p>
+          <p>Content</p>
+        </div>
+      </template>
+      <template slot="title">
+        <span>Title</span>
+      </template>
+      <AntButton>RT</AntButton>
+    </Popover>
+    <Popover placement="right">
+      <template slot="content">
+        <div>
+          <p>Content</p>
+          <p>Content</p>
+        </div>
+      </template>
+      <template slot="title">
+        <span>Title</span>
+      </template>
+      <AntButton>Right</AntButton>
+    </Popover>
+    <Popover placement="rightBottom">
+      <template slot="content">
+        <div>
+          <p>Content</p>
+          <p>Content</p>
+        </div>
+      </template>
+      <template slot="title">
+        <span>Title</span>
+      </template>
+      <AntButton>RB</AntButton>
+    </Popover>
+  </div>
+  <div :style="{ marginLeft: `${buttonWidth}px`, clear: 'both', whiteSpace: 'nowrap' }">
+    <Popover placement="bottomLeft">
+      <template slot="content">
+        <div>
+          <p>Content</p>
+          <p>Content</p>
+        </div>
+      </template>
+      <template slot="title">
+        <span>Title</span>
+      </template>
+      <AntButton>BL</AntButton>
+    </Popover>
+    <Popover placement="bottom">
+      <template slot="content">
+        <div>
+          <p>Content</p>
+          <p>Content</p>
+        </div>
+      </template>
+      <template slot="title">
+        <span>Title</span>
+      </template>
+      <AntButton>Bottom</AntButton>
+    </Popover>
+    <Popover placement="bottomRight">
+      <template slot="content">
+        <div>
+          <p>Content</p>
+          <p>Content</p>
+        </div>
+      </template>
+      <template slot="title">
+        <span>Title</span>
+      </template>
+      <AntButton>BR</AntButton>
+    </Popover>
+  </div>
+</div>
+</template>
+
+<script>
+import { Popover, Button } from 'antd'
+export default {
+  data () {
+    return {
+      buttonWidth: 70,
+    }
+  },
+  components: {
+    Popover,
+    AntButton: Button,
+  },
+}
+</script>
+<style>
+#components-popover-demo-placement .ant-btn {
+  width: 70px;
+  text-align: center;
+  padding: 0;
+  margin-right: 8px;
+  margin-bottom: 8px;
+}
+</style>
diff --git a/components/popover/demo/triggerType.vue b/components/popover/demo/triggerType.vue
new file mode 100644
index 000000000..bbf14fb98
--- /dev/null
+++ b/components/popover/demo/triggerType.vue
@@ -0,0 +1,45 @@
+<template>
+<div>
+<md>
+## 三种触发方式
+鼠标移入、聚集、点击。
+</md>
+  <Popover title="Title" trigger="hover">
+    <template slot="content">
+      <div>
+        <p>Content</p>
+        <p>Content</p>
+      </div>
+    </template>
+    <AntButton type="primary">Hover me</AntButton>
+  </Popover>
+  <Popover title="Title" trigger="focus">
+    <template slot="content">
+      <div>
+        <p>Content</p>
+        <p>Content</p>
+      </div>
+    </template>
+    <AntButton type="primary">Focus me</AntButton>
+  </Popover>
+  <Popover title="Title" trigger="click">
+    <template slot="content">
+      <div>
+        <p>Content</p>
+        <p>Content</p>
+      </div>
+    </template>
+    <AntButton type="primary">Click me</AntButton>
+  </Popover>
+</div>
+</template>
+
+<script>
+import { Popover, Button } from 'antd'
+export default {
+  components: {
+    Popover,
+    AntButton: Button,
+  },
+}
+</script>
diff --git a/components/popover/index.vue b/components/popover/index.vue
new file mode 100644
index 000000000..da31a1806
--- /dev/null
+++ b/components/popover/index.vue
@@ -0,0 +1,67 @@
+<script>
+import Tooltip from '../tooltip'
+import abstractTooltipProps from '../tooltip/abstractTooltipProps'
+import PropTypes from '../_util/vue-types'
+import { getOptionProps } from '../_util/props-util'
+
+export default {
+  name: 'popover',
+  props: {
+    ...abstractTooltipProps,
+    prefixCls: PropTypes.string.def('ant-popover'),
+    transitionName: PropTypes.string.def('zoom-big'),
+    content: PropTypes.any,
+    title: PropTypes.any,
+  },
+  model: {
+    prop: 'visible',
+    event: 'change',
+  },
+  methods: {
+    getPopupDomNode () {
+      return this.$refs.tooltip.getPopupDomNode()
+    },
+    getOverlay (h) {
+      const { title, prefixCls, content, $slots } = this
+      return (
+        <div>
+          {(title || $slots.title) &&
+            <div class={`${prefixCls}-title`}>
+              {typeof title === 'function' ? title(h) : title}
+              {$slots.title}
+            </div>
+          }
+          <div class={`${prefixCls}-inner-content`}>
+            {typeof content === 'function' ? content(h) : content}
+            {$slots.content}
+          </div>
+        </div>
+      )
+    },
+  },
+
+  render (h) {
+    const props = getOptionProps(this)
+    delete props.title
+    delete props.content
+    const tooltipProps = {
+      props: {
+        ...props,
+      },
+      ref: 'tooltip',
+      on: this.$listeners,
+    }
+    return (
+      <Tooltip
+        {...tooltipProps}
+      >
+        <template slot='title'>
+          {this.getOverlay(h)}
+        </template>
+        {this.$slots.default}
+      </Tooltip>
+    )
+  },
+}
+
+</script>
diff --git a/components/popover/index.zh-CN.md b/components/popover/index.zh-CN.md
new file mode 100644
index 000000000..986b670c5
--- /dev/null
+++ b/components/popover/index.zh-CN.md
@@ -0,0 +1,27 @@
+---
+category: Components
+subtitle: 气泡卡片
+type: Data Display
+title: Popover
+---
+
+点击/鼠标移入元素,弹出气泡式的卡片浮层。
+
+## 何时使用
+
+当目标元素有进一步的描述和相关操作时,可以收纳到卡片中,根据用户的操作行为进行展现。
+
+和 `Tooltip` 的区别是,用户可以对浮层上的元素进行操作,因此它可以承载更复杂的内容,比如链接或按钮等。
+
+## API
+
+| 参数 | 说明 | 类型 | 默认值 |
+| --- | --- | --- | --- |
+| content | 卡片内容 | string\|function\|slot | 无 |
+| title | 卡片标题 | string\|function\|slot | 无 |
+
+更多属性请参考 [Tooltip](/components/tooltip/#API)。
+
+## 注意
+
+请确保 `Popover` 的子元素能接受 `mouseenter`、`mouseleave`、`focus`、`click` 事件。
diff --git a/components/popover/style/index.js b/components/popover/style/index.js
new file mode 100644
index 000000000..cf31ed80f
--- /dev/null
+++ b/components/popover/style/index.js
@@ -0,0 +1,2 @@
+import '../../style/index.less'
+import './index.less'
diff --git a/components/popover/style/index.less b/components/popover/style/index.less
new file mode 100644
index 000000000..227d2bc5a
--- /dev/null
+++ b/components/popover/style/index.less
@@ -0,0 +1,221 @@
+@import "../../style/themes/default";
+@import "../../style/mixins/index";
+
+@popover-prefix-cls: ~"@{ant-prefix}-popover";
+
+.@{popover-prefix-cls} {
+  position: absolute;
+  top: 0;
+  left: 0;
+  z-index: @zindex-popover;
+  cursor: auto;
+  user-select: text;
+  white-space: normal;
+  font-size: @font-size-base;
+  line-height: @line-height-base;
+  font-weight: normal;
+  text-align: left;
+
+  &:after {
+    content: "";
+    position: absolute;
+    background: rgba(255, 255, 255, 0.01);
+  }
+
+  &-hidden {
+    display: none;
+  }
+
+  // Offset the popover to account for the popover arrow
+  &-placement-top,
+  &-placement-topLeft,
+  &-placement-topRight {
+    padding-bottom: @popover-distance;
+  }
+
+  &-placement-right,
+  &-placement-rightTop,
+  &-placement-rightBottom {
+    padding-left: @popover-distance;
+  }
+
+  &-placement-bottom,
+  &-placement-bottomLeft,
+  &-placement-bottomRight {
+    padding-top: @popover-distance;
+  }
+
+  &-placement-left,
+  &-placement-leftTop,
+  &-placement-leftBottom {
+    padding-right: @popover-distance;
+  }
+
+  &-inner {
+    background-color: @popover-bg;
+    background-clip: padding-box;
+    border-radius: @border-radius-base;
+    box-shadow: @box-shadow-base;
+  }
+
+  &-title {
+    min-width: @popover-min-width;
+    margin: 0; // reset heading margin
+    padding: 8px 16px;
+    min-height: 32px;
+    border-bottom: 1px solid @border-color-split;
+    color: @popover-color;
+    font-weight: 500;
+  }
+
+  &-inner-content {
+    padding: 8px 16px;
+    color: @popover-color;
+  }
+
+  &-message {
+    padding: 8px 0 16px;
+    font-size: @font-size-base;
+    color: @popover-color;
+    > .@{iconfont-css-prefix} {
+      color: @warning-color;
+      line-height: 17px;
+      position: absolute;
+    }
+    &-title {
+      padding-left: 20px;
+    }
+  }
+
+  &-buttons {
+    text-align: right;
+    margin-bottom: 8px;
+    button {
+      margin-left: 8px;
+    }
+  }
+
+  // Arrows
+  // .popover-arrow is outer, .popover-arrow:after is inner
+
+  &-arrow {
+    &,
+    &:after {
+      position: absolute;
+      display: block;
+      width: 0;
+      height: 0;
+      border-color: transparent;
+      border-style: solid;
+    }
+  }
+
+  &-arrow {
+    border-width: @popover-arrow-outer-width;
+  }
+
+  &-arrow:after {
+    border-width: @popover-arrow-width;
+    content: "";
+  }
+
+  &-placement-top > &-content > &-arrow,
+  &-placement-topLeft > &-content >  &-arrow,
+  &-placement-topRight > &-content >  &-arrow {
+    border-bottom-width: 0;
+    border-top-color: @popover-arrow-outer-color;
+    bottom: @popover-distance - @popover-arrow-outer-width;
+    &:after {
+      content: " ";
+      bottom: 1px;
+      margin-left: -@popover-arrow-width;
+      border-bottom-width: 0;
+      border-top-color: @popover-arrow-color;
+    }
+  }
+  &-placement-top > &-content >  &-arrow {
+    left: 50%;
+    margin-left: -@popover-arrow-outer-width;
+  }
+  &-placement-topLeft > &-content >  &-arrow {
+    left: 16px;
+  }
+  &-placement-topRight > &-content >  &-arrow {
+    right: 16px;
+  }
+
+  &-placement-right > &-content >  &-arrow,
+  &-placement-rightTop > &-content >  &-arrow,
+  &-placement-rightBottom > &-content >  &-arrow {
+    left: @popover-distance - @popover-arrow-outer-width;
+    border-left-width: 0;
+    border-right-color: @popover-arrow-outer-color;
+    &:after {
+      content: " ";
+      left: 1px;
+      bottom: -@popover-arrow-width;
+      border-left-width: 0;
+      border-right-color: @popover-arrow-color;
+    }
+  }
+  &-placement-right > &-content >  &-arrow {
+    top: 50%;
+    margin-top: -@popover-arrow-outer-width;
+  }
+  &-placement-rightTop > &-content >  &-arrow {
+    top: 12px;
+  }
+  &-placement-rightBottom > &-content >  &-arrow {
+    bottom: 12px;
+  }
+
+  &-placement-bottom > &-content >  &-arrow,
+  &-placement-bottomLeft > &-content >  &-arrow,
+  &-placement-bottomRight > &-content >  &-arrow {
+    border-top-width: 0;
+    border-bottom-color: @popover-arrow-outer-color;
+    top: @popover-distance - @popover-arrow-outer-width;
+    &:after {
+      content: " ";
+      top: 1px;
+      margin-left: -@popover-arrow-width;
+      border-top-width: 0;
+      border-bottom-color: @popover-arrow-color;
+    }
+  }
+  &-placement-bottom > &-content >  &-arrow {
+    left: 50%;
+    margin-left: -@popover-arrow-outer-width;
+  }
+  &-placement-bottomLeft > &-content >  &-arrow {
+    left: 16px;
+  }
+  &-placement-bottomRight > &-content >  &-arrow {
+    right: 16px;
+  }
+
+  &-placement-left > &-content >  &-arrow,
+  &-placement-leftTop > &-content >  &-arrow,
+  &-placement-leftBottom > &-content >  &-arrow {
+    right: @popover-distance - @popover-arrow-outer-width;
+    border-right-width: 0;
+    border-left-color: @popover-arrow-outer-color;
+    &:after {
+      content: " ";
+      right: 1px;
+      border-right-width: 0;
+      border-left-color: @popover-arrow-color;
+      bottom: -@popover-arrow-width;
+    }
+  }
+  &-placement-left > &-content >  &-arrow {
+    top: 50%;
+    margin-top: -@popover-arrow-outer-width;
+  }
+  &-placement-leftTop > &-content >  &-arrow {
+    top: 12px;
+  }
+  &-placement-leftBottom > &-content >  &-arrow {
+    bottom: 12px;
+  }
+}
diff --git a/components/radio/Radio.vue b/components/radio/Radio.vue
index 60e4b6bfe..616f3c765 100644
--- a/components/radio/Radio.vue
+++ b/components/radio/Radio.vue
@@ -13,7 +13,7 @@
   </label>
 </template>
 <script>
-import hasProp from '../_util/hasProp'
+import hasProp from '../_util/props-util'
 export default {
   name: 'Radio',
   props: {
diff --git a/components/rate/Rate.vue b/components/rate/Rate.vue
index 7f22e1144..4ccedf738 100644
--- a/components/rate/Rate.vue
+++ b/components/rate/Rate.vue
@@ -3,7 +3,7 @@ import Star from './Star.vue'
 import Icon from '../icon'
 import { getOffsetLeft } from './util'
 import { cloneVNodes } from '../_util/vnode'
-import hasProp from '../_util/hasProp'
+import hasProp from '../_util/props-util'
 
 export default {
   name: 'Rate',
diff --git a/components/style.js b/components/style.js
index fa65f1323..82780a16e 100644
--- a/components/style.js
+++ b/components/style.js
@@ -11,5 +11,6 @@ import './badge/style'
 import './tabs/style'
 import './input/style'
 import './tooltip/style'
+import './popover/style'
 
 import './menu/style'
diff --git a/components/tabs/Tabs.vue b/components/tabs/Tabs.vue
index 1a80817e8..5690a3e08 100644
--- a/components/tabs/Tabs.vue
+++ b/components/tabs/Tabs.vue
@@ -3,7 +3,7 @@ import Icon from '../icon'
 import KeyCode from './KeyCode'
 import TabContent from './TabContent'
 import ScrollableInkTabBar from './ScrollableInkTabBar'
-import hasProp from '../_util/hasProp'
+import hasProp from '../_util/props-util'
 function getDefaultActiveKey (t) {
   let activeKey
   t.$slots.default && t.$slots.default.forEach(({ componentOptions = {}, key: tabKey }) => {
diff --git a/components/tabs/index.vue b/components/tabs/index.vue
index 703e8af1d..a4a82045c 100644
--- a/components/tabs/index.vue
+++ b/components/tabs/index.vue
@@ -1,7 +1,7 @@
 <script>
 import Tabs from './Tabs'
 import isFlexSupported from '../_util/isFlexSupported'
-import hasProp from '../_util/hasProp'
+import hasProp from '../_util/props-util'
 export default {
   props: {
     prefixCls: { type: String, default: 'ant-tabs' },
diff --git a/components/tooltip/abstractTooltipProps.js b/components/tooltip/abstractTooltipProps.js
new file mode 100644
index 000000000..9faf70877
--- /dev/null
+++ b/components/tooltip/abstractTooltipProps.js
@@ -0,0 +1,19 @@
+import PropTypes from '../_util/vue-types'
+export default {
+  trigger: PropTypes.oneOf(['hover', 'focus', 'click']).def('hover'),
+  visible: PropTypes.bool,
+  placement: PropTypes.oneOf(['top', 'left', 'right', 'bottom',
+    'topLeft', 'topRight', 'bottomLeft', 'bottomRight',
+    'leftTop', 'leftBottom', 'rightTop', 'rightBottom']).def('top'),
+  transitionName: PropTypes.string.def('zoom-big-fast'),
+  // onVisibleChange: PropTypes.func,
+  overlayStyle: PropTypes.object.def({}),
+  overlayClassName: PropTypes.string,
+  prefixCls: PropTypes.string.def('ant-tooltip'),
+  mouseEnterDelay: PropTypes.number.def(0.1),
+  mouseLeaveDelay: PropTypes.number.def(0.1),
+  getTooltipContainer: PropTypes.func,
+  getPopupContainer: PropTypes.func,
+  arrowPointAtCenter: PropTypes.bool.def(false),
+  autoAdjustOverflow: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]).def(true),
+}
diff --git a/components/tooltip/index.zh-CN.md b/components/tooltip/index.zh-CN.md
new file mode 100644
index 000000000..9cecf0f03
--- /dev/null
+++ b/components/tooltip/index.zh-CN.md
@@ -0,0 +1,45 @@
+---
+category: Components
+subtitle: 文字提示
+type: Data Display
+title: Tooltip
+---
+
+简单的文字提示气泡框。
+
+## 何时使用
+
+鼠标移入则显示提示,移出消失,气泡浮层不承载复杂文本和操作。
+
+可用来代替系统默认的 `title` 提示,提供一个`按钮/文字/操作`的文案解释。
+
+## API
+
+| 参数 | 说明 | 类型 | 默认值 |
+| --- | --- | --- | --- |
+| title | 提示文字 | `string` `function` `slot` | 无 |
+
+### 共同的 API
+
+以下 API 为 Tooltip、Popconfirm、Popover 共享的 API。
+
+| 参数 | 说明 | 类型 | 默认值 |
+| --- | --- | --- | --- |
+| arrowPointAtCenter | 箭头是否指向目标元素中 | boolean | `false` |
+| autoAdjustOverflow | 气泡被遮挡时自动调整位置 | boolean | `true` |
+| getPopupContainer | 浮层渲染父节点,默认渲染到 body 上 | Function(triggerNode) | () => document.body |
+| mouseEnterDelay | 鼠标移入后延时多少才显示 Tooltip,单位:秒 | number | 0 |
+| mouseLeaveDelay | 鼠标移出后延时多少才隐藏 Tooltip,单位:秒 | number | 0.1 |
+| overlayClassName | 卡片类名 | string | 无 |
+| overlayStyle | 卡片样式 | object | 无 |
+| placement | 气泡框位置,可选 `top` `left` `right` `bottom` `topLeft` `topRight` `bottomLeft` `bottomRight` `leftTop` `leftBottom` `rightTop` `rightBottom` | string | top |
+| trigger | 触发行为,可选 `hover/focus/click` | string | hover |
+| visible(v-model) | 用于手动控制浮层显隐 | boolean | false |
+
+### 事件
+| 事件名称 | 说明 | 回调参数 |
+| visibleChange | 显示隐藏变换时触发 | (visible) |
+
+## 注意
+
+请确保 `Tooltip` 的子元素能接受 `mouseenter`、`mouseleave`、`focus`、`click` 事件。
diff --git a/components/tooltip/src/Tooltip.vue b/components/tooltip/src/Tooltip.vue
index 5d13ae671..67577d786 100644
--- a/components/tooltip/src/Tooltip.vue
+++ b/components/tooltip/src/Tooltip.vue
@@ -2,10 +2,11 @@
 import PropTypes from '../../_util/vue-types'
 import Trigger from '../../trigger'
 import { placements } from './placements'
-import hasProp from '../../_util/hasProp'
+import hasProp from '../../_util/props-util'
+function noop () {}
 export default {
   props: {
-    trigger: PropTypes.any.def(['hover']),
+    trigger: PropTypes.any.def('hover'),
     defaultVisible: PropTypes.bool,
     visible: PropTypes.bool,
     placement: PropTypes.string.def('right'),
@@ -14,7 +15,6 @@ export default {
       PropTypes.object,
     ]),
     animation: PropTypes.any,
-    // onVisibleChange: PropTypes.func,
     afterVisibleChange: PropTypes.func.def(() => {}),
     overlay: PropTypes.any,
     overlayStyle: PropTypes.object,
@@ -47,12 +47,6 @@ export default {
     getPopupDomNode () {
       return this.$refs.trigger.getPopupDomNode()
     },
-    onVisibleChange (val) {
-      this.$emit('visibleChange', val)
-    },
-    onPopupAlign () {
-      this.$emit('popupAlign', ...arguments)
-    },
   },
   render (h) {
     const {
@@ -79,7 +73,6 @@ export default {
         popupPlacement: placement,
         popupAlign: align,
         getPopupContainer: getTooltipContainer,
-        // onPopupVisibleChange: onVisibleChange,
         afterPopupVisibleChange: afterVisibleChange,
         popupTransitionName: transitionName,
         popupAnimation: animation,
@@ -91,8 +84,8 @@ export default {
         ...extraProps,
       },
       on: {
-        popupVisibleChange: this.onVisibleChange,
-        popupAlign: this.onPopupAlign,
+        popupVisibleChange: this.$listeners.visibleChange || noop,
+        popupAlign: this.$listeners.popupAlign || noop,
       },
       ref: 'trigger',
     }
diff --git a/components/tooltip/tooltip.vue b/components/tooltip/tooltip.vue
index 6c19912d2..c66fd4730 100644
--- a/components/tooltip/tooltip.vue
+++ b/components/tooltip/tooltip.vue
@@ -3,7 +3,8 @@ import { cloneElement, isValidElement, getClass, getStyle } from '../_util/vnode
 import RcTooltip from './src/tooltip'
 import getPlacements from './placements'
 import PropTypes from '../_util/vue-types'
-import hasProp from '../_util/hasProp'
+import hasProp from '../_util/props-util'
+import abstractTooltipProps from './abstractTooltipProps'
 
 const splitObject = (obj, keys) => {
   const picked = {}
@@ -20,23 +21,12 @@ const splitObject = (obj, keys) => {
 export default {
   name: 'Tooltip',
   props: {
-    trigger: PropTypes.oneOf(['hover', 'focus', 'click']).def(['hover']),
-    visible: PropTypes.bool,
+    ...abstractTooltipProps,
     title: PropTypes.any,
-    placement: PropTypes.oneOf(['top', 'left', 'right', 'bottom',
-      'topLeft', 'topRight', 'bottomLeft', 'bottomRight',
-      'leftTop', 'leftBottom', 'rightTop', 'rightBottom']).def('top'),
-    transitionName: PropTypes.string.def('zoom-big-fast'),
-    // onVisibleChange: PropTypes.func,
-    overlayStyle: PropTypes.object,
-    overlayClassName: PropTypes.string,
-    prefixCls: PropTypes.string.def('ant-tooltip'),
-    mouseEnterDelay: PropTypes.number.def(0.1),
-    mouseLeaveDelay: PropTypes.number.def(0.1),
-    getTooltipContainer: PropTypes.func,
-    getPopupContainer: PropTypes.func,
-    arrowPointAtCenter: PropTypes.bool.def(false),
-    autoAdjustOverflow: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]).def(true),
+  },
+  model: {
+    prop: 'visible',
+    event: 'change',
   },
   data () {
     return {
@@ -54,7 +44,7 @@ export default {
         this.sVisible = this.isNoTitle() ? false : visible
       }
       if (!this.isNoTitle()) {
-        this.$emit('visibleChange', visible)
+        this.$emit('change', visible)
       }
     },
 
@@ -164,7 +154,9 @@ export default {
     if (!hasProp(this, 'visible') && this.isNoTitle()) {
       sVisible = false
     }
-
+    if (!children) {
+      return null
+    }
     const child = this.getDisabledCompatibleChildren(isValidElement(children) ? children : <span>{children}</span>)
     const childCls = {
       [openClassName || `${prefixCls}-open`]: true,
diff --git a/components/trigger/Popup.vue b/components/trigger/Popup.vue
index 38a314044..4587ad612 100644
--- a/components/trigger/Popup.vue
+++ b/components/trigger/Popup.vue
@@ -3,6 +3,7 @@ import PropTypes from '../_util/vue-types'
 import Align from '../align'
 import PopupInner from './PopupInner'
 import LazyRenderBox from './LazyRenderBox'
+import { noop } from './utils'
 
 export default {
   props: {
@@ -60,7 +61,7 @@ export default {
         popupDomNode.className = popupDomNode.className.replace(this.currentAlignClassName, currentAlignClassName)
         this.currentAlignClassName = currentAlignClassName
       }
-      this.$emit('align', popupDomNode, align)
+      this.$listeners.align && this.$listeners.align(popupDomNode, align)
     },
 
     getPopupDomNode () {
@@ -93,20 +94,15 @@ export default {
     getClassName (currentAlignClassName) {
       return `${this.$props.prefixCls} ${this.$props.popupClassName} ${currentAlignClassName}`
     },
-    onMouseEnter (e) {
-      this.$emit('mouseenter', e)
-    },
-    onMouseLeave (e) {
-      this.$emit('mouseleave', e)
-    },
     afterLeave (el) {
       if (this.destroyPopupOnHide) {
         this.destroyPopup = true
       }
     },
     getPopupElement () {
-      const { $props: props, onMouseEnter, onMouseLeave, $slots } = this
+      const { $props: props, $slots, $listeners } = this
       const { align, visible, prefixCls, animation } = props
+      const { mouseenter, mouseleave } = $listeners
       this.currentAlignClassName = this.currentAlignClassName || props.getClassNameFromAlign(align)
       const className = this.getClassName(this.currentAlignClassName)
       // const hiddenClassName = `${prefixCls}-hidden`
@@ -121,8 +117,8 @@ export default {
         },
         class: `${className}`,
         on: {
-          mouseenter: onMouseEnter,
-          mouseleave: onMouseLeave,
+          mouseenter: mouseenter || noop,
+          mouseleave: mouseleave || noop,
         },
         ref: 'popupInstance',
         style: { ...this.getZIndexStyle() },
diff --git a/components/trigger/PopupInner.vue b/components/trigger/PopupInner.vue
index 323452c8f..707e96223 100644
--- a/components/trigger/PopupInner.vue
+++ b/components/trigger/PopupInner.vue
@@ -8,22 +8,14 @@ export default {
     prefixCls: PropTypes.string,
     visible: PropTypes.bool,
   },
-  methods: {
-    onMouseEnter (e) {
-      this.$emit('mouseenter', e)
-    },
-    onMouseLeave (e) {
-      this.$emit('mouseleave', e)
-    },
-  },
   render () {
     const { prefixCls, visible } = this.$props
-    const { onMouseEnter, onMouseLeave } = this
+    const { $listeners } = this
+    const divProps = {
+      on: $listeners,
+    }
     return (
-      <div
-        onMouseenter={onMouseEnter}
-        onMouseleave={onMouseLeave}
-      >
+      <div {...divProps}>
         <LazyRenderBox class={`${prefixCls}-content`} visible={visible}>
           {this.$slots.default}
         </LazyRenderBox>
diff --git a/components/trigger/index.vue b/components/trigger/index.vue
index a56b5310c..e65c0bc27 100644
--- a/components/trigger/index.vue
+++ b/components/trigger/index.vue
@@ -1,12 +1,12 @@
 <script>
 import PropTypes from '../_util/vue-types'
 import contains from '../_util/Dom/contains'
-import hasProp from '../_util/hasProp'
+import hasProp from '../_util/props-util'
 import addEventListener from '../_util/Dom/addEventListener'
 import warning from '../_util/warning'
 import Popup from './Popup'
 import { getAlignFromPlacement, getPopupClassNameFromAlign, noop } from './utils'
-import StateMixin from '../_util/StateMixin'
+import BaseMixin from '../_util/BaseMixin'
 import { cloneElement, cloneVNode } from '../_util/vnode'
 
 function returnEmptyString () {
@@ -62,7 +62,7 @@ export default {
     maskAnimation: PropTypes.string,
   },
 
-  mixins: [StateMixin],
+  mixins: [BaseMixin],
   data () {
     const props = this.$props
     let popupVisible
@@ -282,9 +282,6 @@ export default {
       }
       return popupAlign
     },
-    onPopupAlign () {
-      this.$emit('popupAlign', ...arguments)
-    },
     getComponent (h) {
       const mouseProps = {}
       if (this.isMouseEnterToShow()) {
@@ -294,7 +291,7 @@ export default {
         mouseProps.mouseleave = this.onPopupMouseleave
       }
       const { prefixCls, destroyPopupOnHide, sPopupVisible,
-        popupStyle, popupClassName, action, onPopupAlign,
+        popupStyle, popupClassName, action,
         popupAnimation, handleGetPopupClassFromAlign, getRootDomNode,
         mask, zIndex, popupTransitionName, getPopupAlign,
         maskAnimation, maskTransitionName, popup, $slots, getContainer } = this
@@ -317,7 +314,7 @@ export default {
           popupClassName,
         },
         on: {
-          align: onPopupAlign,
+          align: this.$listeners.popupAlign || noop,
           ...mouseProps,
         },
         ref: 'popup',
@@ -358,7 +355,7 @@ export default {
           })
           this.$forceUpdate()
         }
-        this.$emit('popupVisibleChange', sPopupVisible)
+        this.$listeners.popupVisibleChange && this.$listeners.popupVisibleChange(sPopupVisible)
       }
     },
 
diff --git a/contributors.md b/contributors.md
index bedada84f..a87464170 100644
--- a/contributors.md
+++ b/contributors.md
@@ -10,6 +10,7 @@ Tag | done
 ToolTip | done
 Popconfirm
 Popover
+Menu
 Carousel
 Mention
 Input | done |select完成后补全demo
@@ -31,7 +32,6 @@ BackTop
 Dropdown
 Layout
 message
-Menu
 Modal
 notification
 Anchor

From 8612331300a4e3063f7d7f47edb966b1b58a176c Mon Sep 17 00:00:00 2001
From: tangjinzhou <415800467@qq.com>
Date: Fri, 12 Jan 2018 17:06:01 +0800
Subject: [PATCH 6/7] =?UTF-8?q?=E4=BC=98=E5=8C=96=20tabs?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 components/tabs/index.js                      |  4 +--
 components/tabs/index.vue                     | 35 ++++++++++---------
 .../tabs/{index.zh-CN => index.zh-CN.md}      |  0
 components/tabs/{ => src}/InkTabBar.vue       |  0
 components/tabs/{ => src}/InkTabBarMixin.js   |  0
 components/tabs/{ => src}/KeyCode.js          |  0
 .../tabs/{ => src}/ScrollableInkTabBar.vue    |  3 +-
 .../tabs/{ => src}/ScrollableTabBar.vue       |  3 +-
 .../tabs/{ => src}/ScrollableTabBarMixin.js   |  6 ++--
 components/tabs/{ => src}/TabBar.vue          | 13 ++-----
 components/tabs/{ => src}/TabBarMixin.js      | 15 +++-----
 components/tabs/{ => src}/TabContent.vue      |  0
 components/tabs/{ => src}/TabPane.vue         |  0
 components/tabs/{ => src}/Tabs.vue            | 31 +++++++++-------
 components/tabs/{ => src}/utils.js            |  0
 15 files changed, 51 insertions(+), 59 deletions(-)
 rename components/tabs/{index.zh-CN => index.zh-CN.md} (100%)
 rename components/tabs/{ => src}/InkTabBar.vue (100%)
 rename components/tabs/{ => src}/InkTabBarMixin.js (100%)
 rename components/tabs/{ => src}/KeyCode.js (100%)
 rename components/tabs/{ => src}/ScrollableInkTabBar.vue (77%)
 rename components/tabs/{ => src}/ScrollableTabBar.vue (78%)
 rename components/tabs/{ => src}/ScrollableTabBarMixin.js (97%)
 rename components/tabs/{ => src}/TabBar.vue (69%)
 rename components/tabs/{ => src}/TabBarMixin.js (94%)
 rename components/tabs/{ => src}/TabContent.vue (100%)
 rename components/tabs/{ => src}/TabPane.vue (100%)
 rename components/tabs/{ => src}/Tabs.vue (91%)
 rename components/tabs/{ => src}/utils.js (100%)

diff --git a/components/tabs/index.js b/components/tabs/index.js
index 087cc67eb..b91a2b6fb 100644
--- a/components/tabs/index.js
+++ b/components/tabs/index.js
@@ -1,6 +1,6 @@
 import Tabs from './index.vue'
-import TabPane from './TabPane'
-import TabContent from './TabContent'
+import TabPane from './src/TabPane'
+import TabContent from './src/TabContent'
 Tabs.TabPane = TabPane
 export default Tabs
 export { TabPane, TabContent }
diff --git a/components/tabs/index.vue b/components/tabs/index.vue
index a4a82045c..aed9aa73b 100644
--- a/components/tabs/index.vue
+++ b/components/tabs/index.vue
@@ -1,5 +1,5 @@
 <script>
-import Tabs from './Tabs'
+import Tabs from './src/Tabs'
 import isFlexSupported from '../_util/isFlexSupported'
 import hasProp from '../_util/props-util'
 export default {
@@ -75,7 +75,6 @@ export default {
       tabPosition,
       tabBarStyle,
       hideAdd,
-      onTabClick,
       onPrevClick,
       onNextClick,
       animated,
@@ -114,36 +113,38 @@ export default {
       }
     })
     const tabBarProps = {
-      inkBarAnimated,
-      onTabClick,
-      onPrevClick,
-      onNextClick,
+      props: {
+        hideAdd,
+        removeTab: this.removeTab,
+        createNewTab: this.createNewTab,
+        inkBarAnimated,
+      },
+      on: {
+        // tabClick: onTabClick,
+        prevClick: onPrevClick,
+        nextClick: onNextClick,
+      },
       style: tabBarStyle,
-      hideAdd,
-      removeTab: this.removeTab,
-      createNewTab: this.createNewTab,
     }
     const tabContentProps = {
-      animated: tabPaneAnimated,
-      animatedWithMargin: true,
+      props: {
+        animated: tabPaneAnimated,
+        animatedWithMargin: true,
+      },
     }
-    const self = this
     const tabsProps = {
       props: {
         prefixCls,
         tabBarPosition: tabPosition,
-        onChange: this.handleChange,
         tabBarProps: tabBarProps,
         tabContentProps: tabContentProps,
         destroyInactiveTabPane,
         defaultActiveKey,
         type,
-        onTabClick: this.onTabClick,
       },
       on: {
-        change (val) {
-          self.handleChange(val)
-        },
+        change: this.handleChange,
+        tabClick: this.onTabClick,
       },
     }
     if (hasProp(this, 'activeKey')) {
diff --git a/components/tabs/index.zh-CN b/components/tabs/index.zh-CN.md
similarity index 100%
rename from components/tabs/index.zh-CN
rename to components/tabs/index.zh-CN.md
diff --git a/components/tabs/InkTabBar.vue b/components/tabs/src/InkTabBar.vue
similarity index 100%
rename from components/tabs/InkTabBar.vue
rename to components/tabs/src/InkTabBar.vue
diff --git a/components/tabs/InkTabBarMixin.js b/components/tabs/src/InkTabBarMixin.js
similarity index 100%
rename from components/tabs/InkTabBarMixin.js
rename to components/tabs/src/InkTabBarMixin.js
diff --git a/components/tabs/KeyCode.js b/components/tabs/src/KeyCode.js
similarity index 100%
rename from components/tabs/KeyCode.js
rename to components/tabs/src/KeyCode.js
diff --git a/components/tabs/ScrollableInkTabBar.vue b/components/tabs/src/ScrollableInkTabBar.vue
similarity index 77%
rename from components/tabs/ScrollableInkTabBar.vue
rename to components/tabs/src/ScrollableInkTabBar.vue
index f04450b9f..5a65407d0 100644
--- a/components/tabs/ScrollableInkTabBar.vue
+++ b/components/tabs/src/ScrollableInkTabBar.vue
@@ -2,10 +2,11 @@
 import InkTabBarMixin from './InkTabBarMixin'
 import ScrollableTabBarMixin from './ScrollableTabBarMixin'
 import TabBarMixin from './TabBarMixin'
+import BaseMixin from '../../_util/BaseMixin'
 
 export default {
   name: 'ScrollableInkTabBar',
-  mixins: [TabBarMixin, InkTabBarMixin, ScrollableTabBarMixin],
+  mixins: [TabBarMixin, InkTabBarMixin, ScrollableTabBarMixin, BaseMixin],
   render (h) {
     const inkBarNode = this.getInkBarNode()
     const tabs = this.getTabs(h)
diff --git a/components/tabs/ScrollableTabBar.vue b/components/tabs/src/ScrollableTabBar.vue
similarity index 78%
rename from components/tabs/ScrollableTabBar.vue
rename to components/tabs/src/ScrollableTabBar.vue
index 7abc2d748..7b38d9637 100644
--- a/components/tabs/ScrollableTabBar.vue
+++ b/components/tabs/src/ScrollableTabBar.vue
@@ -1,10 +1,11 @@
 <script>
 import ScrollableTabBarMixin from './ScrollableTabBarMixin'
 import TabBarMixin from './TabBarMixin'
+import BaseMixin from '../../_util/BaseMixin'
 
 export default {
   name: 'ScrollableTabBar',
-  mixins: [TabBarMixin, ScrollableTabBarMixin],
+  mixins: [TabBarMixin, ScrollableTabBarMixin, BaseMixin],
   render (h) {
     const inkBarNode = this.getInkBarNode()
     const tabs = this.getTabs(h)
diff --git a/components/tabs/ScrollableTabBarMixin.js b/components/tabs/src/ScrollableTabBarMixin.js
similarity index 97%
rename from components/tabs/ScrollableTabBarMixin.js
rename to components/tabs/src/ScrollableTabBarMixin.js
index 6df86318b..d872ad5aa 100644
--- a/components/tabs/ScrollableTabBarMixin.js
+++ b/components/tabs/src/ScrollableTabBarMixin.js
@@ -6,8 +6,6 @@ function noop () {
 export default {
   props: {
     scrollAnimated: { type: Boolean, default: true },
-    onPrevClick: { type: Function, default: noop },
-    onNextClick: { type: Function, default: noop },
   },
 
   data () {
@@ -201,7 +199,7 @@ export default {
     },
 
     prevClick (e) {
-      this.$props.onPrevClick(e)
+      this.__emit('prevClick', e)
       const navWrapNode = this.$refs.navWrap
       const navWrapNodeWH = this.getOffsetWH(navWrapNode)
       const { offset } = this
@@ -209,7 +207,7 @@ export default {
     },
 
     nextClick (e) {
-      this.$props.onNextClick(e)
+      this.__emit('nextClick', e)
       const navWrapNode = this.$refs.navWrap
       const navWrapNodeWH = this.getOffsetWH(navWrapNode)
       const { offset } = this
diff --git a/components/tabs/TabBar.vue b/components/tabs/src/TabBar.vue
similarity index 69%
rename from components/tabs/TabBar.vue
rename to components/tabs/src/TabBar.vue
index 423f97cc7..8e65f6a7d 100644
--- a/components/tabs/TabBar.vue
+++ b/components/tabs/src/TabBar.vue
@@ -1,9 +1,8 @@
 <script>
 import TabBarMixin from './TabBarMixin'
-function noop () {
-}
+import BaseMixin from '../../_util/BaseMixin'
 export default {
-  mixins: [TabBarMixin],
+  mixins: [TabBarMixin, BaseMixin],
   name: 'TabBar',
   props: {
     prefixCls: {
@@ -15,14 +14,6 @@ export default {
       type: String,
     },
     disabled: Boolean,
-    onKeyDown: {
-      default: noop,
-      type: Function,
-    },
-    onTabClick: {
-      default: noop,
-      type: Function,
-    },
     activeKey: String,
     panels: Array,
   },
diff --git a/components/tabs/TabBarMixin.js b/components/tabs/src/TabBarMixin.js
similarity index 94%
rename from components/tabs/TabBarMixin.js
rename to components/tabs/src/TabBarMixin.js
index 5ebb0028d..56fc9b9cb 100644
--- a/components/tabs/TabBarMixin.js
+++ b/components/tabs/src/TabBarMixin.js
@@ -1,4 +1,4 @@
-import Icon from '../icon'
+import Icon from '../../icon'
 function noop () {
 }
 export default {
@@ -12,14 +12,6 @@ export default {
       type: String,
     },
     disabled: Boolean,
-    onKeyDown: {
-      default: noop,
-      type: Function,
-    },
-    onTabClick: {
-      default: noop,
-      type: Function,
-    },
     activeKey: String,
     panels: Array,
     hideAdd: Boolean,
@@ -51,7 +43,7 @@ export default {
         } else {
         }
         const onClick = () => {
-          !disabled && this.onTabClick(tabKey)
+          !disabled && this.__emit('tabClick', tabKey)
         }
 
         let tabC = typeof tab === 'function' ? child.tab(h, tabKey) : tab
@@ -86,6 +78,9 @@ export default {
 
       return rst
     },
+    onKeyDown (e) {
+      this.__emit('keydown', e)
+    },
     getRootNode (contents, createElement) {
       const {
         prefixCls, onKeyDown, tabBarPosition, hideAdd,
diff --git a/components/tabs/TabContent.vue b/components/tabs/src/TabContent.vue
similarity index 100%
rename from components/tabs/TabContent.vue
rename to components/tabs/src/TabContent.vue
diff --git a/components/tabs/TabPane.vue b/components/tabs/src/TabPane.vue
similarity index 100%
rename from components/tabs/TabPane.vue
rename to components/tabs/src/TabPane.vue
diff --git a/components/tabs/Tabs.vue b/components/tabs/src/Tabs.vue
similarity index 91%
rename from components/tabs/Tabs.vue
rename to components/tabs/src/Tabs.vue
index 5690a3e08..babb0f479 100644
--- a/components/tabs/Tabs.vue
+++ b/components/tabs/src/Tabs.vue
@@ -1,9 +1,10 @@
 <script>
-import Icon from '../icon'
+import Icon from '../../icon'
 import KeyCode from './KeyCode'
 import TabContent from './TabContent'
 import ScrollableInkTabBar from './ScrollableInkTabBar'
-import hasProp from '../_util/props-util'
+import hasProp from '../../_util/props-util'
+import BaseMixin from '../../_util/BaseMixin'
 function getDefaultActiveKey (t) {
   let activeKey
   t.$slots.default && t.$slots.default.forEach(({ componentOptions = {}, key: tabKey }) => {
@@ -23,8 +24,7 @@ function activeKeyIsValid (t, key) {
   })
   return key !== undefined && keys.indexOf(key) >= 0
 }
-function noop () {
-}
+
 export default {
   name: 'Tabs',
   components: { Icon },
@@ -32,6 +32,7 @@ export default {
     prop: 'activeKey',
     event: 'change',
   },
+  mixins: [BaseMixin],
   props: {
     prefixCls: {
       default: 'ant-tabs',
@@ -53,8 +54,6 @@ export default {
         return ['line', 'card', 'editable-card'].includes(value)
       },
     },
-    onChange: { type: Function, default: noop },
-    onTabClick: { type: Function, default: noop },
   },
   data () {
     return {
@@ -90,7 +89,7 @@ export default {
       return activeKey
     },
     handleTabClick (activeKey) {
-      this.onTabClick(activeKey)
+      this.__emit('tabClick', activeKey)
       this.setActiveKey(activeKey)
     },
 
@@ -112,7 +111,7 @@ export default {
         if (!hasProp(this, 'activeKey')) {
           this.stateActiveKey = activeKey
         }
-        this.onChange(activeKey)
+        this.__emit('change', activeKey)
       }
     },
 
@@ -172,25 +171,31 @@ export default {
     })
     const tabContentProps = {
       props: {
-        ...this.tabContentProps,
+        ...this.tabContentProps.props,
         prefixCls,
         tabBarPosition,
         activeKey: stateActiveKey,
         destroyInactiveTabPane,
-        onChange: setActiveKey,
+        // onChange: setActiveKey,
+      },
+      on: {
+        change: setActiveKey,
       },
     }
     const tabBarProps = {
       props: {
-        ...this.tabBarProps,
+        ...this.tabBarProps.props,
         panels: panels,
         prefixCls: prefixCls,
-        onKeyDown: onNavKeyDown,
         tabBarPosition: tabBarPosition,
-        onTabClick: handleTabClick,
         activeKey: stateActiveKey,
       },
       style: this.tabBarProps.style || {},
+      on: {
+        ...this.tabBarProps.on,
+        keydown: onNavKeyDown,
+        tabClick: handleTabClick,
+      },
     }
     const contents = [
       <ScrollableInkTabBar
diff --git a/components/tabs/utils.js b/components/tabs/src/utils.js
similarity index 100%
rename from components/tabs/utils.js
rename to components/tabs/src/utils.js

From 5d4f905a2f8d4cb73c8be6e470cb0f83a74fb5d7 Mon Sep 17 00:00:00 2001
From: tangjinzhou <415800467@qq.com>
Date: Fri, 12 Jan 2018 19:04:42 +0800
Subject: [PATCH 7/7] add popconfirm

---
 components/_util/props-util.js       |  10 ++-
 components/button/button.vue         |  31 +------
 components/button/buttonTypes.js     |  12 +++
 components/index.js                  |   2 +
 components/popconfirm/demo/basic.vue |  29 +++++++
 components/popconfirm/index.vue      | 118 +++++++++++++++++++++++++++
 components/popconfirm/index.zh-CN.md |   4 +-
 components/popover/index.vue         |  31 +++----
 components/style.js                  |   1 +
 contributors.md                      |   2 +-
 10 files changed, 188 insertions(+), 52 deletions(-)
 create mode 100644 components/button/buttonTypes.js
 create mode 100644 components/popconfirm/demo/basic.vue

diff --git a/components/_util/props-util.js b/components/_util/props-util.js
index d98722fc2..68e8f97b2 100644
--- a/components/_util/props-util.js
+++ b/components/_util/props-util.js
@@ -18,5 +18,13 @@ const getOptionProps = (instance) => {
   return filterProps($props, $options.propsData)
 }
 
-export { hasProp, filterProps, getOptionProps }
+const getComponentFromProp = (instance, h, prop) => {
+  const temp = instance[prop]
+  if (temp !== undefined) {
+    return typeof temp === 'function' ? temp(h) : temp
+  }
+  return instance.$slots[prop]
+}
+
+export { hasProp, filterProps, getOptionProps, getComponentFromProp }
 export default hasProp
diff --git a/components/button/button.vue b/components/button/button.vue
index 39b78a8d0..e82a87c07 100644
--- a/components/button/button.vue
+++ b/components/button/button.vue
@@ -2,40 +2,13 @@
 import Icon from '../icon'
 const rxTwoCNChar = /^[\u4e00-\u9fa5]{2}$/
 const isTwoCNChar = rxTwoCNChar.test.bind(rxTwoCNChar)
+import buttonTypes from './buttonTypes'
 export default {
   name: 'Button',
   __ANT_BUTTON: true,
   components: { Icon },
   props: {
-    prefixCls: {
-      default: 'ant-btn',
-      type: String,
-    },
-    type: {
-      validator (value) {
-        return ['primary', 'danger', 'dashed', 'ghost', 'default'].includes(value)
-      },
-    },
-    htmlType: {
-      default: 'button',
-      validator (value) {
-        return ['button', 'submit', 'reset'].includes(value)
-      },
-    },
-    icon: String,
-    shape: {
-      validator (value) {
-        return ['circle', 'circle-outline'].includes(value)
-      },
-    },
-    size: {
-      validator (value) {
-        return ['small', 'large', 'default'].includes(value)
-      },
-    },
-    loading: [Boolean, Object],
-    disabled: Boolean,
-    ghost: Boolean,
+    ...buttonTypes,
   },
   data () {
     return {
diff --git a/components/button/buttonTypes.js b/components/button/buttonTypes.js
new file mode 100644
index 000000000..2104f1aa7
--- /dev/null
+++ b/components/button/buttonTypes.js
@@ -0,0 +1,12 @@
+import PropTypes from '../_util/vue-types'
+export default {
+  prefixCls: PropTypes.string.def('ant-btn'),
+  type: PropTypes.oneOf(['primary', 'danger', 'dashed', 'ghost', 'default']).def('default'),
+  htmlType: PropTypes.oneOf(['button', 'submit', 'reset']).def('button'),
+  icon: PropTypes.string,
+  shape: PropTypes.oneOf(['circle', 'circle-outline']),
+  size: PropTypes.oneOf(['small', 'large', 'default']).def('default'),
+  loading: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
+  disabled: PropTypes.bool,
+  ghost: PropTypes.bool,
+}
diff --git a/components/index.js b/components/index.js
index 3b8f554d7..18db6ca7d 100644
--- a/components/index.js
+++ b/components/index.js
@@ -29,3 +29,5 @@ export { default as Tabs } from './tabs'
 export { default as Input } from './input'
 
 export { default as Popover } from './popover'
+
+export { default as Popconfirm } from './popconfirm'
diff --git a/components/popconfirm/demo/basic.vue b/components/popconfirm/demo/basic.vue
new file mode 100644
index 000000000..95f6bbb29
--- /dev/null
+++ b/components/popconfirm/demo/basic.vue
@@ -0,0 +1,29 @@
+<template>
+<div>
+<md>
+## 基本
+最简单的用法。
+</md>
+  <Popconfirm title="Are you sure delete this task?" @confirm="confirm" @cancel="cancel" okText="Yes" cancelText="No">
+    <a href="#">Delete</a>
+  </Popconfirm>
+</div>
+</template>
+
+<script>
+import { Popconfirm, Button } from 'antd'
+export default {
+  methods: {
+    confirm (e) {
+      console.log(e)
+    },
+    cancel (e) {
+      console.log(e)
+    },
+  },
+  components: {
+    Popconfirm,
+    AntButton: Button,
+  },
+}
+</script>
diff --git a/components/popconfirm/index.vue b/components/popconfirm/index.vue
index e69de29bb..9807ce386 100644
--- a/components/popconfirm/index.vue
+++ b/components/popconfirm/index.vue
@@ -0,0 +1,118 @@
+<script>
+import omit from 'omit.js'
+import Tooltip from '../tooltip'
+import abstractTooltipProps from '../tooltip/abstractTooltipProps'
+import PropTypes from '../_util/vue-types'
+import { getOptionProps, hasProp, getComponentFromProp } from '../_util/props-util'
+import BaseMixin from '../_util/BaseMixin'
+import buttonTypes from '../button/buttonTypes'
+import Icon from '../icon'
+import Button from '../button'
+
+export default {
+  name: 'popconfirm',
+  props: {
+    ...abstractTooltipProps,
+    prefixCls: PropTypes.string.def('ant-popover'),
+    transitionName: PropTypes.string.def('zoom-big'),
+    content: PropTypes.any,
+    title: PropTypes.any,
+    trigger: abstractTooltipProps.trigger.def('click'),
+    okType: buttonTypes.type.def('primary'),
+    okText: PropTypes.any,
+    cancelText: PropTypes.any,
+  },
+  mixins: [BaseMixin],
+  model: {
+    prop: 'visible',
+    event: 'change',
+  },
+  data () {
+    return {
+      sVisible: this.$props.visible,
+    }
+  },
+  methods: {
+    onConfirm (e) {
+      this.setVisible(false)
+      this.$emit('confirm', e)
+    },
+
+    onCancel (e) {
+      this.setVisible(false)
+      this.$emit('cancel', e)
+    },
+
+    onVisibleChange (sVisible) {
+      this.setVisible(sVisible)
+    },
+
+    setVisible (sVisible) {
+      const props = this.$props
+      if (!hasProp(this, 'visible')) {
+        this.setState({ sVisible })
+      }
+
+      const { onVisibleChange } = props
+      if (onVisibleChange) {
+        onVisibleChange(sVisible)
+      }
+      this.$emit('change', sVisible)
+    },
+    getPopupDomNode () {
+      return this.$refs.tooltip.getPopupDomNode()
+    },
+  },
+  render (h) {
+    const { prefixCls, okType } = this.$props
+    const props = getOptionProps(this)
+    const otherProps = omit(props, [
+      'title',
+      'content',
+      'cancelText',
+      'okText',
+    ])
+    const tooltipProps = {
+      props: {
+        ...otherProps,
+        visible: this.sVisible,
+      },
+      ref: 'tooltip',
+      on: {
+        change: this.onVisibleChange,
+      },
+    }
+    const overlay = (
+      <div>
+        <div class={`${prefixCls}-inner-content`}>
+          <div class={`${prefixCls}-message`}>
+            <Icon type='exclamation-circle' />
+            <div class={`${prefixCls}-message-title`}>
+              {getComponentFromProp(this, h, 'title')}
+            </div>
+          </div>
+          <div class={`${prefixCls}-buttons`}>
+            <Button onClick={this.onCancel} size='small'>
+              {getComponentFromProp(this, h, 'cancelText')}
+            </Button>
+            <Button onClick={this.onConfirm} type={okType} size='small'>
+              {getComponentFromProp(this, h, 'okText')}
+            </Button>
+          </div>
+        </div>
+      </div>
+    )
+    return (
+      <Tooltip
+        {...tooltipProps}
+      >
+        <template slot='title'>
+          {overlay}
+        </template>
+        {this.$slots.default}
+      </Tooltip>
+    )
+  },
+}
+
+</script>
diff --git a/components/popconfirm/index.zh-CN.md b/components/popconfirm/index.zh-CN.md
index bbdf06f3a..9442b1fac 100644
--- a/components/popconfirm/index.zh-CN.md
+++ b/components/popconfirm/index.zh-CN.md
@@ -17,8 +17,8 @@ title: Popconfirm
 
 | 参数 | 说明 | 类型 | 默认值 |
 | --- | --- | --- | --- |
-| cancelText | 取消按钮文字 | string | 取消 |
-| okText | 确认按钮文字 | string | 确定 |
+| cancelText | 取消按钮文字 | string\|function\|slot | 取消 |
+| okText | 确认按钮文字 | string\|function\|slot | 确定 |
 | okType | 确认按钮类型 | string | primary |
 | title | 确认框的描述 | string\|function\|slot | 无 |
 
diff --git a/components/popover/index.vue b/components/popover/index.vue
index da31a1806..1089d6f0f 100644
--- a/components/popover/index.vue
+++ b/components/popover/index.vue
@@ -2,7 +2,7 @@
 import Tooltip from '../tooltip'
 import abstractTooltipProps from '../tooltip/abstractTooltipProps'
 import PropTypes from '../_util/vue-types'
-import { getOptionProps } from '../_util/props-util'
+import { getOptionProps, getComponentFromProp } from '../_util/props-util'
 
 export default {
   name: 'popover',
@@ -21,26 +21,10 @@ export default {
     getPopupDomNode () {
       return this.$refs.tooltip.getPopupDomNode()
     },
-    getOverlay (h) {
-      const { title, prefixCls, content, $slots } = this
-      return (
-        <div>
-          {(title || $slots.title) &&
-            <div class={`${prefixCls}-title`}>
-              {typeof title === 'function' ? title(h) : title}
-              {$slots.title}
-            </div>
-          }
-          <div class={`${prefixCls}-inner-content`}>
-            {typeof content === 'function' ? content(h) : content}
-            {$slots.content}
-          </div>
-        </div>
-      )
-    },
   },
 
   render (h) {
+    const { title, prefixCls, content, $slots } = this
     const props = getOptionProps(this)
     delete props.title
     delete props.content
@@ -56,7 +40,16 @@ export default {
         {...tooltipProps}
       >
         <template slot='title'>
-          {this.getOverlay(h)}
+          <div>
+            {(title || $slots.title) &&
+            <div class={`${prefixCls}-title`}>
+              {getComponentFromProp(this, h, 'title')}
+            </div>
+            }
+            <div class={`${prefixCls}-inner-content`}>
+              {getComponentFromProp(this, h, 'content')}
+            </div>
+          </div>
         </template>
         {this.$slots.default}
       </Tooltip>
diff --git a/components/style.js b/components/style.js
index 82780a16e..108572eed 100644
--- a/components/style.js
+++ b/components/style.js
@@ -12,5 +12,6 @@ import './tabs/style'
 import './input/style'
 import './tooltip/style'
 import './popover/style'
+import './popconfirm/style'
 
 import './menu/style'
diff --git a/contributors.md b/contributors.md
index a87464170..483bcb023 100644
--- a/contributors.md
+++ b/contributors.md
@@ -9,7 +9,7 @@ Tabs | done
 Tag | done
 ToolTip | done
 Popconfirm
-Popover
+Popover | done
 Menu
 Carousel
 Mention