add affix demo
							parent
							
								
									08f9f6afbb
								
							
						
					
					
						commit
						dd1266ee61
					
				|  | @ -0,0 +1,23 @@ | |||
| <cn> | ||||
| #### 基本 | ||||
| 最简单的用法。 | ||||
| </cn> | ||||
| 
 | ||||
| <us> | ||||
| #### basic | ||||
| The simplest usage. | ||||
| </us> | ||||
| 
 | ||||
| ```html | ||||
| <template> | ||||
|   <div> | ||||
|     <a-affix> | ||||
|       <a-button type="primary">Affix top</a-button> | ||||
|     </a-affix> | ||||
|     <br /> | ||||
|     <a-affix :offsetBottom="0"> | ||||
|       <a-button type="primary">Affix bottom</a-button> | ||||
|     </a-affix> | ||||
|   </div> | ||||
| </template> | ||||
| ``` | ||||
|  | @ -0,0 +1,37 @@ | |||
| <script> | ||||
| import Basic from './basic' | ||||
| import Onchange from './on-change' | ||||
| import Target from './target' | ||||
| import CN from '../index.zh-CN.md' | ||||
| import US from '../index.en-US.md' | ||||
| const md = { | ||||
|   cn: `# Affix 固钉 | ||||
|           将页面元素钉在可视范围。 | ||||
| ## 何时使用 | ||||
| 当内容区域比较长,需要滚动页面时,这部分内容对应的操作或者导航需要在滚动范围内始终展现。常用于侧边菜单和按钮组合。 | ||||
| 页面可视范围过小时,慎用此功能以免遮挡页面内容。 | ||||
|           ## 代码演示`, | ||||
|   us: `# Affix | ||||
|           Make an element stick to viewport. | ||||
| ## When To Use | ||||
| When user browses a long web page, some content need to stick to the viewport. This is common for menus and actions. | ||||
| Please note that Affix should not cover other content on the page, especially when the size of the viewport is small. | ||||
| `, | ||||
| } | ||||
| export default { | ||||
|   render () { | ||||
|     return ( | ||||
|       <div> | ||||
|         <md cn={md.cn} us={md.us}/> | ||||
|         <Basic /> | ||||
|         <Onchange /> | ||||
|         <Target /> | ||||
|         <api> | ||||
|           <CN slot='cn' /> | ||||
|           <US/> | ||||
|         </api> | ||||
|       </div> | ||||
|     ) | ||||
|   }, | ||||
| } | ||||
| </script> | ||||
|  | @ -0,0 +1,26 @@ | |||
| <cn> | ||||
| #### 固定状态改变的回调 | ||||
| 可以获得是否固定的状态。 | ||||
| </cn> | ||||
| 
 | ||||
| <us> | ||||
| #### Callback | ||||
| Callback with affixed state. | ||||
| </us> | ||||
| 
 | ||||
| ```html | ||||
| <template> | ||||
|   <a-affix :offsetTop="120" @change="change"> | ||||
|     <a-button>120px to affix top</a-button> | ||||
|   </a-affix> | ||||
| </template> | ||||
| <script> | ||||
| export default { | ||||
|   methods: { | ||||
|     change(affixed) { | ||||
|       console.log(affixed) | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
| ``` | ||||
|  | @ -0,0 +1,34 @@ | |||
| <cn> | ||||
| #### 滚动容器 | ||||
| 用 `target` 设置 `Affix` 需要监听其滚动事件的元素,默认为 `window`。 | ||||
| </cn> | ||||
| 
 | ||||
| <us> | ||||
| #### Container to scroll. | ||||
| Set a `target` for 'Affix', which is listen to scroll event of target element (default is `window`). | ||||
| </us> | ||||
| 
 | ||||
| ```html | ||||
| <template> | ||||
|   <div id="components-affix-demo-target" class="scrollable-container" ref="container"> | ||||
|     <div class="background"> | ||||
|       <a-affix :target="() => this.$refs.container"> | ||||
|         <a-button type="primary"> | ||||
|           Fixed at the top of container | ||||
|         </a-button> | ||||
|       </a-affix> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
| <style> | ||||
| #components-affix-demo-target.scrollable-container { | ||||
|   height: 100px; | ||||
|   overflow-y: scroll; | ||||
| } | ||||
| #components-affix-demo-target .background { | ||||
|   padding-top: 60px; | ||||
|   height: 300px; | ||||
|   background-image: url('https://zos.alipayobjects.com/rmsportal/RmjwQiJorKyobvI.jpg'); | ||||
| } | ||||
| </style> | ||||
| ``` | ||||
|  | @ -0,0 +1,21 @@ | |||
| 
 | ||||
| ## API | ||||
| 
 | ||||
| | Property | Description | Type | Default | | ||||
| | -------- | ----------- | ---- | ------- | | ||||
| | offsetBottom | Pixels to offset from bottom when calculating position of scroll | number | - | | ||||
| | offsetTop | Pixels to offset from top when calculating position of scroll | number | 0 | | ||||
| | target | specifies the scrollable area dom node | () => HTMLElement | () => window | | ||||
| 
 | ||||
| ### events | ||||
| | Events Name | Description | Arguments | | ||||
| | --- | --- | --- | | ||||
| | onChange | Callback for when affix state is changed | Function(affixed) | | ||||
| 
 | ||||
| **Note:** Children of `Affix` can not be `position: absolute`, but you can set `Affix` as `position: absolute`: | ||||
| 
 | ||||
| ````html | ||||
| <a-affix :style="{ position: 'absolute', top: y, left: x}"> | ||||
|   ... | ||||
| </a-affix> | ||||
| ```` | ||||
|  | @ -5,7 +5,8 @@ import classNames from 'classnames' | |||
| import shallowequal from 'shallowequal' | ||||
| import omit from 'omit.js' | ||||
| import getScroll from '../_util/getScroll' | ||||
| import { throttleByAnimationFrameDecorator } from '../_util/throttleByAnimationFrame' | ||||
| import BaseMixin from '../_util/BaseMixin' | ||||
| import throttleByAnimationFrame from '../_util/throttleByAnimationFrame' | ||||
| 
 | ||||
| function getTargetRect (target) { | ||||
|   return target !== window | ||||
|  | @ -55,14 +56,10 @@ const AffixProps = { | |||
|   prefixCls: PropTypes.string, | ||||
| } | ||||
| 
 | ||||
| // export interface AffixState { | ||||
| //   affixStyle: React.CSSProperties | undefined; | ||||
| //   placeholderStyle: React.CSSProperties | undefined; | ||||
| // } | ||||
| 
 | ||||
| export default { | ||||
|   name: 'Affix', | ||||
|   props: AffixProps, | ||||
|   mixins: [BaseMixin], | ||||
|   data () { | ||||
|     this.events = [ | ||||
|       'resize', | ||||
|  | @ -79,6 +76,9 @@ export default { | |||
|       placeholderStyle: undefined, | ||||
|     } | ||||
|   }, | ||||
|   beforeMount () { | ||||
|     this.updatePosition = throttleByAnimationFrame(this.updatePosition) | ||||
|   }, | ||||
|   mounted () { | ||||
|     const target = this.target || getDefaultTarget | ||||
|     // Wait for parent component ref has its value | ||||
|  | @ -96,7 +96,6 @@ export default { | |||
|       }) | ||||
|     }, | ||||
|   }, | ||||
| 
 | ||||
|   beforeDestroy () { | ||||
|     this.clearEventListeners() | ||||
|     clearTimeout(this.timeout) | ||||
|  | @ -130,7 +129,6 @@ export default { | |||
|       this.setState({ placeholderStyle: placeholderStyle }) | ||||
|     }, | ||||
| 
 | ||||
|     // @throttleByAnimationFrameDecorator() | ||||
|     updatePosition (e) { | ||||
|       let { offsetTop } = this | ||||
|       const { offsetBottom, offset, target = getDefaultTarget } = this | ||||
|  | @ -244,4 +242,4 @@ export default { | |||
|   }, | ||||
| } | ||||
| 
 | ||||
| </script> | ||||
| </script> | ||||
|  |  | |||
|  | @ -0,0 +1,23 @@ | |||
| 
 | ||||
| ## API | ||||
| 
 | ||||
| | 成员 | 说明 | 类型 | 默认值 | | ||||
| | --- | --- | --- | --- | | ||||
| | offsetBottom | 距离窗口底部达到指定偏移量后触发 | number |  | | ||||
| | offsetTop | 距离窗口顶部达到指定偏移量后触发 | number |  | | ||||
| | target | 设置 `Affix` 需要监听其滚动事件的元素,值为一个返回对应 DOM 元素的函数 | () => HTMLElement | () => window | | ||||
| 
 | ||||
| 
 | ||||
| ### 事件 | ||||
| | 事件名称 | 说明 | 回调参数 | | ||||
| | --- | --- | --- | | ||||
| | change | 固定状态改变时触发的回调函数 | Function(affixed) | | ||||
| 
 | ||||
| 
 | ||||
| **注意:**`Affix` 内的元素不要使用绝对定位,如需要绝对定位的效果,可以直接设置 `Affix` 为绝对定位: | ||||
| 
 | ||||
| ````html | ||||
| <a-affix :style="{ position: 'absolute', top: y, left: x}"> | ||||
|   ... | ||||
| </a-affix> | ||||
| ```` | ||||
|  | @ -11,7 +11,7 @@ There are `primary` button, `default` button, `dashed` button and `danger` butto | |||
| 
 | ||||
| ```html | ||||
| <template> | ||||
|   <div class='test'> | ||||
|   <div> | ||||
|     <a-button type="primary">Primary</a-button> | ||||
|     <a-button>Default</a-button> | ||||
|     <a-button type="dashed">Dashed</a-button> | ||||
|  |  | |||
|  | @ -1,9 +1,8 @@ | |||
| <script> | ||||
|   import { Tabs } from 'antd' | ||||
|   import omit from 'omit.js' | ||||
|   import PropTypes from '../_util/vue-types' | ||||
|   import addEventListener from '../_util/Dom/addEventListener' | ||||
|   import { hasProp, getComponentFromProp, getComponentName } from '../_util/props-util' | ||||
|   import { getComponentFromProp, getComponentName } from '../_util/props-util' | ||||
|   import throttleByAnimationFrame from '../_util/throttleByAnimationFrame' | ||||
|   import BaseMixin from '../_util/BaseMixin' | ||||
| 
 | ||||
|  | @ -18,32 +17,25 @@ | |||
|       bordered: PropTypes.bool.def(true), | ||||
|       bodyStyle: PropTypes.object, | ||||
|       loading: PropTypes.bool.def(false), | ||||
|       noHovering: PropTypes.bool.def(false), | ||||
|       hoverable: PropTypes.bool.def(false), | ||||
|       type: PropTypes.string, | ||||
|       actions: PropTypes.any, | ||||
|       tabList: PropTypes.array, | ||||
|     }, | ||||
|     data () { | ||||
|       this.updateWiderPaddingCalled = false | ||||
|       return { | ||||
|         widerPadding: false, | ||||
|         updateWiderPaddingCalled: false, | ||||
|       } | ||||
|     }, | ||||
|     mounted () { | ||||
|     beforeMount () { | ||||
|       this.updateWiderPadding = throttleByAnimationFrame(this.updateWiderPadding) | ||||
|     }, | ||||
|     mounted () { | ||||
|       this.updateWiderPadding() | ||||
|       this.resizeEvent = addEventListener(window, 'resize', this.updateWiderPadding) | ||||
| 
 | ||||
|       // if (hasProp(this, 'noHovering')) { | ||||
|       //   warning( | ||||
|       //     !this.noHovering, | ||||
|       //     '`noHovering` of Card is deperated, you can remove it safely or use `hoverable` instead.', | ||||
|       //   ) | ||||
|       //   warning(!!this.noHovering, '`noHovering={false}` of Card is deperated, use `hoverable` instead.') | ||||
|       // } | ||||
|     }, | ||||
|     beforeMount () { | ||||
|     beforeDestroy () { | ||||
|       if (this.resizeEvent) { | ||||
|         this.resizeEvent.remove() | ||||
|       } | ||||
|  | @ -84,19 +76,11 @@ | |||
|         }) | ||||
|         return containGrid | ||||
|       }, | ||||
|       // For 2.x compatible | ||||
|       getCompatibleHoverable () { | ||||
|         const { noHovering, hoverable } = this.$props | ||||
|         if (hasProp(this, 'noHovering')) { | ||||
|           return !noHovering || hoverable | ||||
|         } | ||||
|         return !!hoverable | ||||
|       }, | ||||
|     }, | ||||
|     render () { | ||||
|       const { | ||||
|         prefixCls = 'ant-card', extra, bodyStyle, title, loading, | ||||
|         bordered = true, type, tabList, ...others | ||||
|         bordered = true, type, tabList, hoverable, | ||||
|       } = this.$props | ||||
| 
 | ||||
|       const { $slots } = this | ||||
|  | @ -105,7 +89,7 @@ | |||
|         [`${prefixCls}`]: true, | ||||
|         [`${prefixCls}-loading`]: loading, | ||||
|         [`${prefixCls}-bordered`]: bordered, | ||||
|         [`${prefixCls}-hoverable`]: this.getCompatibleHoverable(), | ||||
|         [`${prefixCls}-hoverable`]: !!hoverable, | ||||
|         [`${prefixCls}-wider-padding`]: this.widerPadding, | ||||
|         [`${prefixCls}-padding-transition`]: this.updateWiderPaddingCalled, | ||||
|         [`${prefixCls}-contain-grid`]: this.isContainGrid($slots.default), | ||||
|  | @ -166,11 +150,9 @@ | |||
|       ) | ||||
|       const actions = getComponentFromProp(this, 'actions') | ||||
|       const actionDom = actions || null | ||||
|       const divProps = omit(others, [ | ||||
|         'tabChange', | ||||
|       ]) | ||||
| 
 | ||||
|       return ( | ||||
|         <div {...divProps} class={classString} ref='cardContainerRef'> | ||||
|         <div class={classString} ref='cardContainerRef'> | ||||
|           {head} | ||||
|           {coverDom} | ||||
|           {children ? body : null} | ||||
|  |  | |||
|  | @ -89,3 +89,5 @@ export { default as LocaleProvider } from './locale-provider' | |||
| 
 | ||||
| export { default as AutoComplete } from './auto-complete' | ||||
| 
 | ||||
| export { default as Affix } from './affix' | ||||
| 
 | ||||
|  |  | |||
|  | @ -24,3 +24,4 @@ import './spin/style' | |||
| import './select/style' | ||||
| import './switch/style' | ||||
| import './auto-complete/style' | ||||
| import './affix/style' | ||||
|  |  | |||
|  | @ -0,0 +1,313 @@ | |||
| <script> | ||||
| import React, { Component, cloneElement } from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import Trigger from 'rc-trigger'; | ||||
| import Menus from './Menus'; | ||||
| import KeyCode from 'rc-util/lib/KeyCode'; | ||||
| import arrayTreeFilter from 'array-tree-filter'; | ||||
| import shallowEqualArrays from 'shallow-equal/arrays'; | ||||
| 
 | ||||
| const BUILT_IN_PLACEMENTS = { | ||||
|   bottomLeft: { | ||||
|     points: ['tl', 'bl'], | ||||
|     offset: [0, 4], | ||||
|     overflow: { | ||||
|       adjustX: 1, | ||||
|       adjustY: 1, | ||||
|     }, | ||||
|   }, | ||||
|   topLeft: { | ||||
|     points: ['bl', 'tl'], | ||||
|     offset: [0, -4], | ||||
|     overflow: { | ||||
|       adjustX: 1, | ||||
|       adjustY: 1, | ||||
|     }, | ||||
|   }, | ||||
|   bottomRight: { | ||||
|     points: ['tr', 'br'], | ||||
|     offset: [0, 4], | ||||
|     overflow: { | ||||
|       adjustX: 1, | ||||
|       adjustY: 1, | ||||
|     }, | ||||
|   }, | ||||
|   topRight: { | ||||
|     points: ['br', 'tr'], | ||||
|     offset: [0, -4], | ||||
|     overflow: { | ||||
|       adjustX: 1, | ||||
|       adjustY: 1, | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
| 
 | ||||
| class Cascader extends Component { | ||||
|   constructor(props) { | ||||
|     super(props); | ||||
|     let initialValue = []; | ||||
|     if ('value' in props) { | ||||
|       initialValue = props.value || []; | ||||
|     } else if ('defaultValue' in props) { | ||||
|       initialValue = props.defaultValue || []; | ||||
|     } | ||||
| 
 | ||||
|     this.state = { | ||||
|       popupVisible: props.popupVisible, | ||||
|       activeValue: initialValue, | ||||
|       value: initialValue, | ||||
|     }; | ||||
|   } | ||||
|   componentWillReceiveProps(nextProps) { | ||||
|     if ('value' in nextProps && !shallowEqualArrays(this.props.value, nextProps.value)) { | ||||
|       const newValues = { | ||||
|         value: nextProps.value || [], | ||||
|         activeValue: nextProps.value || [], | ||||
|       }; | ||||
|       // allow activeValue diff from value | ||||
|       // https://github.com/ant-design/ant-design/issues/2767 | ||||
|       if ('loadData' in nextProps) { | ||||
|         delete newValues.activeValue; | ||||
|       } | ||||
|       this.setState(newValues); | ||||
|     } | ||||
|     if ('popupVisible' in nextProps) { | ||||
|       this.setState({ | ||||
|         popupVisible: nextProps.popupVisible, | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
|   getPopupDOMNode() { | ||||
|     return this.trigger.getPopupDomNode(); | ||||
|   } | ||||
|   getCurrentLevelOptions() { | ||||
|     const { options } = this.props; | ||||
|     const { activeValue = [] } = this.state; | ||||
|     const result = arrayTreeFilter(options, (o, level) => o.value === activeValue[level]); | ||||
|     if (result[result.length - 2]) { | ||||
|       return result[result.length - 2].children; | ||||
|     } | ||||
|     return [...options].filter(o => !o.disabled); | ||||
|   } | ||||
|   getActiveOptions(activeValue) { | ||||
|     return arrayTreeFilter(this.props.options, (o, level) => o.value === activeValue[level]); | ||||
|   } | ||||
|   setPopupVisible = (popupVisible) => { | ||||
|     if (!('popupVisible' in this.props)) { | ||||
|       this.setState({ popupVisible }); | ||||
|     } | ||||
|     // sync activeValue with value when panel open | ||||
|     if (popupVisible && !this.state.visible) { | ||||
|       this.setState({ | ||||
|         activeValue: this.state.value, | ||||
|       }); | ||||
|     } | ||||
|     this.props.onPopupVisibleChange(popupVisible); | ||||
|   } | ||||
|   handleChange = (options, setProps, e) => { | ||||
|     if (e.type !== 'keydown' || e.keyCode === KeyCode.ENTER) { | ||||
|       this.props.onChange(options.map(o => o.value), options); | ||||
|       this.setPopupVisible(setProps.visible); | ||||
|     } | ||||
|   } | ||||
|   handlePopupVisibleChange = (popupVisible) => { | ||||
|     this.setPopupVisible(popupVisible); | ||||
|   } | ||||
|   handleMenuSelect = (targetOption, menuIndex, e) => { | ||||
|     // Keep focused state for keyboard support | ||||
|     const triggerNode = this.trigger.getRootDomNode(); | ||||
|     if (triggerNode && triggerNode.focus) { | ||||
|       triggerNode.focus(); | ||||
|     } | ||||
|     const { changeOnSelect, loadData, expandTrigger } = this.props; | ||||
|     if (!targetOption || targetOption.disabled) { | ||||
|       return; | ||||
|     } | ||||
|     let { activeValue } = this.state; | ||||
|     activeValue = activeValue.slice(0, menuIndex + 1); | ||||
|     activeValue[menuIndex] = targetOption.value; | ||||
|     const activeOptions = this.getActiveOptions(activeValue); | ||||
|     if (targetOption.isLeaf === false && !targetOption.children && loadData) { | ||||
|       if (changeOnSelect) { | ||||
|         this.handleChange(activeOptions, { visible: true }, e); | ||||
|       } | ||||
|       this.setState({ activeValue }); | ||||
|       loadData(activeOptions); | ||||
|       return; | ||||
|     } | ||||
|     const newState = {}; | ||||
|     if (!targetOption.children || !targetOption.children.length) { | ||||
|       this.handleChange(activeOptions, { visible: false }, e); | ||||
|       // set value to activeValue when select leaf option | ||||
|       newState.value = activeValue; | ||||
|       // add e.type judgement to prevent `onChange` being triggered by mouseEnter | ||||
|     } else if (changeOnSelect && (e.type === 'click' || e.type === 'keydown')) { | ||||
|       if (expandTrigger === 'hover') { | ||||
|         this.handleChange(activeOptions, { visible: false }, e); | ||||
|       } else { | ||||
|         this.handleChange(activeOptions, { visible: true }, e); | ||||
|       } | ||||
|       // set value to activeValue on every select | ||||
|       newState.value = activeValue; | ||||
|     } | ||||
|     newState.activeValue = activeValue; | ||||
|     //  not change the value by keyboard | ||||
|     if ('value' in this.props || | ||||
|         (e.type === 'keydown' && e.keyCode !== KeyCode.ENTER)) { | ||||
|       delete newState.value; | ||||
|     } | ||||
|     this.setState(newState); | ||||
|   } | ||||
|   handleKeyDown = (e) => { | ||||
|     const { children } = this.props; | ||||
|     // https://github.com/ant-design/ant-design/issues/6717 | ||||
|     // Don't bind keyboard support when children specify the onKeyDown | ||||
|     if (children && children.props.onKeyDown) { | ||||
|       children.props.onKeyDown(e); | ||||
|       return; | ||||
|     } | ||||
|     const activeValue = [...this.state.activeValue]; | ||||
|     const currentLevel = activeValue.length - 1 < 0 ? 0 : activeValue.length - 1; | ||||
|     const currentOptions = this.getCurrentLevelOptions(); | ||||
|     const currentIndex = currentOptions.map(o => o.value).indexOf(activeValue[currentLevel]); | ||||
|     if (e.keyCode !== KeyCode.DOWN && | ||||
|         e.keyCode !== KeyCode.UP && | ||||
|         e.keyCode !== KeyCode.LEFT && | ||||
|         e.keyCode !== KeyCode.RIGHT && | ||||
|         e.keyCode !== KeyCode.ENTER && | ||||
|         e.keyCode !== KeyCode.BACKSPACE && | ||||
|         e.keyCode !== KeyCode.ESC) { | ||||
|       return; | ||||
|     } | ||||
|     // Press any keys above to reopen menu | ||||
|     if (!this.state.popupVisible && | ||||
|         e.keyCode !== KeyCode.BACKSPACE && | ||||
|         e.keyCode !== KeyCode.LEFT && | ||||
|         e.keyCode !== KeyCode.RIGHT && | ||||
|         e.keyCode !== KeyCode.ESC) { | ||||
|       this.setPopupVisible(true); | ||||
|       return; | ||||
|     } | ||||
|     if (e.keyCode === KeyCode.DOWN || e.keyCode === KeyCode.UP) { | ||||
|       let nextIndex = currentIndex; | ||||
|       if (nextIndex !== -1) { | ||||
|         if (e.keyCode === KeyCode.DOWN) { | ||||
|           nextIndex += 1; | ||||
|           nextIndex = nextIndex >= currentOptions.length ? 0 : nextIndex; | ||||
|         } else { | ||||
|           nextIndex -= 1; | ||||
|           nextIndex = nextIndex < 0 ? currentOptions.length - 1 : nextIndex; | ||||
|         } | ||||
|       } else { | ||||
|         nextIndex = 0; | ||||
|       } | ||||
|       activeValue[currentLevel] = currentOptions[nextIndex].value; | ||||
|     } else if (e.keyCode === KeyCode.LEFT || e.keyCode === KeyCode.BACKSPACE) { | ||||
|       activeValue.splice(activeValue.length - 1, 1); | ||||
|     } else if (e.keyCode === KeyCode.RIGHT) { | ||||
|       if (currentOptions[currentIndex] && currentOptions[currentIndex].children) { | ||||
|         activeValue.push(currentOptions[currentIndex].children[0].value); | ||||
|       } | ||||
|     } else if (e.keyCode === KeyCode.ESC) { | ||||
|       this.setPopupVisible(false); | ||||
|       return; | ||||
|     } | ||||
|     if (!activeValue || activeValue.length === 0) { | ||||
|       this.setPopupVisible(false); | ||||
|     } | ||||
|     const activeOptions = this.getActiveOptions(activeValue); | ||||
|     const targetOption = activeOptions[activeOptions.length - 1]; | ||||
|     this.handleMenuSelect(targetOption, activeOptions.length - 1, e); | ||||
| 
 | ||||
|     if (this.props.onKeyDown) { | ||||
|       this.props.onKeyDown(e); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   saveTrigger = (node) => { | ||||
|     this.trigger = node; | ||||
|   } | ||||
| 
 | ||||
|   render() { | ||||
|     const { | ||||
|       prefixCls, transitionName, popupClassName, options, disabled, | ||||
|       builtinPlacements, popupPlacement, children, ...restProps, | ||||
|     } = this.props; | ||||
|     // Did not show popup when there is no options | ||||
|     let menus = <div />; | ||||
|     let emptyMenuClassName = ''; | ||||
|     if (options && options.length > 0) { | ||||
|       menus = ( | ||||
|         <Menus | ||||
|           {...this.props} | ||||
|           value={this.state.value} | ||||
|           activeValue={this.state.activeValue} | ||||
|           onSelect={this.handleMenuSelect} | ||||
|           visible={this.state.popupVisible} | ||||
|         /> | ||||
|       ); | ||||
|     } else { | ||||
|       emptyMenuClassName = ` ${prefixCls}-menus-empty`; | ||||
|     } | ||||
|     return ( | ||||
|       <Trigger | ||||
|         ref={this.saveTrigger} | ||||
|         {...restProps} | ||||
|         options={options} | ||||
|         disabled={disabled} | ||||
|         popupPlacement={popupPlacement} | ||||
|         builtinPlacements={builtinPlacements} | ||||
|         popupTransitionName={transitionName} | ||||
|         action={disabled ? [] : ['click']} | ||||
|         popupVisible={disabled ? false : this.state.popupVisible} | ||||
|         onPopupVisibleChange={this.handlePopupVisibleChange} | ||||
|         prefixCls={`${prefixCls}-menus`} | ||||
|         popupClassName={popupClassName + emptyMenuClassName} | ||||
|         popup={menus} | ||||
|       > | ||||
|         {cloneElement(children, { | ||||
|           onKeyDown: this.handleKeyDown, | ||||
|           tabIndex: disabled ? undefined : 0, | ||||
|         })} | ||||
|       </Trigger> | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| Cascader.defaultProps = { | ||||
|   options: [], | ||||
|   onChange() {}, | ||||
|   onPopupVisibleChange() {}, | ||||
|   disabled: false, | ||||
|   transitionName: '', | ||||
|   prefixCls: 'rc-cascader', | ||||
|   popupClassName: '', | ||||
|   popupPlacement: 'bottomLeft', | ||||
|   builtinPlacements: BUILT_IN_PLACEMENTS, | ||||
|   expandTrigger: 'click', | ||||
| }; | ||||
| 
 | ||||
| Cascader.propTypes = { | ||||
|   value: PropTypes.array, | ||||
|   defaultValue: PropTypes.array, | ||||
|   options: PropTypes.array.isRequired, | ||||
|   onChange: PropTypes.func, | ||||
|   onPopupVisibleChange: PropTypes.func, | ||||
|   popupVisible: PropTypes.bool, | ||||
|   disabled: PropTypes.bool, | ||||
|   transitionName: PropTypes.string, | ||||
|   popupClassName: PropTypes.string, | ||||
|   popupPlacement: PropTypes.string, | ||||
|   prefixCls: PropTypes.string, | ||||
|   dropdownMenuColumnStyle: PropTypes.object, | ||||
|   builtinPlacements: PropTypes.object, | ||||
|   loadData: PropTypes.func, | ||||
|   changeOnSelect: PropTypes.bool, | ||||
|   children: PropTypes.node, | ||||
|   onKeyDown: PropTypes.func, | ||||
|   expandTrigger: PropTypes.string, | ||||
| }; | ||||
| 
 | ||||
| export default Cascader; | ||||
| 
 | ||||
| </script> | ||||
|  | @ -0,0 +1,156 @@ | |||
| <script> | ||||
| import React from 'react' | ||||
| import PropTypes from 'prop-types' | ||||
| import arrayTreeFilter from 'array-tree-filter' | ||||
| import { findDOMNode } from 'react-dom' | ||||
| 
 | ||||
| class Menus extends React.Component { | ||||
|   constructor (props) { | ||||
|     super(props) | ||||
| 
 | ||||
|     this.menuItems = {} | ||||
|   } | ||||
| 
 | ||||
|   componentDidMount () { | ||||
|     this.scrollActiveItemToView() | ||||
|   } | ||||
| 
 | ||||
|   componentDidUpdate (prevProps) { | ||||
|     if (!prevProps.visible && this.props.visible) { | ||||
|       this.scrollActiveItemToView() | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   getOption (option, menuIndex) { | ||||
|     const { prefixCls, expandTrigger } = this.props | ||||
|     const onSelect = this.props.onSelect.bind(this, option, menuIndex) | ||||
|     let expandProps = { | ||||
|       onClick: onSelect, | ||||
|     } | ||||
|     let menuItemCls = `${prefixCls}-menu-item` | ||||
|     const hasChildren = option.children && option.children.length > 0 | ||||
|     if (hasChildren || option.isLeaf === false) { | ||||
|       menuItemCls += ` ${prefixCls}-menu-item-expand` | ||||
|     } | ||||
|     if (expandTrigger === 'hover' && hasChildren) { | ||||
|       expandProps = { | ||||
|         onMouseEnter: this.delayOnSelect.bind(this, onSelect), | ||||
|         onMouseLeave: this.delayOnSelect.bind(this), | ||||
|         onClick: onSelect, | ||||
|       } | ||||
|     } | ||||
|     if (this.isActiveOption(option, menuIndex)) { | ||||
|       menuItemCls += ` ${prefixCls}-menu-item-active` | ||||
|       expandProps.ref = this.saveMenuItem(menuIndex) | ||||
|     } | ||||
|     if (option.disabled) { | ||||
|       menuItemCls += ` ${prefixCls}-menu-item-disabled` | ||||
|     } | ||||
|     if (option.loading) { | ||||
|       menuItemCls += ` ${prefixCls}-menu-item-loading` | ||||
|     } | ||||
|     let title = '' | ||||
|     if (option.title) { | ||||
|       title = option.title | ||||
|     } else if (typeof option.label === 'string') { | ||||
|       title = option.label | ||||
|     } | ||||
|     return ( | ||||
|       <li | ||||
|         key={option.value} | ||||
|         className={menuItemCls} | ||||
|         title={title} | ||||
|         {...expandProps} | ||||
|       > | ||||
|         {option.label} | ||||
|       </li> | ||||
|     ) | ||||
|   } | ||||
| 
 | ||||
|   getActiveOptions (values) { | ||||
|     const activeValue = values || this.props.activeValue | ||||
|     const options = this.props.options | ||||
|     return arrayTreeFilter(options, (o, level) => o.value === activeValue[level]) | ||||
|   } | ||||
| 
 | ||||
|   getShowOptions () { | ||||
|     const { options } = this.props | ||||
|     const result = this.getActiveOptions() | ||||
|       .map(activeOption => activeOption.children) | ||||
|       .filter(activeOption => !!activeOption) | ||||
|     result.unshift(options) | ||||
|     return result | ||||
|   } | ||||
| 
 | ||||
|   delayOnSelect (onSelect, ...args) { | ||||
|     if (this.delayTimer) { | ||||
|       clearTimeout(this.delayTimer) | ||||
|       this.delayTimer = null | ||||
|     } | ||||
|     if (typeof onSelect === 'function') { | ||||
|       this.delayTimer = setTimeout(() => { | ||||
|         onSelect(args) | ||||
|         this.delayTimer = null | ||||
|       }, 150) | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   scrollActiveItemToView () { | ||||
|     // scroll into view | ||||
|     const optionsLength = this.getShowOptions().length | ||||
|     for (let i = 0; i < optionsLength; i++) { | ||||
|       const itemComponent = this.menuItems[i] | ||||
|       if (itemComponent) { | ||||
|         const target = findDOMNode(itemComponent) | ||||
|         target.parentNode.scrollTop = target.offsetTop | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   isActiveOption (option, menuIndex) { | ||||
|     const { activeValue = [] } = this.props | ||||
|     return activeValue[menuIndex] === option.value | ||||
|   } | ||||
| 
 | ||||
|   saveMenuItem = (index) => (node) => { | ||||
|     this.menuItems[index] = node | ||||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { prefixCls, dropdownMenuColumnStyle } = this.props | ||||
|     return ( | ||||
|       <div> | ||||
|         {this.getShowOptions().map((options, menuIndex) => | ||||
|           <ul className={`${prefixCls}-menu`} key={menuIndex} style={dropdownMenuColumnStyle}> | ||||
|             {options.map(option => this.getOption(option, menuIndex))} | ||||
|           </ul> | ||||
|         )} | ||||
|       </div> | ||||
|     ) | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| Menus.defaultProps = { | ||||
|   options: [], | ||||
|   value: [], | ||||
|   activeValue: [], | ||||
|   onSelect () {}, | ||||
|   prefixCls: 'rc-cascader-menus', | ||||
|   visible: false, | ||||
|   expandTrigger: 'click', | ||||
| } | ||||
| 
 | ||||
| Menus.propTypes = { | ||||
|   value: PropTypes.array, | ||||
|   activeValue: PropTypes.array, | ||||
|   options: PropTypes.array.isRequired, | ||||
|   prefixCls: PropTypes.string, | ||||
|   expandTrigger: PropTypes.string, | ||||
|   onSelect: PropTypes.func, | ||||
|   visible: PropTypes.bool, | ||||
|   dropdownMenuColumnStyle: PropTypes.object, | ||||
| } | ||||
| 
 | ||||
| export default Menus | ||||
| 
 | ||||
| </script> | ||||
|  | @ -0,0 +1,167 @@ | |||
| .effect() { | ||||
|   animation-duration: .3s; | ||||
|   animation-fill-mode: both; | ||||
|   transform-origin: 0 0; | ||||
| } | ||||
| 
 | ||||
| .rc-cascader { | ||||
|   font-size: 12px; | ||||
|   &-menus { | ||||
|     font-size: 12px; | ||||
|     overflow: hidden; | ||||
|     background: #fff; | ||||
|     position: absolute; | ||||
|     border: 1px solid #d9d9d9; | ||||
|     border-radius: 6px; | ||||
|     box-shadow: 0 0 4px rgba(0,0,0,0.17); | ||||
|     white-space: nowrap; | ||||
| 
 | ||||
|     &-hidden { | ||||
|       display: none; | ||||
|     } | ||||
| 
 | ||||
|     &.slide-up-enter, &.slide-up-appear { | ||||
|       .effect(); | ||||
|       opacity: 0; | ||||
|       animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); | ||||
|       animation-play-state: paused; | ||||
|     } | ||||
| 
 | ||||
|     &.slide-up-leave { | ||||
|       .effect(); | ||||
|       opacity: 1; | ||||
|       animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34); | ||||
|       animation-play-state: paused; | ||||
|     } | ||||
| 
 | ||||
|     &.slide-up-enter.slide-up-enter-active&-placement-bottomLeft, | ||||
|     &.slide-up-appear.slide-up-appear-active&-placement-bottomLeft { | ||||
|       animation-name: SlideUpIn; | ||||
|       animation-play-state: running; | ||||
|     } | ||||
| 
 | ||||
|     &.slide-up-enter.slide-up-enter-active&-placement-topLeft, | ||||
|     &.slide-up-appear.slide-up-appear-active&-placement-topLeft { | ||||
|       animation-name: SlideDownIn; | ||||
|       animation-play-state: running; | ||||
|     } | ||||
| 
 | ||||
|     &.slide-up-leave.slide-up-leave-active&-placement-bottomLeft { | ||||
|       animation-name: SlideUpOut; | ||||
|       animation-play-state: running; | ||||
|     } | ||||
| 
 | ||||
|     &.slide-up-leave.slide-up-leave-active&-placement-topLeft { | ||||
|       animation-name: SlideDownOut; | ||||
|       animation-play-state: running; | ||||
|     } | ||||
|   } | ||||
|   &-menu { | ||||
|     display: inline-block; | ||||
|     width: 100px; | ||||
|     height: 192px; | ||||
|     list-style: none; | ||||
|     margin: 0; | ||||
|     padding: 0; | ||||
|     border-right: 1px solid #e9e9e9; | ||||
|     overflow: auto; | ||||
|     &:last-child { | ||||
|       border-right: 0; | ||||
|     } | ||||
|   } | ||||
|   &-menu-item { | ||||
|     height: 32px; | ||||
|     line-height: 32px; | ||||
|     padding: 0 16px; | ||||
|     cursor: pointer; | ||||
|     white-space: nowrap; | ||||
|     overflow: hidden; | ||||
|     text-overflow: ellipsis; | ||||
|     transition: all 0.3s ease; | ||||
|     position: relative; | ||||
|     &:hover { | ||||
|       background: tint(#2db7f5, 90%); | ||||
|     } | ||||
|     &-disabled { | ||||
|       cursor: not-allowed; | ||||
|       color: #ccc; | ||||
|       &:hover { | ||||
|         background: transparent; | ||||
|       } | ||||
|     } | ||||
|     &-loading:after { | ||||
|       position: absolute; | ||||
|       right: 12px; | ||||
|       content: 'loading'; | ||||
|       color: #aaa; | ||||
|       font-style: italic; | ||||
|     } | ||||
|     &-active { | ||||
|       background: tint(#2db7f5, 80%); | ||||
|       &:hover { | ||||
|         background: tint(#2db7f5, 80%); | ||||
|       } | ||||
|     } | ||||
|     &-expand { | ||||
|       position: relative; | ||||
|       &:after { | ||||
|         content: '>'; | ||||
|         font-size: 12px; | ||||
|         color: #999; | ||||
|         position: absolute; | ||||
|         right: 16px; | ||||
|         line-height: 32px; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| @keyframes SlideUpIn { | ||||
|   0% { | ||||
|     opacity: 0; | ||||
|     transform-origin: 0% 0%; | ||||
|     transform: scaleY(.8); | ||||
|   } | ||||
|   100% { | ||||
|     opacity: 1; | ||||
|     transform-origin: 0% 0%; | ||||
|     transform: scaleY(1); | ||||
|   } | ||||
| } | ||||
| @keyframes SlideUpOut { | ||||
|   0% { | ||||
|     opacity: 1; | ||||
|     transform-origin: 0% 0%; | ||||
|     transform: scaleY(1); | ||||
|   } | ||||
|   100% { | ||||
|     opacity: 0; | ||||
|     transform-origin: 0% 0%; | ||||
|     transform: scaleY(0.8); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| @keyframes SlideDownIn { | ||||
|   0% { | ||||
|     opacity: 0; | ||||
|     transform-origin: 0% 100%; | ||||
|     transform: scaleY(0.8); | ||||
|   } | ||||
|   100% { | ||||
|     opacity: 1; | ||||
|     transform-origin: 0% 100%; | ||||
|     transform: scaleY(1); | ||||
|   } | ||||
| } | ||||
| @keyframes SlideDownOut { | ||||
|   0% { | ||||
|     opacity: 1; | ||||
|     transform-origin: 0% 100%; | ||||
|     transform: scaleY(1); | ||||
|   } | ||||
|   100% { | ||||
|     opacity: 0; | ||||
|     transform-origin: 0% 100%; | ||||
|     transform: scaleY(0.8); | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,3 @@ | |||
| // export this package's api
 | ||||
| import Cascader from './Cascader' | ||||
| export default Cascader | ||||
|  | @ -15,9 +15,6 @@ export default { | |||
|   props: { | ||||
|     ...switchPropTypes, | ||||
|     prefixCls: switchPropTypes.prefixCls.def('rc-switch'), | ||||
|     checkedChildren: switchPropTypes.checkedChildren, | ||||
|     unCheckedChildren: switchPropTypes.unCheckedChildren, | ||||
|     defaultChecked: switchPropTypes.defaultChecked.def(''), | ||||
|     // onChange: switchPropTypes.onChange.def(noop), | ||||
|     // onClick: switchPropTypes.onClick.def(noop), | ||||
|   }, | ||||
|  |  | |||
|  | @ -19,6 +19,8 @@ Select | done | |||
| Input | done | ||||
| InputNumber | ||||
| AutoComplete | done | ||||
| Affix | done | ||||
| Cascader | ||||
| Modal | ||||
| Alert | ||||
| Calendar | ||||
|  | @ -32,7 +34,6 @@ Mention | |||
| ##万 | ||||
| Grid | ||||
| Col | ||||
| Affix | ||||
| BackTop | ||||
| Layout | ||||
| Anchor | ||||
|  |  | |||
|  | @ -25,4 +25,5 @@ export { default as message } from 'antd/message/demo/index.vue' | |||
| export { default as spin } from 'antd/spin/demo/index.vue' | ||||
| export { default as switch } from 'antd/switch/demo/index.vue' | ||||
| export { default as autoComplete } from 'antd/auto-complete/demo/index.vue' | ||||
| export { default as affix } from 'antd/Affix/demo/index.vue' | ||||
| 
 | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ const AsyncComp = () => { | |||
|   const hashs = window.location.hash.split('/') | ||||
|   const d = hashs[hashs.length - 1] | ||||
|   return { | ||||
|     component: import(`../components/auto-complete/demo/${d}`), | ||||
|     component: import(`../components/affix/demo/${d}`), | ||||
|   } | ||||
| } | ||||
| export default [ | ||||
|  |  | |||
|  | @ -100,6 +100,7 @@ | |||
|     "lodash.isplainobject": "^4.0.6", | ||||
|     "moment": "^2.20.1", | ||||
|     "omit.js": "^1.0.0", | ||||
|     "shallowequal": "^1.0.2", | ||||
|     "vue": "^2.5.13", | ||||
|     "vue-clipboard2": "0.0.8", | ||||
|     "vue-types": "^1.0.2", | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 tangjinzhou
						tangjinzhou