Improve multi entry + autofill UI

This commit is contained in:
Maciej Ziarkowski 2020-03-27 14:32:19 +00:00
parent c3f757734a
commit a5f29c6aad
5 changed files with 68 additions and 21 deletions

View File

@ -7,13 +7,17 @@ export interface TextDataEntryInputProps {
name?: string;
id?: string;
onChange?: (key: string, val: any) => void;
onConfirm?: (key: string, val: any) => void;
maxLength?: number;
disabled?: boolean;
placeholder?: string;
valueTransform?: (val: string) => string;
confirmOnEnter?: boolean;
autofill?: boolean;
showAllOptionsOnEmpty?: boolean;
confirmOnAutofillSelect?: boolean;
}
export const DataEntryInput: React.FC<TextDataEntryInputProps & {value?: string}> = props => {
@ -21,13 +25,29 @@ export const DataEntryInput: React.FC<TextDataEntryInputProps & {value?: string}
const nameAttr = props.name || props.slug;
const idAttr = props.id || props.slug;
const handleChange = (value: string) => {
console.log(value);
const transformValue = (value: string) => {
const transform = props.valueTransform || (x => x);
const transformedValue = value === '' ?
null :
transform(value);
props.onChange(props.slug, transformedValue);
return transformedValue;
};
const handleChange = (value: string) => {
props.onChange?.(props.slug, transformValue(value));
};
const handleConfirm = () => {
props.onConfirm?.(props.slug, props.value);
};
const handleAutofillSelect = (value: string) => {
const transformedValue = transformValue(value);
if(props.confirmOnAutofillSelect) {
props.onConfirm?.(props.slug, transformedValue);
} else {
props.onChange?.(props.slug, transformedValue);
}
};
return (
@ -39,6 +59,15 @@ export const DataEntryInput: React.FC<TextDataEntryInputProps & {value?: string}
maxLength={props.maxLength}
disabled={props.disabled}
placeholder={props.placeholder}
onKeyDown={e => {
if(e.keyCode === 13) {
// prevent form submit on enter
e.preventDefault();
if(props.confirmOnEnter) {
handleConfirm();
}
}
}}
onChange={e => handleChange(e.target.value)}
onInput={e => setEditing(true)}
onFocus={e => setEditing(true)}
@ -48,7 +77,7 @@ export const DataEntryInput: React.FC<TextDataEntryInputProps & {value?: string}
props.autofill &&
<AutofillDropdown
editing={isEditing}
onSelect={value => handleChange(value)}
onSelect={handleAutofillSelect}
onClose={() => setEditing(false)}
fieldName={props.slug}
fieldValue={props.value}

View File

@ -10,6 +10,10 @@ import { DataTitleCopyable } from '../data-title';
interface MultiDataEntryProps extends BaseDataEntryProps, TextDataEntryInputProps {
value: string[];
editableEntries: boolean;
confirmOnEnter: boolean;
addOnAutofillSelect: boolean;
acceptAutofillValuesOnly: boolean;
}
interface MultiDataEntryState {
@ -19,7 +23,10 @@ interface MultiDataEntryState {
class MultiDataEntry extends Component<MultiDataEntryProps, MultiDataEntryState> {
static defaultProps = {
editableEntries: false
editableEntries: false,
confirmOnEnter: true,
addOnAutofillSelect: false,
acceptAutofillValuesOnly: false
};
constructor(props) {
@ -32,7 +39,6 @@ class MultiDataEntry extends Component<MultiDataEntryProps, MultiDataEntryState>
this.edit = this.edit.bind(this);
this.addNew = this.addNew.bind(this);
this.remove = this.remove.bind(this);
this.getValues = this.getValues.bind(this);
}
getValues() {
@ -52,10 +58,12 @@ class MultiDataEntry extends Component<MultiDataEntryProps, MultiDataEntryState>
values.splice(index, 1, value);
this.props.onChange(this.props.slug, values);
}
addNew(event) {
event.preventDefault();
if (this.state.newValue == undefined) return;
const values = this.cloneValues().concat(this.state.newValue);
addNew(newValue?: string) {
// accept a newValue parameter to handle cases where the value is set and submitted at the same time
// (like with autofill select enabled) - but otherwise use the current newValue saved in state
const val = newValue ?? this.state.newValue;
if (val == undefined) return;
const values = this.cloneValues().concat(val);
this.setState({newValue: null});
this.props.onChange(this.props.slug, values);
}
@ -69,7 +77,8 @@ class MultiDataEntry extends Component<MultiDataEntryProps, MultiDataEntryState>
render() {
const values = this.getValues();
const props = this.props;
const isDisabled = props.mode === 'view' || props.disabled;
const isEditing = props.mode === 'edit';
const isDisabled = !isEditing || props.disabled;
return <Fragment>
<DataTitleCopyable
slug={props.slug}
@ -80,7 +89,7 @@ class MultiDataEntry extends Component<MultiDataEntryProps, MultiDataEntryState>
<div id={`${props.slug}-wrapper`}>
<ul className="data-link-list">
{
values.length === 0 &&
values.length === 0 && !isEditing &&
<div className="input-group">
<input className="form-control no-entries" type="text" value="No entries" disabled={true} />
</div>
@ -124,18 +133,22 @@ class MultiDataEntry extends Component<MultiDataEntryProps, MultiDataEntryState>
value={this.state.newValue}
disabled={props.disabled}
onChange={(key, val) => this.setNewValue(val)}
onConfirm={(key, val) => this.addNew(val)}
maxLength={props.maxLength}
placeholder={props.placeholder}
valueTransform={props.valueTransform}
confirmOnEnter={props.confirmOnEnter}
autofill={props.autofill}
showAllOptionsOnEmpty={props.showAllOptionsOnEmpty}
confirmOnAutofillSelect={true}
/>
<div className="input-group-append">
<button type="button"
className="btn btn-outline-dark"
title="Add to list"
onClick={this.addNew}
onClick={() => this.addNew()}
disabled={this.state.newValue == undefined}
>+</button>
</div>

View File

@ -15,17 +15,22 @@ const UseView: React.FunctionComponent<CategoryViewProps> = (props) => (
<Fragment>
<MultiDataEntry
title={dataFields.current_landuse_group.title}
tooltip={dataFields.current_landuse_group.tooltip}
slug="current_landuse_group"
value={props.building.current_landuse_group}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
// tooltip={dataFields.current_landuse_class.tooltip}
placeholder="New land use group..."
confirmOnEnter={true}
tooltip={dataFields.current_landuse_group.tooltip}
placeholder="Type new land use group here"
autofill={true}
showAllOptionsOnEmpty={true}
addOnAutofillSelect={true}
/>
{
props.mode != 'view' &&
<InfoBox msg="Land use order is automatically derived from the land use groups"></InfoBox>
}
<DataEntry
title={dataFields.current_landuse_order.title}
tooltip={dataFields.current_landuse_order.tooltip}
@ -36,10 +41,6 @@ const UseView: React.FunctionComponent<CategoryViewProps> = (props) => (
copy={props.copy}
onChange={props.onChange}
/>
{
props.mode != 'view' &&
<InfoBox msg="Land use order is automatically derived from the land use groups"></InfoBox>
}
</Fragment>
);
const UseContainer = withCopyEdit(UseView);

View File

@ -21,3 +21,7 @@
.tooltip .arrow {
right: 7px;
}
.tooltip a {
color: rgb(255, 11, 245);
}

View File

@ -79,7 +79,7 @@ class Tooltip extends Component<TooltipProps, TooltipState> {
<div className="tooltip bs-tooltip-bottom">
<div className="arrow"></div>
<div className="tooltip-inner">
{...tooltipTextToComponents(this.props.text)}
{tooltipTextToComponents(this.props.text)}
</div>
</div>
)