forked from p96170835/amis
添加 ArrayInput 组件
This commit is contained in:
parent
85a531ad95
commit
d8f6aa6655
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
.#{$ns}Combo {
|
||||
&-placeholder {
|
||||
color: $text--muted-color;
|
||||
padding-top: px2rem(7px);
|
||||
padding-top: $Form-label-paddingTop;
|
||||
}
|
||||
|
||||
&-toolbarBtn {
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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'
|
||||
})
|
||||
)
|
||||
);
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -64,6 +64,7 @@ register('en', {
|
|||
'请输入街道信息': 'Enter street info',
|
||||
'删除': 'Delete',
|
||||
'新增': 'New',
|
||||
'新增一条': 'Add a data',
|
||||
'新增一条数据': 'Add a data',
|
||||
'类型': 'Type',
|
||||
'拖拽排序': 'Drag to sort',
|
||||
|
|
Loading…
Reference in New Issue