添加 TreeCheckboxes

This commit is contained in:
2betop 2020-05-13 11:00:01 +08:00
parent f4af7ddcd7
commit 068e31e4b6
6 changed files with 292 additions and 5 deletions

View File

@ -681,6 +681,7 @@
&-expandBtn {
position: relative;
z-index: 1;
color: $Table-expandBtn-color;
> i {
display: inline-block;
@ -693,7 +694,6 @@
display: inline-block;
line-height: 1;
font-size: $Table-expandBtn-fontSize;
color: $Table-expandBtn-color;
font-family: $Table-expandBtn-vendor;
content: $Table-expandBtn-icon;
transition: transform ease-in-out 0.2s;

View File

@ -356,3 +356,70 @@
cursor: pointer;
}
}
.#{$ns}TreeCheckboxes {
.#{$ns}Table-expandBtn {
color: $icon-color;
margin-right: 5px;
}
&-sublist {
position: relative;
margin: 0 0 0 35px;
&:before {
width: 1px;
content: '';
display: block;
position: absolute;
top: -5px;
bottom: 15px;
left: -19px;
border-left: dashed 1px $icon-color;
}
}
&-item {
position: relative;
&.is-collapsed > .#{$ns}TreeCheckboxes-sublist {
display: none;
}
}
&-sublist &-item:before {
height: 1px;
content: '';
display: block;
position: absolute;
top: 15px;
width: 19px;
left: -19px;
border-top: dashed 1px $icon-color;
}
&-itemInner {
display: flex;
height: $Form-input-height;
line-height: $Form-input-lineHeight;
font-size: $Form-input-fontSize;
padding: (
$Form-input-height - $Form-input-lineHeight * $Form-input-fontSize
)/2 $gap-sm;
flex-direction: row;
> .#{$ns}Checkbox {
margin-right: 0;
}
cursor: pointer;
user-select: none;
&.is-disabled {
pointer-events: none;
color: $text--muted-color;
}
}
&-itemLabel {
flex-grow: 1;
}
}

View File

@ -64,7 +64,7 @@
}
&-item {
background: $white;
// background: $white;
}
&-itemDrager {
@ -123,6 +123,12 @@
}
}
// &--ver {
// .#{$ns}Combo-item {
// background: $white;
// }
// }
&--ver:not(&--noBorder) {
@include clearfix();
> .#{$ns}Combo-items {

View File

@ -11,7 +11,7 @@ import chunk from 'lodash/chunk';
import {ClassNamesFn, themeable, ThemeProps} from '../theme';
import {Option, value2array, Options} from './Select';
import find from 'lodash/find';
import { autobind } from '../utils/helper';
import { autobind, findTree } from '../utils/helper';
// import isPlainObject from 'lodash/isPlainObject';
export interface CheckboxesProps extends ThemeProps {
@ -29,7 +29,7 @@ export interface CheckboxesProps extends ThemeProps {
disabled?: boolean;
}
export class Checkboxes<T extends CheckboxesProps = CheckboxesProps> extends React.Component<T, any> {
export class Checkboxes<T extends CheckboxesProps = CheckboxesProps, S = any> extends React.Component<T, S> {
static defaultProps = {
placeholder: '暂无选项',
itemRender: (option: Option) => <span>{option.label}</span>
@ -50,7 +50,7 @@ export class Checkboxes<T extends CheckboxesProps = CheckboxesProps> extends Rea
return value
.map((value: any) => {
const option = find(options, option => option2value(option) === value);
const option = findTree(options, option => option2value(option) === value);
return option;
})
.filter((item: any) => item);

View File

@ -0,0 +1,212 @@
import {Checkboxes, CheckboxesProps} from './Checkboxes';
import {themeable} from '../theme';
import React from 'react';
import uncontrollable from 'uncontrollable';
import Checkbox from './Checkbox';
import {Option} from './Select';
export interface TreeCheckboxesProps extends CheckboxesProps {}
export interface TreeCheckboxesState {
collapsed: Array<Option>;
}
export class TreeCheckboxes extends Checkboxes<
TreeCheckboxesProps,
TreeCheckboxesState
> {
valueArray: Array<Option>;
state: TreeCheckboxesState = {
collapsed: []
};
toggleOption(option: Option) {
const {value, onChange, option2value, options} = this.props;
if (option.disabled) {
return;
}
let valueArray = Checkboxes.value2array(value, options, option2value);
if (
option.value === void 0 &&
Array.isArray(option.children) &&
option.children.length
) {
const someCheckedFn = (child: Option) =>
(Array.isArray(child.children) && child.children.length
? child.children.some(someCheckedFn)
: false) ||
(child.value !== void 0 && ~valueArray.indexOf(child));
const someChecked = option.children.some(someCheckedFn);
const eachFn = (child: Option) => {
if (Array.isArray(child.children) && child.children.length) {
child.children.forEach(eachFn);
}
if (child.value !== void 0) {
const idx = valueArray.indexOf(child);
~idx && valueArray.splice(idx, 1);
if (!someChecked) {
valueArray.push(child);
}
}
};
option.children.forEach(eachFn);
} else {
let idx = valueArray.indexOf(option);
if (~idx) {
valueArray.splice(idx, 1);
} else {
valueArray.push(option);
}
}
let newValue: string | Array<Option> = option2value
? valueArray.map(item => option2value(item))
: valueArray;
onChange && onChange(newValue);
}
toggleCollapsed(option: Option) {
const collapsed = this.state.collapsed.concat();
const idx = collapsed.indexOf(option);
if (~idx) {
collapsed.splice(idx, 1);
} else {
collapsed.push(option);
}
this.setState({
collapsed
});
}
renderItem(option: Option, index: number) {
const {
labelClassName,
disabled,
classnames: cx,
itemClassName,
itemRender
} = this.props;
const valueArray = this.valueArray;
let partial = false;
let checked = false;
let hasChildren = Array.isArray(option.children) && option.children.length;
if (option.value === void 0 && hasChildren) {
let allchecked = true;
let partialChecked = false;
const eachFn = (child: Option) => {
if (Array.isArray(child.children) && child.children.length) {
child.children.forEach(eachFn);
}
if (child.value !== void 0) {
const isIn = !!~valueArray.indexOf(child);
if (isIn && !partialChecked) {
partialChecked = true;
} else if (!isIn && allchecked) {
allchecked = false;
}
checked = partialChecked;
partial = partialChecked && !allchecked;
}
};
option.children!.forEach(eachFn);
} else {
checked = !!~valueArray.indexOf(option);
}
const collapsed = !!~this.state.collapsed.indexOf(option);
return (
<div
key={index}
className={cx(
'TreeCheckboxes-item',
disabled || option.disabled ? 'is-disabled' : '',
collapsed ? 'is-collapsed' : ''
)}
>
<div
className={cx(
'TreeCheckboxes-itemInner',
itemClassName,
option.className
)}
onClick={() => this.toggleOption(option)}
>
{hasChildren ? (
<a
onClick={(e: React.MouseEvent<any>) => {
e.stopPropagation();
this.toggleCollapsed(option);
}}
className={cx('Table-expandBtn', !collapsed ? 'is-active' : '')}
>
<i />
</a>
) : null}
<div className={cx('TreeCheckboxes-itemLabel')}>
{itemRender(option)}
</div>
<Checkbox
checked={checked}
partial={partial}
disabled={disabled || option.disabled}
labelClassName={labelClassName}
description={option.description}
/>
</div>
{Array.isArray(option.children) && option.children.length ? (
<div className={cx('TreeCheckboxes-sublist')}>
{option.children.map((option, key) => this.renderItem(option, key))}
</div>
) : null}
</div>
);
}
render() {
const {
value,
options,
className,
placeholder,
classnames: cx,
option2value
} = this.props;
this.valueArray = Checkboxes.value2array(value, options, option2value);
let body: Array<React.ReactNode> = [];
if (Array.isArray(options) && options.length) {
body = options.map((option, key) => this.renderItem(option, key));
}
return (
<div className={cx('TreeCheckboxes', className)}>
{body && body.length ? body : <div>{placeholder}</div>}
</div>
);
}
}
export default themeable(
uncontrollable(TreeCheckboxes, {
value: 'onChange'
})
);

View File

@ -1010,6 +1010,8 @@ export default class Form extends React.Component<FormProps, object> {
};
}
// 自定义组件如果在节点设置了 label name 什么的,就用 formItem 包一层
// 至少自动支持了 valdiations, label, description 等逻辑。
if (control.component && control.label && control.name) {
const cache = this.componentCache.get(control.component);