Skip to content

Commit

Permalink
添加 TreeCheckboxes
Browse files Browse the repository at this point in the history
  • Loading branch information
2betop committed May 13, 2020
1 parent f4af7dd commit 068e31e
Show file tree
Hide file tree
Showing 6 changed files with 292 additions and 5 deletions.
2 changes: 1 addition & 1 deletion scss/components/_table.scss
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,7 @@
&-expandBtn {
position: relative;
z-index: 1;
color: $Table-expandBtn-color;

> i {
display: inline-block;
Expand All @@ -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;
Expand Down
67 changes: 67 additions & 0 deletions scss/components/form/_checks.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
8 changes: 7 additions & 1 deletion scss/components/form/_combo.scss
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
}

&-item {
background: $white;
// background: $white;
}

&-itemDrager {
Expand Down Expand Up @@ -123,6 +123,12 @@
}
}

// &--ver {
// .#{$ns}Combo-item {
// background: $white;
// }
// }

&--ver:not(&--noBorder) {
@include clearfix();
> .#{$ns}Combo-items {
Expand Down
6 changes: 3 additions & 3 deletions src/components/Checkboxes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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>
Expand All @@ -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);
Expand Down
212 changes: 212 additions & 0 deletions src/components/TreeCheckboxes.tsx
Original file line number Diff line number Diff line change
@@ -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'
})
);
2 changes: 2 additions & 0 deletions src/renderers/Form/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down

0 comments on commit 068e31e

Please sign in to comment.