EditableInput component
This commit is contained in:
parent
c57ead4c62
commit
521e117d95
3 changed files with 130 additions and 0 deletions
92
client/modules/IDE/components/EditableInput.jsx
Normal file
92
client/modules/IDE/components/EditableInput.jsx
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import InlineSVG from 'react-inlinesvg';
|
||||||
|
|
||||||
|
const editIconUrl = require('../../../images/pencil.svg');
|
||||||
|
|
||||||
|
function EditIcon() {
|
||||||
|
return <InlineSVG className="editable-input__icon" src={editIconUrl} alt="Edit" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function EditableInput({
|
||||||
|
validate, value, emptyPlaceholder, InputComponent, inputProps, onChange
|
||||||
|
}) {
|
||||||
|
const [isEditing, setIsEditing] = React.useState(false);
|
||||||
|
const [currentValue, setCurrentValue] = React.useState(value || '');
|
||||||
|
const displayValue = currentValue || emptyPlaceholder;
|
||||||
|
const classes = `editable-input editable-input--${isEditing ? 'is-editing' : 'is-not-editing'}`;
|
||||||
|
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}>
|
||||||
|
<button className="editable-input__label" onClick={beginEditing}>
|
||||||
|
<span>{displayValue}</span>
|
||||||
|
<EditIcon />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<InputComponent
|
||||||
|
className="editable-input__input"
|
||||||
|
type="text"
|
||||||
|
{...inputProps}
|
||||||
|
disabled={!isEditing}
|
||||||
|
onBlur={doneEditing}
|
||||||
|
onChange={updateValue}
|
||||||
|
onKeyPress={checkForKeyAction}
|
||||||
|
ref={inputRef}
|
||||||
|
value={currentValue}
|
||||||
|
/>
|
||||||
|
</span >
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
EditableInput.defaultProps = {
|
||||||
|
emptyPlaceholder: 'No value',
|
||||||
|
InputComponent: 'input',
|
||||||
|
inputProps: {},
|
||||||
|
validate: () => true,
|
||||||
|
value: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
EditableInput.propTypes = {
|
||||||
|
emptyPlaceholder: PropTypes.string,
|
||||||
|
InputComponent: PropTypes.elementType,
|
||||||
|
// eslint-disable-next-line react/forbid-prop-types
|
||||||
|
inputProps: PropTypes.object,
|
||||||
|
onChange: PropTypes.func.isRequired,
|
||||||
|
validate: PropTypes.func,
|
||||||
|
value: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditableInput;
|
37
client/styles/components/_editable-input.scss
Normal file
37
client/styles/components/_editable-input.scss
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
.editable-input {
|
||||||
|
height: 70%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editable-input__label {
|
||||||
|
@include themify() {
|
||||||
|
color: getThemifyVariable('inactive-text-color');
|
||||||
|
&:hover {
|
||||||
|
color: getThemifyVariable('primary-text-color');
|
||||||
|
& .editable-input__icon path {
|
||||||
|
fill: getThemifyVariable('primary-text-color');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cursor: pointer;
|
||||||
|
line-height: #{18 / $base-font-size}rem;
|
||||||
|
|
||||||
|
font-size: unset;
|
||||||
|
font-weight: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editable-input__icon svg {
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editable-input--is-not-editing .editable-input__input,
|
||||||
|
.editable-input--is-editing .editable-input__label {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editable-input--is-editing .editable-input__input,
|
||||||
|
.editable-input--is-not-editing .editable-input__label {
|
||||||
|
display: block;
|
||||||
|
}
|
|
@ -48,6 +48,7 @@
|
||||||
@import 'components/uploader';
|
@import 'components/uploader';
|
||||||
@import 'components/tabs';
|
@import 'components/tabs';
|
||||||
@import 'components/dashboard-header';
|
@import 'components/dashboard-header';
|
||||||
|
@import 'components/editable-input';
|
||||||
|
|
||||||
@import 'layout/dashboard';
|
@import 'layout/dashboard';
|
||||||
@import 'layout/ide';
|
@import 'layout/ide';
|
||||||
|
|
Loading…
Reference in a new issue