Skip to content

Commit 83e18e5

Browse files
authored
Merge pull request #115 from OneNoteDev/feature/create-error
Implement error popovers and keyboard actions
2 parents 1bf4b1c + ee3e334 commit 83e18e5

12 files changed

+240
-37
lines changed

package-lock.json

+29
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "onenotepicker",
3-
"version": "2.4.1",
3+
"version": "2.4.2",
44
"files": [
55
"dist/**/*"
66
],
@@ -54,6 +54,7 @@
5454
"react-dev-utils": "^4.1.0",
5555
"react-dom": "^15.6.2",
5656
"react-hot-loader": "^1.3.1",
57+
"react-popper": "0.10.1",
5758
"react-test-renderer": "^15.6.2",
5859
"rimraf": "^2.6.1",
5960
"sass-loader": "^6.0.5",

src/components/createNewNotebook/createNewNotebookErrorRenderStrategy.tsx

+3-5
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
import * as React from 'react';
22

33
import { NodeRenderStrategy } from '../treeView/nodeRenderStrategy';
4-
import { ErrorInfoIconSvg } from '../icons/errorInfoIcon.svg';
54
import { Strings } from '../../strings';
65
import { CreateNewNotebookCommonProperties } from './createNewNotebookCommonProperties';
76
import { CreateNewNotebookRowTemplate } from './createNewNotebookRowTemplate';
7+
import { ErrorIconWithPopover } from '../errorIconWithPopover';
88

99
export class CreateNewNotebookErrorRenderStrategy extends CreateNewNotebookCommonProperties implements NodeRenderStrategy {
1010
onClickBinded = () => { };
1111

1212
// We don't listen for enter as we assume that we want the user to change the name before
1313
// re-attempting the create
1414
constructor(
15+
private errorMessage: string,
1516
private notebookNameInputValue: string,
1617
private onChangeBinded: (event: React.ChangeEvent<HTMLInputElement>) => void) {
1718
super();
1819
}
1920

2021
element(): JSX.Element {
21-
// TODO (machiam) error info should have a popover as per redlines
2222
return (
2323
<CreateNewNotebookRowTemplate>
2424
<div className='picker-label'>
@@ -30,9 +30,7 @@ export class CreateNewNotebookErrorRenderStrategy extends CreateNewNotebookCommo
3030
value={this.notebookNameInputValue}
3131
onChange={this.onChangeBinded} />
3232
</div>
33-
<div className='error-info-icon'>
34-
<ErrorInfoIconSvg />
35-
</div>
33+
<ErrorIconWithPopover errorMessage={this.errorMessage}></ErrorIconWithPopover>
3634
</CreateNewNotebookRowTemplate>
3735
);
3836
}

src/components/createNewNotebook/createNewNotebookInputRenderStrategy.tsx

+4-7
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import * as React from 'react';
22

33
import { NodeRenderStrategy } from '../treeView/nodeRenderStrategy';
4-
import { ErrorInfoIconSvg } from '../icons/errorInfoIcon.svg';
54
import { Strings } from '../../strings';
65
import { NameValidator } from '../../nameValidator';
76
import { CreateNewNotebookCommonProperties } from './createNewNotebookCommonProperties';
87
import { CreateNewNotebookRowTemplate } from './createNewNotebookRowTemplate';
8+
import { ErrorIconWithPopover } from '../errorIconWithPopover';
99

1010
export class CreateNewNotebookInputRenderStrategy extends CreateNewNotebookCommonProperties implements NodeRenderStrategy {
1111
onClickBinded = () => { };
@@ -19,7 +19,6 @@ export class CreateNewNotebookInputRenderStrategy extends CreateNewNotebookCommo
1919
}
2020

2121
element(): JSX.Element {
22-
// TODO (machiam) error info should have a popover as per redlines
2322
return (
2423
<CreateNewNotebookRowTemplate>
2524
<div className='picker-label'>
@@ -34,15 +33,13 @@ export class CreateNewNotebookInputRenderStrategy extends CreateNewNotebookCommo
3433
onKeyPress={this.onKeyPress.bind(this)}
3534
onChange={this.onChangeBinded} />
3635
</div>
37-
<div className='error-info-icon' style={this.showError() ? { } : { visibility: 'hidden' }}>
38-
<ErrorInfoIconSvg />
39-
</div>
36+
<ErrorIconWithPopover errorMessage={this.errorIfExists()}></ErrorIconWithPopover>
4037
</CreateNewNotebookRowTemplate>
4138
);
4239
}
4340

44-
private showError(): boolean {
45-
return !!NameValidator.validateNotebookName(this.notebookNameInputValue);
41+
private errorIfExists(): string | undefined {
42+
return NameValidator.validateNotebookName(this.notebookNameInputValue);
4643
}
4744

4845
private onKeyPress(event): void {

src/components/createNewNotebook/createNewNotebookNode.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ export class CreateNewNotebookNode extends React.Component<CreateNewNotebookNode
4040
return new CreateNewNotebookInputRenderStrategy(inputValue, onEnter, onInputChange, setInputRefAndFocus);
4141
}
4242

43-
private createErrorRenderStrategy(inputValue: string, onInputChange: (evt: React.ChangeEvent<HTMLInputElement>) => void): NodeRenderStrategy {
44-
return new CreateNewNotebookErrorRenderStrategy(inputValue, onInputChange);
43+
private createErrorRenderStrategy(errorMessage: string, inputValue: string, onInputChange: (evt: React.ChangeEvent<HTMLInputElement>) => void): NodeRenderStrategy {
44+
return new CreateNewNotebookErrorRenderStrategy(errorMessage, inputValue, onInputChange);
4545
}
4646

4747
private inProgressRenderStrategy(inputValue: string): NodeRenderStrategy {

src/components/createNewSection/createNewSectionErrorRenderStrategy.tsx

+3-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import * as React from 'react';
22

33
import { NodeRenderStrategy } from '../treeView/nodeRenderStrategy';
4-
import { ErrorInfoIconSvg } from '../icons/errorInfoIcon.svg';
54
import { Strings } from '../../strings';
65
import { CreateNewSectionCommonProperties } from './createNewSectionCommonProperties';
76
import { CreateNewSectionRowTemplate } from './createNewSectionRowTemplate';
7+
import { ErrorIconWithPopover } from '../errorIconWithPopover';
88

99
export class CreateNewSectionErrorRenderStrategy extends CreateNewSectionCommonProperties implements NodeRenderStrategy {
1010
onClickBinded = () => { };
@@ -13,13 +13,13 @@ export class CreateNewSectionErrorRenderStrategy extends CreateNewSectionCommonP
1313
// re-attempting the create
1414
constructor(
1515
parentId: string,
16+
private errorMessage: string,
1617
private sectionNameInputValue: string,
1718
private onChangeBinded: (event: React.ChangeEvent<HTMLInputElement>) => void) {
1819
super(parentId);
1920
}
2021

2122
element(): JSX.Element {
22-
// TODO (machiam) error info should have a popover as per redlines
2323
return (
2424
<CreateNewSectionRowTemplate>
2525
<div className='picker-label'>
@@ -31,9 +31,7 @@ export class CreateNewSectionErrorRenderStrategy extends CreateNewSectionCommonP
3131
value={this.sectionNameInputValue}
3232
onChange={this.onChangeBinded} />
3333
</div>
34-
<div className='error-info-icon'>
35-
<ErrorInfoIconSvg />
36-
</div>
34+
<ErrorIconWithPopover errorMessage={this.errorMessage}></ErrorIconWithPopover>
3735
</CreateNewSectionRowTemplate>
3836
);
3937
}

src/components/createNewSection/createNewSectionInputRenderStrategy.tsx

+4-7
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import * as React from 'react';
22

33
import { NodeRenderStrategy } from '../treeView/nodeRenderStrategy';
4-
import { ErrorInfoIconSvg } from '../icons/errorInfoIcon.svg';
54
import { Strings } from '../../strings';
65
import { NameValidator } from '../../nameValidator';
76
import { CreateNewSectionCommonProperties } from './createNewSectionCommonProperties';
87
import { CreateNewSectionRowTemplate } from './createNewSectionRowTemplate';
8+
import { ErrorIconWithPopover } from '../errorIconWithPopover';
99

1010
export class CreateNewSectionInputRenderStrategy extends CreateNewSectionCommonProperties implements NodeRenderStrategy {
1111
onClickBinded = () => { };
@@ -20,7 +20,6 @@ export class CreateNewSectionInputRenderStrategy extends CreateNewSectionCommonP
2020
}
2121

2222
element(): JSX.Element {
23-
// TODO (machiam) error info should have a popover as per redlines
2423
return (
2524
<CreateNewSectionRowTemplate>
2625
<div className='picker-label'>
@@ -34,15 +33,13 @@ export class CreateNewSectionInputRenderStrategy extends CreateNewSectionCommonP
3433
onKeyPress={this.onKeyPress.bind(this)}
3534
onChange={this.onChangeBinded} />
3635
</div>
37-
<div className='error-info-icon' style={this.showError() ? { } : { visibility: 'hidden' }}>
38-
<ErrorInfoIconSvg />
39-
</div>
36+
<ErrorIconWithPopover errorMessage={this.errorIfExists()}></ErrorIconWithPopover>
4037
</CreateNewSectionRowTemplate>
4138
);
4239
}
4340

44-
private showError(): boolean {
45-
return !!NameValidator.validateNotebookName(this.notebookNameInputValue);
41+
private errorIfExists(): string | undefined {
42+
return NameValidator.validateSectionName(this.notebookNameInputValue);
4643
}
4744

4845
private onKeyPress(event): void {

src/components/createNewSection/createNewSectionNode.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ export class CreateNewSectionNode extends React.Component<CreateNewSectionNodePr
4444
return new CreateNewSectionInputRenderStrategy(this.props.parent.id, inputValue, onEnter, onInputChange, setInputRefAndFocus);
4545
}
4646

47-
private createErrorRenderStrategy(inputValue: string, onInputChange: (evt: React.ChangeEvent<HTMLInputElement>) => void): NodeRenderStrategy {
48-
return new CreateNewSectionErrorRenderStrategy(this.props.parent.id, inputValue, onInputChange);
47+
private createErrorRenderStrategy(errorMessage: string, inputValue: string, onInputChange: (evt: React.ChangeEvent<HTMLInputElement>) => void): NodeRenderStrategy {
48+
return new CreateNewSectionErrorRenderStrategy(this.props.parent.id, errorMessage, inputValue, onInputChange);
4949
}
5050

5151
private inProgressRenderStrategy(inputValue: string): NodeRenderStrategy {
+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import * as React from 'react';
2+
import { Manager, Target, Popper, Arrow } from 'react-popper';
3+
4+
import { ErrorInfoIconSvg } from './icons/errorInfoIcon.svg';
5+
6+
export interface ErrorIconWithPopoverProps {
7+
errorMessage: string | undefined;
8+
}
9+
10+
export interface ErrorIconWithPopoverState {
11+
popoverIsOpen: boolean;
12+
}
13+
14+
export class ErrorIconWithPopover extends React.Component<ErrorIconWithPopoverProps, ErrorIconWithPopoverState> {
15+
private wrapperRef: Node;
16+
17+
constructor(props: ErrorIconWithPopoverProps) {
18+
super(props);
19+
20+
this.state = {
21+
popoverIsOpen: false
22+
};
23+
24+
// Used to handle if user clicks outside error icon, the popover should close
25+
this.setWrapperRef = this.setWrapperRef.bind(this);
26+
this.handleClickOutside = this.handleClickOutside.bind(this);
27+
28+
// Used to handle if the user scrolls anywhere on the page, the popover should close
29+
this.scrollHandler = this.scrollHandler.bind(this);
30+
31+
// Used to handle if the user hits space on the error icon, the popover should toggle visibility
32+
this.keyUpHandler = this.keyUpHandler.bind(this);
33+
34+
// Used to handle if the user clicks the error icon, the popover should toggle visibility
35+
this.clickHandler = this.clickHandler.bind(this);
36+
}
37+
38+
componentDidMount() {
39+
document.addEventListener('mousedown', this.handleClickOutside);
40+
window.addEventListener('scroll', this.scrollHandler, true);
41+
}
42+
43+
componentWillUnmount() {
44+
document.removeEventListener('mousedown', this.handleClickOutside);
45+
window.removeEventListener('scroll', this.scrollHandler, true);
46+
}
47+
48+
componentWillReceiveProps() {
49+
this.setState({
50+
popoverIsOpen: false
51+
});
52+
}
53+
54+
private handleClickOutside(event) {
55+
if (this.wrapperRef && !this.wrapperRef.contains(event.target) && this.state.popoverIsOpen) {
56+
this.setState({
57+
popoverIsOpen: false
58+
});
59+
}
60+
}
61+
62+
private setWrapperRef(node) {
63+
this.wrapperRef = node as Node;
64+
}
65+
66+
private scrollHandler() {
67+
this.setState({
68+
popoverIsOpen: false
69+
});
70+
}
71+
72+
private keyUpHandler(event) {
73+
// Space
74+
if (event.keyCode === 32) {
75+
this.clickHandler();
76+
}
77+
}
78+
79+
private clickHandler() {
80+
this.setState({
81+
popoverIsOpen: !this.state.popoverIsOpen
82+
});
83+
}
84+
85+
render() {
86+
return (
87+
<div ref={this.setWrapperRef} className='error-info-icon'>
88+
<Manager >
89+
<Target>
90+
<div
91+
tabIndex={0}
92+
style={this.props.errorMessage ? {} : { visibility: 'hidden' }}
93+
onClick={this.clickHandler}
94+
onKeyUp={this.keyUpHandler}>
95+
<ErrorInfoIconSvg/>
96+
</div>
97+
</Target>
98+
<Popper
99+
placement={'bottom'}
100+
modifiers={{
101+
hide: {
102+
enabled: false
103+
},
104+
preventOverflow: {
105+
enabled: true,
106+
},
107+
arrow: {
108+
enabled: true
109+
}
110+
}}
111+
className='error-info-popover popper'
112+
eventsEnabled={true}>
113+
<div
114+
className='error-info-popover-content'
115+
style={this.shouldShowPopover() ? {} : { visibility: 'hidden' }}>
116+
{this.props.errorMessage}
117+
</div>
118+
<Arrow className='popper__arrow' />
119+
</Popper>
120+
</Manager>
121+
</div>
122+
);
123+
}
124+
125+
private shouldShowPopover() {
126+
return this.state.popoverIsOpen;
127+
}
128+
}

0 commit comments

Comments
 (0)