Skip to content

Commit fc6496c

Browse files
committed
use EditableInput in Toolbar
1 parent 613bb86 commit fc6496c

File tree

8 files changed

+64
-140
lines changed

8 files changed

+64
-140
lines changed

client/constants.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@ export const PROJECT_SAVE_SUCCESS = 'PROJECT_SAVE_SUCCESS';
3030
export const PROJECT_SAVE_FAIL = 'PROJECT_SAVE_FAIL';
3131
export const NEW_PROJECT = 'NEW_PROJECT';
3232
export const RESET_PROJECT = 'RESET_PROJECT';
33-
export const SHOW_EDIT_PROJECT_NAME = 'SHOW_EDIT_PROJECT_NAME';
34-
export const HIDE_EDIT_PROJECT_NAME = 'HIDE_EDIT_PROJECT_NAME';
3533

3634
export const SET_PROJECT = 'SET_PROJECT';
3735
export const SET_PROJECTS = 'SET_PROJECTS';

client/modules/IDE/actions/project.js

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -351,18 +351,6 @@ export function cloneProject(project) {
351351
};
352352
}
353353

354-
export function showEditProjectName() {
355-
return {
356-
type: ActionTypes.SHOW_EDIT_PROJECT_NAME
357-
};
358-
}
359-
360-
export function hideEditProjectName() {
361-
return {
362-
type: ActionTypes.HIDE_EDIT_PROJECT_NAME
363-
};
364-
}
365-
366354
export function setProjectSavedTime(updatedAt) {
367355
return {
368356
type: ActionTypes.SET_PROJECT_SAVED_TIME,

client/modules/IDE/components/EditableInput.jsx

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,36 @@ function EditableInput({
1111
emptyPlaceholder,
1212
InputComponent,
1313
inputProps,
14-
onChange
14+
onChange,
15+
disabled,
16+
'aria-label': ariaLabel
1517
}) {
1618
const [isEditing, setIsEditing] = React.useState(false);
1719
const [currentValue, setCurrentValue] = React.useState(value || '');
1820
const displayValue = currentValue || emptyPlaceholder;
1921
const hasValue = currentValue !== '';
2022
const classes = `editable-input editable-input--${
2123
isEditing ? 'is-editing' : 'is-not-editing'
22-
} editable-input--${hasValue ? 'has-value' : 'has-placeholder'}`;
23-
const inputRef = React.createRef();
24+
} editable-input--${hasValue ? 'has-value' : 'has-placeholder'} ${
25+
disabled ? 'editable-input--disabled' : ''
26+
}`;
27+
const inputRef = React.useRef();
2428
const { t } = useTranslation();
2529
React.useEffect(() => {
2630
if (isEditing) {
27-
inputRef.current.focus();
31+
inputRef.current?.focus();
2832
}
2933
}, [isEditing]);
3034

3135
function beginEditing() {
3236
setIsEditing(true);
3337
}
3438

39+
function cancelEditing() {
40+
setIsEditing(false);
41+
setCurrentValue(value);
42+
}
43+
3544
function doneEditing() {
3645
setIsEditing(false);
3746

@@ -51,6 +60,8 @@ function EditableInput({
5160
function checkForKeyAction(event) {
5261
if (event.key === 'Enter') {
5362
doneEditing();
63+
} else if (event.key === 'Escape' || event.key === 'Esc') {
64+
cancelEditing();
5465
}
5566
}
5667

@@ -59,7 +70,11 @@ function EditableInput({
5970
<button
6071
className="editable-input__label"
6172
onClick={beginEditing}
62-
aria-label={t('EditableInput.EditValue', { display: displayValue })}
73+
aria-label={
74+
ariaLabel ?? t('EditableInput.EditValue', { display: displayValue })
75+
}
76+
aria-hidden={isEditing}
77+
disabled={disabled}
6378
>
6479
<span>{displayValue}</span>
6580
<EditIcon
@@ -74,9 +89,10 @@ function EditableInput({
7489
type="text"
7590
{...inputProps}
7691
disabled={!isEditing}
92+
aria-hidden={!isEditing}
7793
onBlur={doneEditing}
7894
onChange={updateValue}
79-
onKeyPress={checkForKeyAction}
95+
onKeyDown={checkForKeyAction}
8096
ref={inputRef}
8197
value={currentValue}
8298
/>
@@ -89,7 +105,9 @@ EditableInput.defaultProps = {
89105
InputComponent: 'input',
90106
inputProps: {},
91107
validate: () => true,
92-
value: ''
108+
value: '',
109+
disabled: false,
110+
'aria-label': undefined
93111
};
94112

95113
EditableInput.propTypes = {
@@ -99,7 +117,9 @@ EditableInput.propTypes = {
99117
inputProps: PropTypes.object, // eslint-disable-line
100118
onChange: PropTypes.func.isRequired,
101119
validate: PropTypes.func,
102-
value: PropTypes.string
120+
value: PropTypes.string,
121+
disabled: PropTypes.bool,
122+
'aria-label': PropTypes.string
103123
};
104124

105125
export default EditableInput;

client/modules/IDE/components/Toolbar.jsx

Lines changed: 14 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -11,53 +11,19 @@ import * as projectActions from '../actions/project';
1111
import PlayIcon from '../../../images/play.svg';
1212
import StopIcon from '../../../images/stop.svg';
1313
import PreferencesIcon from '../../../images/preferences.svg';
14-
import EditProjectNameIcon from '../../../images/pencil.svg';
14+
import EditableInput from './EditableInput';
1515

1616
class Toolbar extends React.Component {
1717
constructor(props) {
1818
super(props);
19-
this.handleKeyPress = this.handleKeyPress.bind(this);
20-
this.handleProjectNameChange = this.handleProjectNameChange.bind(this);
21-
this.handleProjectNameClick = this.handleProjectNameClick.bind(this);
2219
this.handleProjectNameSave = this.handleProjectNameSave.bind(this);
23-
24-
this.state = {
25-
projectNameInputValue: props.project.name
26-
};
27-
}
28-
29-
handleKeyPress(event) {
30-
if (event.key === 'Enter') {
31-
this.props.hideEditProjectName();
32-
this.projectNameInput.blur();
33-
}
34-
}
35-
36-
handleProjectNameChange(event) {
37-
this.setState({ projectNameInputValue: event.target.value });
3820
}
3921

40-
handleProjectNameClick() {
41-
if (this.canEditProjectName) {
42-
this.props.showEditProjectName();
43-
setTimeout(() => {
44-
this.projectNameInput?.focus();
45-
}, 140);
46-
}
47-
}
48-
49-
handleProjectNameSave() {
50-
const newProjectName = this.state.projectNameInputValue.trim();
51-
if (newProjectName.length === 0) {
52-
this.setState({
53-
projectNameInputValue: this.props.project.name
54-
});
55-
} else {
56-
this.props.setProjectName(newProjectName);
57-
this.props.hideEditProjectName();
58-
if (this.props.project.id) {
59-
this.props.saveProject();
60-
}
22+
handleProjectNameSave(value) {
23+
const newProjectName = value.trim();
24+
this.props.setProjectName(newProjectName);
25+
if (this.props.project.id) {
26+
this.props.saveProject();
6127
}
6228
}
6329

@@ -84,11 +50,6 @@ class Toolbar extends React.Component {
8450
'toolbar__preferences-button': true,
8551
'toolbar__preferences-button--selected': this.props.preferencesIsVisible
8652
});
87-
const nameContainerClass = classNames({
88-
'toolbar__project-name-container': true,
89-
'toolbar__project-name-container--editing': this.props.project
90-
.isEditingName
91-
});
9253

9354
const canEditProjectName = this.canEditProjectName();
9455

@@ -139,34 +100,17 @@ class Toolbar extends React.Component {
139100
{this.props.t('Toolbar.Auto-refresh')}
140101
</label>
141102
</div>
142-
<div className={nameContainerClass}>
143-
<button
144-
className="toolbar__project-name"
145-
onClick={this.handleProjectNameClick}
103+
<div className="toolbar__project-name-container">
104+
<EditableInput
105+
value={this.props.project.name}
146106
disabled={!canEditProjectName}
147107
aria-label={this.props.t('Toolbar.EditSketchARIA')}
148-
>
149-
<span>{this.props.project.name}</span>
150-
{canEditProjectName && (
151-
<EditProjectNameIcon
152-
className="toolbar__edit-name-button"
153-
focusable="false"
154-
aria-hidden="true"
155-
/>
156-
)}
157-
</button>
158-
<input
159-
type="text"
160-
maxLength="128"
161-
className="toolbar__project-name-input"
162-
aria-label={this.props.t('Toolbar.NewSketchNameARIA')}
163-
value={this.state.projectNameInputValue}
164-
onChange={this.handleProjectNameChange}
165-
ref={(element) => {
166-
this.projectNameInput = element;
108+
inputProps={{
109+
maxLength: 128,
110+
'aria-label': this.props.t('Toolbar.NewSketchNameARIA')
167111
}}
168-
onBlur={this.handleProjectNameSave}
169-
onKeyPress={this.handleKeyPress}
112+
validate={(text) => text.trim().length > 0}
113+
onChange={this.handleProjectNameSave}
170114
/>
171115
{(() => {
172116
if (this.props.owner) {
@@ -205,11 +149,8 @@ Toolbar.propTypes = {
205149
}),
206150
project: PropTypes.shape({
207151
name: PropTypes.string.isRequired,
208-
isEditingName: PropTypes.bool,
209152
id: PropTypes.string
210153
}).isRequired,
211-
showEditProjectName: PropTypes.func.isRequired,
212-
hideEditProjectName: PropTypes.func.isRequired,
213154
infiniteLoop: PropTypes.bool.isRequired,
214155
autorefresh: PropTypes.bool.isRequired,
215156
setAutorefresh: PropTypes.func.isRequired,

client/modules/IDE/components/Toolbar.unit.test.jsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ const renderComponent = (extraProps = {}) => {
3131
},
3232
project: {
3333
name: 'testname',
34-
isEditingName: false,
3534
id: 'id'
3635
},
3736
t: jest.fn()
@@ -46,28 +45,31 @@ const renderComponent = (extraProps = {}) => {
4645

4746
describe('<ToolbarComponent />', () => {
4847
it('sketch owner can switch to sketch name editing mode', async () => {
49-
const props = renderComponent();
48+
renderComponent();
5049
const sketchName = screen.getByLabelText('Edit sketch name');
5150

5251
fireEvent.click(sketchName);
5352

54-
await waitFor(() => expect(props.showEditProjectName).toHaveBeenCalled());
53+
await waitFor(() => {
54+
expect(screen.getByLabelText('New sketch name')).toHaveFocus();
55+
expect(screen.getByLabelText('New sketch name')).toBeEnabled();
56+
});
5557
});
5658

5759
it("non-owner can't switch to sketch editing mode", async () => {
58-
const props = renderComponent({ currentUser: 'not-me' });
60+
renderComponent({ currentUser: 'not-me' });
5961
const sketchName = screen.getByLabelText('Edit sketch name');
6062

6163
fireEvent.click(sketchName);
6264

6365
expect(sketchName).toBeDisabled();
6466
await waitFor(() =>
65-
expect(props.showEditProjectName).not.toHaveBeenCalled()
67+
expect(screen.getByLabelText('New sketch name')).toBeDisabled()
6668
);
6769
});
6870

6971
it('sketch owner can change name', async () => {
70-
const props = renderComponent({ project: { isEditingName: true } });
72+
const props = renderComponent();
7173

7274
const sketchNameInput = screen.getByLabelText('New sketch name');
7375
fireEvent.change(sketchNameInput, {
@@ -82,7 +84,7 @@ describe('<ToolbarComponent />', () => {
8284
});
8385

8486
it("sketch owner can't change to empty name", async () => {
85-
const props = renderComponent({ project: { isEditingName: true } });
87+
const props = renderComponent();
8688

8789
const sketchNameInput = screen.getByLabelText('New sketch name');
8890
fireEvent.change(sketchNameInput, { target: { value: '' } });

client/modules/IDE/reducers/project.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,6 @@ const project = (state, action) => {
3737
};
3838
case ActionTypes.RESET_PROJECT:
3939
return initialState();
40-
case ActionTypes.SHOW_EDIT_PROJECT_NAME:
41-
return Object.assign({}, state, { isEditingName: true });
42-
case ActionTypes.HIDE_EDIT_PROJECT_NAME:
43-
return Object.assign({}, state, { isEditingName: false });
4440
case ActionTypes.SET_PROJECT_SAVED_TIME:
4541
return Object.assign({}, state, { updatedAt: action.value });
4642
case ActionTypes.START_SAVING_PROJECT:

client/styles/components/_editable-input.scss

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
button.editable-input__label {
88
display: flex;
9+
align-items: center;
910

1011
@include themify() {
1112
color: getThemifyVariable('inactive-text-color');
@@ -35,6 +36,13 @@ button.editable-input__label {
3536
height: 1.5rem;
3637
}
3738

39+
.editable-input--disabled {
40+
pointer-events: none;
41+
.editable-input__icon {
42+
display: none;
43+
}
44+
}
45+
3846
.editable-input--is-not-editing .editable-input__input,
3947
.editable-input--is-editing .editable-input__label {
4048
display: none;

0 commit comments

Comments
 (0)