diff --git a/components/tree-select/demo/basic.md b/components/tree-select/demo/basic.md new file mode 100644 index 0000000000..4562aade6f --- /dev/null +++ b/components/tree-select/demo/basic.md @@ -0,0 +1,55 @@ +# 基本 + +- order: 0 + +最简单的用法。 + +--- + +````jsx +import { TreeSelect } from 'antd'; +const TreeNode = TreeSelect.TreeNode; + +const Demo = React.createClass({ + getInitialState() { + return { + value: 'leaf1', + }; + }, + onChange(e) { + let value; + if (e.target) { + value = e.target.value; + } else { + value = e; + } + this.setState({value}); + }, + render() { + return ( +
+

Single Select

+ + + + + + + + sss} key="random3" /> + + + +
+ ); + }, +}); + +ReactDOM.render( + +, document.getElementById('components-tree-select-demo-basic')); +```` diff --git a/components/tree-select/demo/enhance.md b/components/tree-select/demo/enhance.md new file mode 100644 index 0000000000..cd6e59543a --- /dev/null +++ b/components/tree-select/demo/enhance.md @@ -0,0 +1,89 @@ +# 更多功能 + +- order: 1 + +更多功能。 + +--- + +````jsx +import { TreeSelect } from 'antd'; +const TreeNode = TreeSelect.TreeNode; + +const x = 3; +const y = 2; +const z = 1; +const gData = []; +const generateData = (_level, _preKey, _tns) => { + const preKey = _preKey || '0'; + const tns = _tns || gData; + + const children = []; + for (let i = 0; i < x; i++) { + const key = `${preKey}-${i}`; + tns.push({title: key, key: key}); + if (i < y) { + children.push(key); + } + } + if (_level < 0) { + return tns; + } + const __level = _level - 1; + children.forEach((key, index) => { + tns[index].children = []; + return generateData(__level, key, tns[index].children); + }); +}; +generateData(z); + +const Demo = React.createClass({ + getInitialState() { + return { + value: ['0-0'], + }; + }, + onSelect(selectedKey, node, selectedKeys) { + console.log('selected: ', selectedKey, selectedKeys); + this.setState({ + value: selectedKeys, + }); + }, + onChange(value) { + console.log('selected ' + value); + this.setState({ + value: value, + }); + }, + render() { + const loop = data => { + return data.map((item) => { + if (item.children) { + return {loop(item.children)}; + } + return ; + }); + }; + const tProps = { + // defaultValue: this.state.value, + value: this.state.value, + onChange: this.onChange, + onSelect: this.onSelect, + multiple: true, + // treeCheckable: true, + treeDefaultExpandAll: true, + }; + return (
+

more

+ + {loop(gData)} + +
); + }, +}); + +ReactDOM.render(
+ +
+, document.getElementById('components-tree-select-demo-enhance')); +```` diff --git a/components/tree-select/index.jsx b/components/tree-select/index.jsx new file mode 100644 index 0000000000..56e1d5ccc0 --- /dev/null +++ b/components/tree-select/index.jsx @@ -0,0 +1,49 @@ +import React from 'react'; +import TreeSelect, { TreeNode } from 'rc-tree-select'; +import classNames from 'classnames'; +// import assign from 'object-assign'; +// import animation from '../common/openAnimation'; + +const AntTreeSelect = React.createClass({ + getDefaultProps() { + return { + prefixCls: 'ant-tree-select', + transitionName: 'slide-up', + optionLabelProp: 'value', + choiceTransitionName: 'zoom', + showSearch: false, + size: 'default' + }; + }, + render() { + const props = this.props; + let { + size, className, combobox, notFoundContent + } = this.props; + + const cls = classNames({ + 'ant-select-lg': size === 'large', + 'ant-select-sm': size === 'small', + [className]: !!className, + }); + + if (combobox) { + notFoundContent = null; + } + + let checkable = props.treeCheckable; + if (checkable) { + checkable = ; + } + + return ( + + ); + } +}); + +AntTreeSelect.TreeNode = TreeNode; +export default AntTreeSelect; diff --git a/components/tree-select/index.md b/components/tree-select/index.md new file mode 100644 index 0000000000..8f389fc216 --- /dev/null +++ b/components/tree-select/index.md @@ -0,0 +1,49 @@ +# TreeSelect + +- category: Components +- chinese: 树选择控件 +- type: 表单 + +--- + +## 何时使用 + +当需要从树控件中灵活地筛选数据时 + +## API + +### Tree props + +| 参数 | 说明 | 类型 | 默认值 | +|-----------|------------------------------------------|------------|--------| +| value | 指定当前选中的条目 | string/Array | 无 | +| defaultValue | 指定默认选中的条目 | string/Array | 无 | +| multiple | 支持多选 | boolean | false | +| tags | 可以把随意输入的条目作为 tag,输入项不需要与下拉选项匹配 | boolean |false | +| onSelect | 被选中时调用,参数为选中项的 value 值 | function(value, option) | 无 | +| onChange | 选中option,或input的value变化(combobox 模式下)时,调用此函数 | function(value, label) | 无 | +| allowClear | 显示清除按钮 | boolean | false | +| onSearch | 文本框值变化时回调 | function(value: String) | | +| placeholder | 选择框默认文字 | string | 无 | +| searchPlaceholder | 搜索框默认文字 | string | 无 | +| dropdownMatchSelectWidth | 下拉菜单和选择器同宽 | boolean | true | +| combobox | 输入框自动提示模式 | boolean | false | +| size | 选择框大小,可选 `large` `small` | String | default | +| showSearch | 在下拉中显示搜索框 | boolean | false | +| disabled | 是否禁用 | boolean | false | +| treeDefaultExpandAll | 默认展开所有树节点 | bool | false | +| treeCheckable | 显示checkbox | bool | false | +| filterTreeNode | 是否根据输入项进行筛选,返回值true | function(treeNode) | - | +| treeNodeFilterProp | 输入项过滤对应的 treeNode 属性 | String | 'value' | +| treeNodeLabelProp | 作为显示的prop设置 | String | 'value' | +| loadData | 异步加载数据 | function(node) | - | + +### TreeNode props + +| 参数 | 说明 | 类型 | 默认值 | +|-----------|------------------------------------------|------------|--------| +| disabled | 是否禁用 | Boolean | false | +| key | 此项必须设置 | String | | +| value | 默认根据此属性值进行筛选 | String | - | +| title | 树节点显示的内容 | String | '---' | +| isLeaf | 是否是叶子节点 | bool | false | diff --git a/index.js b/index.js index 0e61e43f2c..3d0a4f5fe4 100644 --- a/index.js +++ b/index.js @@ -29,6 +29,7 @@ const antd = { Alert: require('./components/alert'), Validation: require('./components/validation'), Tree: require('./components/tree'), + TreeSelect: require('./components/tree-select'), Upload: require('./components/upload'), Badge: require('./components/badge'), Menu: require('./components/menu'), diff --git a/package.json b/package.json index 4daf8a7a7d..a0c2f4fc28 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,8 @@ "rc-tabs": "~5.6.0", "rc-time-picker": "~1.0.0", "rc-tooltip": "~3.3.0", - "rc-tree": "~0.19.0", + "rc-tree": "^0.23.1", + "rc-tree-select": "^0.3.3", "rc-trigger": "~1.0.6", "rc-upload": "~1.7.0", "rc-util": "~3.0.1", diff --git a/style/components/index.less b/style/components/index.less index ed1ad82834..14107786b5 100644 --- a/style/components/index.less +++ b/style/components/index.less @@ -27,6 +27,7 @@ @import "tag"; @import "alert"; @import "tree"; +@import "treeSelect"; @import "carousel/slick"; @import "carousel/slick-theme"; @import "upload"; diff --git a/style/components/treeSelect.less b/style/components/treeSelect.less new file mode 100644 index 0000000000..6bf36e14f3 --- /dev/null +++ b/style/components/treeSelect.less @@ -0,0 +1,580 @@ +@tree-select-tree-prefix-cls: ant-tree-select-tree; +.antCheckboxFn(@checkbox-prefix-cls: ant-tree-select-tree-checkbox); +@import "../mixins/iconfont"; +.antTreeSwitcherIcon() { + position: relative; + &:after { + .iconfont-size-under-12px(6px); + content: '\e611'; + display: inline-block; + font-family: 'anticon'; + font-weight: bold; + position: absolute; + top: 10px; + right: 4px; + color: #666; + transition: transform .3s ease; + } +} +.@{tree-select-tree-prefix-cls} { + margin: 0; + padding: 5px; + font-size: 12px; + li { + padding: 0; + margin: 0; + list-style: none; + white-space: nowrap; + outline: 0; + ul { + margin: 0; + padding: 0 0 0 18px; + } + a { + display: inline-block; + padding: 1px 4px; + border-radius: 2px; + margin: 0; + cursor: pointer; + height: 20px; + text-decoration: none; + vertical-align: top; + color: #666; + } + span { + &.@{tree-select-tree-prefix-cls}-checkbox { + margin: 3px 4px 0 0; + } + &.@{tree-select-tree-prefix-cls}-switcher-noop, + &.@{tree-select-tree-prefix-cls}-switcher, + &.@{tree-select-tree-prefix-cls}-iconEle { + line-height: 0; + margin: 0; + width: 16px; + height: 18px; + display: inline-block; + vertical-align: middle; + border: 0 none; + cursor: pointer; + outline: none; + } + &.@{tree-select-tree-prefix-cls}-icon_loading { + &:after { + content: '\e6a1'; + display: inline-block; + font-family: 'anticon'; + font-weight: bold; + .animation(loadingCircle 1s infinite linear); + margin-top: 8px; + } + } + &.@{tree-select-tree-prefix-cls}-switcher { + &-disabled { + background: #fff; + position: relative; + &:after { + content: '-'; + position: absolute; + top: 8px; + left: 6px; + color: gray; + } + } + &.@{tree-select-tree-prefix-cls}-roots_open, + &.@{tree-select-tree-prefix-cls}-center_open, + &.@{tree-select-tree-prefix-cls}-bottom_open, + &.@{tree-select-tree-prefix-cls}-noline_open { + .antTreeSwitcherIcon(); + } + &.@{tree-select-tree-prefix-cls}-roots_close, + &.@{tree-select-tree-prefix-cls}-center_close, + &.@{tree-select-tree-prefix-cls}-bottom_close, + &.@{tree-select-tree-prefix-cls}-noline_close { + .antTreeSwitcherIcon(); + .ie-rotate(3); + &:after { + transform: rotate(270deg) scale(0.5); + } + } + } + } + } + &-child-tree { + display: none; + &-open { + display: block; + } + } + &-treenode-disabled { + >span, + >a { + color: gray; + } + } + &-node-selected { + background-color: tint(@primary-color, 90%); + } + &-icon__open { + margin-right: 2px; + vertical-align: top; + } + &-icon__close { + margin-right: 2px; + vertical-align: top; + } +} + + +@tree-select-prefix-cls: ant-tree-select; + +@duration: .3s; + +@import "../mixins/iconfont"; +//mixin +.selection__clear() { + cursor: pointer; + float: right; + font-weight: bold; +} + +.@{tree-select-prefix-cls} { + box-sizing: border-box; + display: inline-block; + margin: 0; + position: relative; + vertical-align: middle; + color: #666; + font-size: @font-size-base; + + > ul > li > a { + padding: 0; + background-color: #fff; + } + + // arrow + &-arrow { + .iconfont-mixin(); + position: absolute; + top: 50%; + right: 8px; + line-height: 1; + margin-top: -5px; + .iconfont-size-under-12px(8px); + + * { + display: none; + } + + &:before { + content: '\e603'; + transition: transform 0.2s ease; + } + } + + &-selection { + outline: none; + user-select: none; + + box-sizing: border-box; + display: block; + + background-color: #fff; + border-radius: @border-radius-base; + border: 1px solid #d9d9d9; + .transition(all .3s @ease-in-out); + + &:hover { + .hover; + } + &:active { + .active; + } + } + + &-disabled { + color: #ccc; + } + + &-disabled &-selection { + &:hover, + &:active { + border-color: #d9d9d9; + } + } + + &-disabled &-selection--single { + cursor: not-allowed; + } + + &-selection--single { + height: 28px; + cursor: pointer; + + .@{tree-select-prefix-cls}-selection__rendered { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + padding-left: 8px; + padding-right: 24px; + line-height: 26px; + } + + .@{tree-select-prefix-cls}-selection__clear { + .selection__clear(); + } + + .@{tree-select-prefix-cls}-selection__placeholder { + color: #ccc; + } + } + + &-lg { + .ant-select-selection--single { + height: 32px; + .ant-select-selection__rendered { + line-height: 30px; + } + } + + .ant-select-selection--multiple { + min-height: 32px; + .ant-select-selection__rendered { + li { + height: 24px; + .ant-select-selection__choice__content { + font-size: 14px; + line-height: 24px; + } + } + } + } + } + + &-sm { + .ant-select-selection--single { + height: 22px; + .ant-select-selection__rendered { + line-height: 20px; + } + } + .ant-select-selection--multiple { + min-height: 22px; + .ant-select-selection__rendered { + li { + height: 14px; + .ant-select-selection__choice__content { + line-height: 14px; + position: relative; + top: -3px; + } + .ant-select-selection__choice__remove { + position: relative; + top: -4px; + } + } + } + } + } + + &-disabled &-selection__choice__remove { + color: #ccc; + cursor: default; + &:hover { + color: #ccc; + } + } + + &-search__field__wrap { + display: inline-block; + position: relative; + } + + &-search__field__placeholder { + position: absolute; + top: 0; + left: 3px; + color: #aaa; + } + + &-search--inline { + float: left; + + .@{tree-select-prefix-cls}-search__field__wrap { + width: 100%; + } + + .@{tree-select-prefix-cls}-search__field { + border: 0; + font-size: 100%; + background: transparent; + outline: 0; + } + > i { + float: right; + } + } + + &-selection--multiple { + min-height: 28px; + cursor: text; + + .@{tree-select-prefix-cls}-search__field__placeholder { + top: 6px; + left: 10px; + } + + .@{tree-select-prefix-cls}-search--inline { + width: auto; + .@{tree-select-prefix-cls}-search__field { + width: 0.75em; + } + } + + .@{tree-select-prefix-cls}-selection__rendered { + overflow: hidden; + text-overflow: ellipsis; + padding-left: 6px; + padding-bottom: 4px; + } + + .@{tree-select-prefix-cls}-selection__clear { + .selection__clear(); + margin-top: 5px; + margin-right: 10px; + } + + > ul > li { + margin-top: 4px; + height: 20px; + line-height: 20px; + } + + .@{tree-select-prefix-cls}-selection__choice { + background-color: #f3f3f3; + border-radius: 4px; + cursor: default; + float: left; + padding: 0 15px; + margin-right: 4px; + max-width: 99%; + position: relative; + overflow: hidden; + transition: padding @duration @ease-in-out; + padding: 0 20px 0 10px; + } + + .@{tree-select-prefix-cls}-selection__choice__content { + display: inline-block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 100%; + transition: margin @duration @ease-in-out; + } + + .@{tree-select-prefix-cls}-selection__choice__remove { + .iconfont-mixin(); + color: #999; + line-height: 20px; + cursor: pointer; + display: inline-block; + font-weight: bold; + transition: all 0.3s @ease-in-out; + .iconfont-size-under-12px(8px); + position: absolute; + right: 4px; + padding: 0 0 0 8px; + &:hover { + color: #404040; + } + &:before { + content: "\e62d"; + } + } + } + + &-open { + .@{tree-select-prefix-cls}-arrow { + .ie-rotate(2); + -ms-transform: rotate(180deg); + &:before { + .rotate(180deg); + } + } + .@{tree-select-prefix-cls}-selection { + .active(); + } + } + + &-combobox { + .@{tree-select-prefix-cls}-arrow { + display: none; + } + .@{tree-select-prefix-cls}-search--inline { + height: 100%; + float: none; + } + .@{tree-select-prefix-cls}-search__field__placeholder { + left: 10px; + cursor: text; + } + .@{tree-select-prefix-cls}-search__field__wrap { + width: 100%; + height: 100%; + } + .@{tree-select-prefix-cls}-search__field { + padding: 0 10px; + width: 100%; + height: 100%; + position: relative; + z-index: 1; + } + .@{tree-select-prefix-cls}-selection__rendered { + padding: 0; + height: 100%; + } + } +} + +.@{tree-select-prefix-cls}-dropdown { + &.slide-up-enter.slide-up-enter-active&-placement-bottomLeft, + &.slide-up-appear.slide-up-appear-active&-placement-bottomLeft { + animation-name: antSlideUpIn; + } + + &.slide-up-enter.slide-up-enter-active&-placement-topLeft, + &.slide-up-appear.slide-up-appear-active&-placement-topLeft { + animation-name: antSlideDownIn; + } + + &.slide-up-leave.slide-up-leave-active&-placement-bottomLeft { + animation-name: antSlideUpOut; + } + + &.slide-up-leave.slide-up-leave-active&-placement-topLeft { + animation-name: antSlideDownOut; + } + + &-hidden { + display: none; + } + + background-color: white; + border: 1px solid #d9d9d9; + box-shadow: @box-shadow-base; + border-radius: @border-radius-base; + box-sizing: border-box; + z-index: 1070; + left: -9999px; + top: -9999px; + position: absolute; + outline: none; + overflow: hidden; + font-size: @font-size-base; + + &-menu { + outline: none; + margin-bottom: 0; + padding-left: 0; // Override default ul/ol + list-style: none; + max-height: 250px; + overflow: auto; + + &-item-group-list { + margin: 0; + padding: 0; + + > .@{tree-select-prefix-cls}-dropdown-menu-item { + padding-left: 24px; + } + } + + &-item-group-title { + color: #999; + line-height: 1.5; + padding: 8px 15px; + } + + &-item { + position: relative; + display: block; + padding: 7px 15px; + font-weight: normal; + color: #666; + white-space: nowrap; + cursor: pointer; + overflow: hidden; + transition: background 0.3s ease; + + &:first-child { + border-top-left-radius: 4px; + border-top-right-radius: 4px; + } + &:last-child { + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + } + &:hover, + &-active { + background-color: tint(@primary-color, 90%); + } + + &-selected { + background-color: tint(@primary-color, 80%); + &:hover { + background-color: tint(@primary-color, 80%); + } + } + + &-disabled { + color: #ccc; + cursor: not-allowed; + + &:hover { + color: #ccc; + background-color: #fff; + cursor: not-allowed; + } + } + + &-divider { + height: 1px; + margin: 1px 0; + overflow: hidden; + background-color: #e5e5e5; + line-height: 0; + } + } + } + + &-container-open, + &-open { + .@{tree-select-prefix-cls}-dropdown { + display: block; + } + } + + .@{tree-select-prefix-cls}-dropdown-search { + display: block; + padding: 4px; + .@{tree-select-prefix-cls}-search__field__placeholder { + left: 7px; + top: 5px; + } + .@{tree-select-prefix-cls}-search__field__wrap { + width: 100%; + } + .@{tree-select-prefix-cls}-search__field { + padding: 4px 7px; + width: 100%; + box-sizing: border-box; + border: 1px solid #d9d9d9; + border-radius: 4px; + outline: none; + } + &.@{tree-select-prefix-cls}-search--hide { + display: none; + } + } +}