mirror of
https://gitee.com/baidu/amis.git
synced 2024-12-02 12:08:13 +08:00
添加 TreeCheckboxes
This commit is contained in:
parent
d5002a11fb
commit
5ecc052924
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
212
src/components/TreeCheckboxes.tsx
Normal file
212
src/components/TreeCheckboxes.tsx
Normal 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'
|
||||
})
|
||||
);
|
@ -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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user