(cherry picked from commit 78fb20282de73c0ea47375895a807235385d90e3)pull/10714/head
parent
ff609848d8
commit
cd490d6334
@ -1,156 +0,0 @@
|
|||||||
import classNames from 'classnames';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import KeyValueListInputItem from './KeyValueListInputItem';
|
|
||||||
import styles from './KeyValueListInput.css';
|
|
||||||
|
|
||||||
class KeyValueListInput extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Lifecycle
|
|
||||||
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props, context);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
isFocused: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onItemChange = (index, itemValue) => {
|
|
||||||
const {
|
|
||||||
name,
|
|
||||||
value,
|
|
||||||
onChange
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const newValue = [...value];
|
|
||||||
|
|
||||||
if (index == null) {
|
|
||||||
newValue.push(itemValue);
|
|
||||||
} else {
|
|
||||||
newValue.splice(index, 1, itemValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
onChange({
|
|
||||||
name,
|
|
||||||
value: newValue
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
onRemoveItem = (index) => {
|
|
||||||
const {
|
|
||||||
name,
|
|
||||||
value,
|
|
||||||
onChange
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const newValue = [...value];
|
|
||||||
newValue.splice(index, 1);
|
|
||||||
|
|
||||||
onChange({
|
|
||||||
name,
|
|
||||||
value: newValue
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
onFocus = () => {
|
|
||||||
this.setState({
|
|
||||||
isFocused: true
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
onBlur = () => {
|
|
||||||
this.setState({
|
|
||||||
isFocused: false
|
|
||||||
});
|
|
||||||
|
|
||||||
const {
|
|
||||||
name,
|
|
||||||
value,
|
|
||||||
onChange
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const newValue = value.reduce((acc, v) => {
|
|
||||||
if (v.key || v.value) {
|
|
||||||
acc.push(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (newValue.length !== value.length) {
|
|
||||||
onChange({
|
|
||||||
name,
|
|
||||||
value: newValue
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
className,
|
|
||||||
value,
|
|
||||||
keyPlaceholder,
|
|
||||||
valuePlaceholder,
|
|
||||||
hasError,
|
|
||||||
hasWarning
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const { isFocused } = this.state;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classNames(
|
|
||||||
className,
|
|
||||||
isFocused && styles.isFocused,
|
|
||||||
hasError && styles.hasError,
|
|
||||||
hasWarning && styles.hasWarning
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{
|
|
||||||
[...value, { key: '', value: '' }].map((v, index) => {
|
|
||||||
return (
|
|
||||||
<KeyValueListInputItem
|
|
||||||
key={index}
|
|
||||||
index={index}
|
|
||||||
keyValue={v.key}
|
|
||||||
value={v.value}
|
|
||||||
keyPlaceholder={keyPlaceholder}
|
|
||||||
valuePlaceholder={valuePlaceholder}
|
|
||||||
isNew={index === value.length}
|
|
||||||
onChange={this.onItemChange}
|
|
||||||
onRemove={this.onRemoveItem}
|
|
||||||
onFocus={this.onFocus}
|
|
||||||
onBlur={this.onBlur}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
KeyValueListInput.propTypes = {
|
|
||||||
className: PropTypes.string.isRequired,
|
|
||||||
name: PropTypes.string.isRequired,
|
|
||||||
value: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
hasError: PropTypes.bool,
|
|
||||||
hasWarning: PropTypes.bool,
|
|
||||||
keyPlaceholder: PropTypes.string,
|
|
||||||
valuePlaceholder: PropTypes.string,
|
|
||||||
onChange: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
KeyValueListInput.defaultProps = {
|
|
||||||
className: styles.inputContainer,
|
|
||||||
value: []
|
|
||||||
};
|
|
||||||
|
|
||||||
export default KeyValueListInput;
|
|
@ -0,0 +1,104 @@
|
|||||||
|
import classNames from 'classnames';
|
||||||
|
import React, { useCallback, useState } from 'react';
|
||||||
|
import { InputOnChange } from 'typings/inputs';
|
||||||
|
import KeyValueListInputItem from './KeyValueListInputItem';
|
||||||
|
import styles from './KeyValueListInput.css';
|
||||||
|
|
||||||
|
interface KeyValue {
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface KeyValueListInputProps {
|
||||||
|
className?: string;
|
||||||
|
name: string;
|
||||||
|
value: KeyValue[];
|
||||||
|
hasError?: boolean;
|
||||||
|
hasWarning?: boolean;
|
||||||
|
keyPlaceholder?: string;
|
||||||
|
valuePlaceholder?: string;
|
||||||
|
onChange: InputOnChange<KeyValue[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function KeyValueListInput({
|
||||||
|
className = styles.inputContainer,
|
||||||
|
name,
|
||||||
|
value = [],
|
||||||
|
hasError = false,
|
||||||
|
hasWarning = false,
|
||||||
|
keyPlaceholder,
|
||||||
|
valuePlaceholder,
|
||||||
|
onChange,
|
||||||
|
}: KeyValueListInputProps): JSX.Element {
|
||||||
|
const [isFocused, setIsFocused] = useState(false);
|
||||||
|
|
||||||
|
const handleItemChange = useCallback(
|
||||||
|
(index: number | null, itemValue: KeyValue) => {
|
||||||
|
const newValue = [...value];
|
||||||
|
|
||||||
|
if (index === null) {
|
||||||
|
newValue.push(itemValue);
|
||||||
|
} else {
|
||||||
|
newValue.splice(index, 1, itemValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange({ name, value: newValue });
|
||||||
|
},
|
||||||
|
[value, name, onChange]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleRemoveItem = useCallback(
|
||||||
|
(index: number) => {
|
||||||
|
const newValue = [...value];
|
||||||
|
newValue.splice(index, 1);
|
||||||
|
onChange({ name, value: newValue });
|
||||||
|
},
|
||||||
|
[value, name, onChange]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onFocus = useCallback(() => setIsFocused(true), []);
|
||||||
|
|
||||||
|
const onBlur = useCallback(() => {
|
||||||
|
setIsFocused(false);
|
||||||
|
|
||||||
|
const newValue = value.reduce((acc: KeyValue[], v) => {
|
||||||
|
if (v.key || v.value) {
|
||||||
|
acc.push(v);
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (newValue.length !== value.length) {
|
||||||
|
onChange({ name, value: newValue });
|
||||||
|
}
|
||||||
|
}, [value, name, onChange]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
className,
|
||||||
|
isFocused && styles.isFocused,
|
||||||
|
hasError && styles.hasError,
|
||||||
|
hasWarning && styles.hasWarning
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{[...value, { key: '', value: '' }].map((v, index) => (
|
||||||
|
<KeyValueListInputItem
|
||||||
|
key={index}
|
||||||
|
index={index}
|
||||||
|
keyValue={v.key}
|
||||||
|
value={v.value}
|
||||||
|
keyPlaceholder={keyPlaceholder}
|
||||||
|
valuePlaceholder={valuePlaceholder}
|
||||||
|
isNew={index === value.length}
|
||||||
|
onChange={handleItemChange}
|
||||||
|
onRemove={handleRemoveItem}
|
||||||
|
onFocus={onFocus}
|
||||||
|
onBlur={onBlur}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default KeyValueListInput;
|
@ -1,124 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import IconButton from 'Components/Link/IconButton';
|
|
||||||
import { icons } from 'Helpers/Props';
|
|
||||||
import TextInput from './TextInput';
|
|
||||||
import styles from './KeyValueListInputItem.css';
|
|
||||||
|
|
||||||
class KeyValueListInputItem extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onKeyChange = ({ value: keyValue }) => {
|
|
||||||
const {
|
|
||||||
index,
|
|
||||||
value,
|
|
||||||
onChange
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
onChange(index, { key: keyValue, value });
|
|
||||||
};
|
|
||||||
|
|
||||||
onValueChange = ({ value }) => {
|
|
||||||
// TODO: Validate here or validate at a lower level component
|
|
||||||
|
|
||||||
const {
|
|
||||||
index,
|
|
||||||
keyValue,
|
|
||||||
onChange
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
onChange(index, { key: keyValue, value });
|
|
||||||
};
|
|
||||||
|
|
||||||
onRemovePress = () => {
|
|
||||||
const {
|
|
||||||
index,
|
|
||||||
onRemove
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
onRemove(index);
|
|
||||||
};
|
|
||||||
|
|
||||||
onFocus = () => {
|
|
||||||
this.props.onFocus();
|
|
||||||
};
|
|
||||||
|
|
||||||
onBlur = () => {
|
|
||||||
this.props.onBlur();
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
keyValue,
|
|
||||||
value,
|
|
||||||
keyPlaceholder,
|
|
||||||
valuePlaceholder,
|
|
||||||
isNew
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.itemContainer}>
|
|
||||||
<div className={styles.inputWrapper}>
|
|
||||||
<TextInput
|
|
||||||
className={styles.keyInput}
|
|
||||||
name="key"
|
|
||||||
value={keyValue}
|
|
||||||
placeholder={keyPlaceholder}
|
|
||||||
onChange={this.onKeyChange}
|
|
||||||
onFocus={this.onFocus}
|
|
||||||
onBlur={this.onBlur}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.inputWrapper}>
|
|
||||||
<TextInput
|
|
||||||
className={styles.valueInput}
|
|
||||||
name="value"
|
|
||||||
value={value}
|
|
||||||
placeholder={valuePlaceholder}
|
|
||||||
onChange={this.onValueChange}
|
|
||||||
onFocus={this.onFocus}
|
|
||||||
onBlur={this.onBlur}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.buttonWrapper}>
|
|
||||||
{
|
|
||||||
isNew ?
|
|
||||||
null :
|
|
||||||
<IconButton
|
|
||||||
name={icons.REMOVE}
|
|
||||||
tabIndex={-1}
|
|
||||||
onPress={this.onRemovePress}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
KeyValueListInputItem.propTypes = {
|
|
||||||
index: PropTypes.number,
|
|
||||||
keyValue: PropTypes.string.isRequired,
|
|
||||||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
|
||||||
keyPlaceholder: PropTypes.string.isRequired,
|
|
||||||
valuePlaceholder: PropTypes.string.isRequired,
|
|
||||||
isNew: PropTypes.bool.isRequired,
|
|
||||||
onChange: PropTypes.func.isRequired,
|
|
||||||
onRemove: PropTypes.func.isRequired,
|
|
||||||
onFocus: PropTypes.func.isRequired,
|
|
||||||
onBlur: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
KeyValueListInputItem.defaultProps = {
|
|
||||||
keyPlaceholder: 'Key',
|
|
||||||
valuePlaceholder: 'Value'
|
|
||||||
};
|
|
||||||
|
|
||||||
export default KeyValueListInputItem;
|
|
@ -0,0 +1,89 @@
|
|||||||
|
import React, { useCallback } from 'react';
|
||||||
|
import IconButton from 'Components/Link/IconButton';
|
||||||
|
import { icons } from 'Helpers/Props';
|
||||||
|
import TextInput from './TextInput';
|
||||||
|
import styles from './KeyValueListInputItem.css';
|
||||||
|
|
||||||
|
interface KeyValueListInputItemProps {
|
||||||
|
index: number;
|
||||||
|
keyValue: string;
|
||||||
|
value: string;
|
||||||
|
keyPlaceholder?: string;
|
||||||
|
valuePlaceholder?: string;
|
||||||
|
isNew: boolean;
|
||||||
|
onChange: (index: number, itemValue: { key: string; value: string }) => void;
|
||||||
|
onRemove: (index: number) => void;
|
||||||
|
onFocus: () => void;
|
||||||
|
onBlur: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function KeyValueListInputItem({
|
||||||
|
index,
|
||||||
|
keyValue,
|
||||||
|
value,
|
||||||
|
keyPlaceholder = 'Key',
|
||||||
|
valuePlaceholder = 'Value',
|
||||||
|
isNew,
|
||||||
|
onChange,
|
||||||
|
onRemove,
|
||||||
|
onFocus,
|
||||||
|
onBlur,
|
||||||
|
}: KeyValueListInputItemProps): JSX.Element {
|
||||||
|
const handleKeyChange = useCallback(
|
||||||
|
({ value: keyValue }: { value: string }) => {
|
||||||
|
onChange(index, { key: keyValue, value });
|
||||||
|
},
|
||||||
|
[index, value, onChange]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleValueChange = useCallback(
|
||||||
|
({ value }: { value: string }) => {
|
||||||
|
onChange(index, { key: keyValue, value });
|
||||||
|
},
|
||||||
|
[index, keyValue, onChange]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleRemovePress = useCallback(() => {
|
||||||
|
onRemove(index);
|
||||||
|
}, [index, onRemove]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.itemContainer}>
|
||||||
|
<div className={styles.keyInputWrapper}>
|
||||||
|
<TextInput
|
||||||
|
className={styles.keyInput}
|
||||||
|
name="key"
|
||||||
|
value={keyValue}
|
||||||
|
placeholder={keyPlaceholder}
|
||||||
|
onChange={handleKeyChange}
|
||||||
|
onFocus={onFocus}
|
||||||
|
onBlur={onBlur}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.valueInputWrapper}>
|
||||||
|
<TextInput
|
||||||
|
className={styles.valueInput}
|
||||||
|
name="value"
|
||||||
|
value={value}
|
||||||
|
placeholder={valuePlaceholder}
|
||||||
|
onChange={handleValueChange}
|
||||||
|
onFocus={onFocus}
|
||||||
|
onBlur={onBlur}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.buttonWrapper}>
|
||||||
|
{isNew ? null : (
|
||||||
|
<IconButton
|
||||||
|
name={icons.REMOVE}
|
||||||
|
tabIndex={-1}
|
||||||
|
onPress={handleRemovePress}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default KeyValueListInputItem;
|
Loading…
Reference in new issue