2019-09-08 18:57:57 +02:00
|
|
|
import PropTypes from 'prop-types';
|
|
|
|
import React from 'react';
|
2020-08-26 17:28:53 +02:00
|
|
|
import { withTranslation } from 'react-i18next';
|
|
|
|
import i18next from 'i18next';
|
2020-04-30 00:34:37 +02:00
|
|
|
import EditIcon from '../../../images/pencil.svg';
|
2019-09-08 18:57:57 +02:00
|
|
|
|
2020-08-26 17:28:53 +02:00
|
|
|
|
2020-05-06 01:03:58 +02:00
|
|
|
// TODO I think this needs a description prop so that it's accessible
|
2019-09-08 18:57:57 +02:00
|
|
|
function EditableInput({
|
2020-07-24 23:11:10 +02:00
|
|
|
validate,
|
|
|
|
value,
|
|
|
|
emptyPlaceholder,
|
|
|
|
InputComponent,
|
|
|
|
inputProps,
|
|
|
|
onChange,
|
2019-09-08 18:57:57 +02:00
|
|
|
}) {
|
|
|
|
const [isEditing, setIsEditing] = React.useState(false);
|
|
|
|
const [currentValue, setCurrentValue] = React.useState(value || '');
|
|
|
|
const displayValue = currentValue || emptyPlaceholder;
|
2020-01-15 11:45:37 +01:00
|
|
|
const hasValue = currentValue !== '';
|
2020-07-24 23:11:10 +02:00
|
|
|
const classes = `editable-input editable-input--${
|
|
|
|
isEditing ? 'is-editing' : 'is-not-editing'
|
|
|
|
} editable-input--${hasValue ? 'has-value' : 'has-placeholder'}`;
|
2019-09-08 18:57:57 +02:00
|
|
|
const inputRef = React.createRef();
|
|
|
|
|
|
|
|
React.useEffect(() => {
|
|
|
|
if (isEditing) {
|
|
|
|
inputRef.current.focus();
|
|
|
|
}
|
|
|
|
}, [isEditing]);
|
|
|
|
|
|
|
|
function beginEditing() {
|
|
|
|
setIsEditing(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
function doneEditing() {
|
|
|
|
setIsEditing(false);
|
|
|
|
|
|
|
|
const isValid = typeof validate === 'function' && validate(currentValue);
|
|
|
|
|
|
|
|
if (isValid) {
|
|
|
|
onChange(currentValue);
|
|
|
|
} else {
|
|
|
|
setCurrentValue(value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function updateValue(event) {
|
|
|
|
setCurrentValue(event.target.value);
|
|
|
|
}
|
|
|
|
|
|
|
|
function checkForKeyAction(event) {
|
|
|
|
if (event.key === 'Enter') {
|
|
|
|
doneEditing();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<span className={classes}>
|
2020-05-06 01:03:58 +02:00
|
|
|
<button
|
|
|
|
className="editable-input__label"
|
|
|
|
onClick={beginEditing}
|
2020-08-26 17:28:53 +02:00
|
|
|
aria-label={this.props.t('EditableInput.EditValue', { display: displayValue })}
|
2020-05-06 01:03:58 +02:00
|
|
|
>
|
2019-09-08 18:57:57 +02:00
|
|
|
<span>{displayValue}</span>
|
2020-07-24 23:11:10 +02:00
|
|
|
<EditIcon
|
|
|
|
className="editable-input__icon"
|
|
|
|
focusable="false"
|
|
|
|
aria-hidden="true"
|
|
|
|
/>
|
2019-09-08 18:57:57 +02:00
|
|
|
</button>
|
|
|
|
|
|
|
|
<InputComponent
|
|
|
|
className="editable-input__input"
|
|
|
|
type="text"
|
|
|
|
{...inputProps}
|
|
|
|
disabled={!isEditing}
|
|
|
|
onBlur={doneEditing}
|
|
|
|
onChange={updateValue}
|
|
|
|
onKeyPress={checkForKeyAction}
|
|
|
|
ref={inputRef}
|
|
|
|
value={currentValue}
|
|
|
|
/>
|
2020-07-24 23:11:10 +02:00
|
|
|
</span>
|
2019-09-08 18:57:57 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
EditableInput.defaultProps = {
|
2020-08-26 17:28:53 +02:00
|
|
|
emptyPlaceholder: i18next.t('EditableInput.EmptyPlaceholder'),
|
2019-09-08 18:57:57 +02:00
|
|
|
InputComponent: 'input',
|
|
|
|
inputProps: {},
|
|
|
|
validate: () => true,
|
|
|
|
value: '',
|
|
|
|
};
|
|
|
|
|
|
|
|
EditableInput.propTypes = {
|
|
|
|
emptyPlaceholder: PropTypes.string,
|
|
|
|
InputComponent: PropTypes.elementType,
|
|
|
|
// eslint-disable-next-line react/forbid-prop-types
|
2020-07-24 23:11:10 +02:00
|
|
|
inputProps: PropTypes.object, // eslint-disable-line
|
2019-09-08 18:57:57 +02:00
|
|
|
onChange: PropTypes.func.isRequired,
|
|
|
|
validate: PropTypes.func,
|
|
|
|
value: PropTypes.string,
|
2020-08-26 17:28:53 +02:00
|
|
|
t: PropTypes.func.isRequired
|
2019-09-08 18:57:57 +02:00
|
|
|
};
|
|
|
|
|
2020-08-26 17:28:53 +02:00
|
|
|
export default withTranslation()(EditableInput);
|