添加 ArrayInput 组件

This commit is contained in:
2betop 2020-06-12 11:07:34 +08:00
parent 85a531ad95
commit d8f6aa6655
8 changed files with 321 additions and 5 deletions

View File

@ -0,0 +1,89 @@
.#{$ns}ArrayInput {
&-placeholder {
color: $text--muted-color;
padding-top: $Form-label-paddingTop;
}
&-addBtn {
> svg {
width: $Combo-addBtn-fontSize;
height: $Combo-addBtn-fontSize;
}
font-size: $Combo-addBtn-fontSize;
@include button-size(
$Combo-addBtn-paddingY,
$Combo-addBtn-paddingX,
$Combo-addBtn-fontSize,
$Combo-addBtn-lineHeight,
$Combo-addBtn-borderRadius,
$Combo-addBtn-height
);
@include button-variant(
$Combo-addBtn-bg,
$Combo-addBtn-border,
$Combo-addBtn-color,
$Combo-addBtn-onHover-bg,
$Combo-addBtn-onHover-border,
$Combo-addBtn-onHover-color,
$Combo-addBtn-onActive-bg,
$Combo-addBtn-onActive-border,
$Combo-addBtn-onActive-color
);
&.is-disabled {
pointer-events: none;
opacity: $Button-onDisabled-opacity;
}
}
&-toolbar {
margin-top: $gap-sm;
}
&-item {
display: flex;
flex-direction: row;
align-items: flex-start;
margin-bottom: $gap-sm;
> div {
flex-grow: 1;
width: 0;
min-width: 120px;
margin-right: $gap-xs;
&:not(:first-child) {
margin-left: $gap-xs;
}
}
&--dragging {
position: relative;
opacity: 0.4;
}
}
&-itemRemove,
&-itemDrager {
margin: 0;
flex-grow: unset;
display: inline-block;
padding: $Form-label-paddingTop $gap-xs;
cursor: pointer;
> svg {
color: $icon-color;
}
&:hover > svg {
color: $icon-onHover-color;
}
}
&-itemDrager {
cursor: move;
}
}

View File

@ -1,7 +1,7 @@
.#{$ns}Combo {
&-placeholder {
color: $text--muted-color;
padding-top: px2rem(7px);
padding-top: $Form-label-paddingTop;
}
&-toolbarBtn {

View File

@ -480,12 +480,14 @@ $Satus-icon-width: px2rem(14px);
@import '../layout/aside';
@import '../layout/hbox';
@import '../layout/vbox';
@import '../components/button'; // 这个要放在最前面
@import '../components/modal';
@import '../components/drawer';
@import '../components/tooltip';
@import '../components/popover';
@import '../components/toast';
@import '../components/alert';
@import '../components/array-input';
@import '../components/tabs';
@import '../components/nav';
@import '../components/page';
@ -496,7 +498,6 @@ $Satus-icon-width: px2rem(14px);
@import '../components/panel';
@import '../components/service';
@import '../components/spinner';
@import '../components/button';
@import '../components/button-group';
@import '../components/dropdown';
@import '../components/collapse';

View File

@ -166,12 +166,14 @@ pre {
@import '../layout/aside';
@import '../layout/hbox';
@import '../layout/vbox';
@import '../components/button'; // 这个要放在最前面
@import '../components/modal';
@import '../components/drawer';
@import '../components/tooltip';
@import '../components/popover';
@import '../components/toast';
@import '../components/alert';
@import '../components/array-input';
@import '../components/tabs';
@import '../components/nav';
@import '../components/page';
@ -182,7 +184,6 @@ pre {
@import '../components/panel';
@import '../components/service';
@import '../components/spinner';
@import '../components/button';
@import '../components/button-group';
@import '../components/dropdown';
@import '../components/collapse';

View File

@ -29,12 +29,14 @@ $Form-input-borderColor: #cfdadd;
@import '../layout/aside';
@import '../layout/hbox';
@import '../layout/vbox';
@import '../components/button'; // 这个要放在最前面
@import '../components/modal';
@import '../components/drawer';
@import '../components/tooltip';
@import '../components/popover';
@import '../components/toast';
@import '../components/alert';
@import '../components/array-input';
@import '../components/tabs';
@import '../components/nav';
@import '../components/page';
@ -45,7 +47,6 @@ $Form-input-borderColor: #cfdadd;
@import '../components/panel';
@import '../components/service';
@import '../components/spinner';
@import '../components/button';
@import '../components/button-group';
@import '../components/dropdown';
@import '../components/collapse';

View File

@ -0,0 +1,221 @@
import React from 'react';
import {ThemeProps, themeable} from '../theme';
import {LocaleProps, localeable} from '../locale';
import InputBox from './InputBox';
import {Icon} from './icons';
import Button from './Button';
import {autobind, guid} from '../utils/helper';
import {uncontrollable} from 'uncontrollable';
import Sortable from 'sortablejs';
import {findDOMNode} from 'react-dom';
export interface ArrayInputProps extends ThemeProps, LocaleProps {
value?: Array<any>;
onChange?: (value: Array<any>) => void;
placeholder: string;
itemRender: (
value: any,
onChange: (value: any) => void,
index: number,
disabled?: boolean
) => JSX.Element;
itemInitalValue?: any;
maxLength?: number;
minLength?: number;
disabled?: boolean;
sortable?: boolean;
removable?: boolean;
addable?: boolean;
editable?: boolean;
sortTip?: string;
}
export class ArrayInput extends React.Component<ArrayInputProps> {
static defaultProps = {
placeholder: '<空>',
itemRender: (value: any, onChange: (value: any) => void, index: number) => (
<InputBox value={value} onChange={onChange} />
)
};
id: string = guid();
dragTip?: HTMLElement;
sortable?: Sortable;
handleItemOnChange(index: number, itemValue: any) {
const {onChange} = this.props;
const value = this.props.value;
const newValue = Array.isArray(value) ? value.concat() : [];
newValue.splice(index, 1, itemValue);
onChange?.(newValue);
}
@autobind
dragTipRef(ref: any) {
if (!this.dragTip && ref) {
this.initDragging();
} else if (this.dragTip && !ref) {
this.destroyDragging();
}
this.dragTip = ref;
}
@autobind
handleAdd() {
const {value, onChange, itemInitalValue} = this.props;
const newValue = Array.isArray(value) ? value.concat() : [];
newValue.push(itemInitalValue);
onChange?.(newValue);
}
@autobind
handleRemove(e: React.MouseEvent<HTMLElement>) {
const indx = parseInt(e.currentTarget.getAttribute('data-index')!, 10);
const {value, onChange, itemInitalValue} = this.props;
const newValue = Array.isArray(value) ? value.concat() : [];
newValue.splice(indx, 1);
onChange?.(newValue);
}
initDragging() {
const onChange = this.props.onChange;
const ns = this.props.classPrefix;
const dom = findDOMNode(this) as HTMLElement;
this.sortable = new Sortable(
dom.querySelector(`.drag-group`) as HTMLElement,
{
group: `array-input-${this.id}`,
animation: 150,
handle: `.drag-bar`,
ghostClass: `${ns}ArrayInput-item--dragging`,
onEnd: (e: any) => {
// 没有移动
if (e.newIndex === e.oldIndex) {
return;
}
// 换回来
const parent = e.to as HTMLElement;
if (
e.newIndex < e.oldIndex &&
e.oldIndex < parent.childNodes.length - 1
) {
parent.insertBefore(e.item, parent.childNodes[e.oldIndex + 1]);
} else if (e.oldIndex < parent.childNodes.length - 1) {
parent.insertBefore(e.item, parent.childNodes[e.oldIndex]);
} else {
parent.appendChild(e.item);
}
const value = this.props.value;
if (!Array.isArray(value)) {
return;
}
const newValue = value.concat();
newValue.splice(e.newIndex, 0, newValue.splice(e.oldIndex, 1)[0]);
onChange?.(newValue);
}
}
);
}
destroyDragging() {
this.sortable && this.sortable.destroy();
}
renderItem(value: any, index: number, collection: Array<any>) {
const {
itemRender,
disabled,
classnames: cx,
sortable,
removable,
minLength
} = this.props;
return (
<div className={cx('ArrayInput-item')} key={index}>
{sortable && collection.length > 1 && !disabled ? (
<a className={cx('ArrayInput-itemDrager drag-bar')}>
<Icon icon="drag-bar" className="icon" />
</a>
) : null}
{itemRender(
value,
this.handleItemOnChange.bind(this, index),
index,
disabled
)}
{removable !== false &&
!disabled &&
(!minLength || collection.length > minLength) ? (
<a
data-index={index}
className={cx('ArrayInput-itemRemove')}
onClick={this.handleRemove}
>
<Icon icon="close" className="icon" />
</a>
) : null}
</div>
);
}
render() {
const {
classnames: cx,
value,
placeholder,
translate: __,
maxLength,
sortable,
sortTip,
disabled
} = this.props;
return (
<div className={cx('ArrayInput')}>
{Array.isArray(value) && value.length ? (
<div className={cx('ArrayInput-items drag-group')}>
{value.map((item, index) => this.renderItem(item, index, value))}
</div>
) : (
<div className={cx('ArrayInput-placeholder')}>{__(placeholder)}</div>
)}
<div className={cx('ArrayInput-toolbar')}>
{!Array.isArray(value) || !maxLength || value.length < maxLength ? (
<Button
className={cx('ArrayInput-addBtn')}
onClick={this.handleAdd}
level=""
disabled={disabled}
>
<Icon icon="plus" className="icon" />
<span>{__('新增一条')}</span>
</Button>
) : null}
{sortable && Array.isArray(value) && value.length ? (
<span className={cx(`Combo-dragableTip`)} ref={this.dragTipRef}>
{Array.isArray(value) && value.length > 1 ? __(sortTip) : ''}
</span>
) : null}
</div>
</div>
);
}
}
export default themeable(
localeable(
uncontrollable(ArrayInput, {
value: 'onChange'
})
)
);

View File

@ -51,6 +51,7 @@ import ListRadios from './ListRadios';
import TreeRadios from './TreeRadios';
import ListGroup from './ListGroup';
import NumberInput from './NumberInput';
import ArrayInput from './ArrayInput';
export {
NotFound,
@ -104,5 +105,6 @@ export {
ListRadios,
TreeRadios,
ListGroup,
NumberInput
NumberInput,
ArrayInput
};

View File

@ -64,6 +64,7 @@ register('en', {
'请输入街道信息': 'Enter street info',
'删除': 'Delete',
'新增': 'New',
'新增一条': 'Add a data',
'新增一条数据': 'Add a data',
'类型': 'Type',
'拖拽排序': 'Drag to sort',