Skip to content

Commit aacb88d

Browse files
authored
Add CodeEditor component. (#104)
* Add CodeEditor component. * Use KeyboardAccessible in CodeEditor. Update KeyboardAccessible to respect child's onKeyUp and onKeyDown props.
1 parent 4cb9d4a commit aacb88d

File tree

16 files changed

+521
-43
lines changed

16 files changed

+521
-43
lines changed

docs/src/services/routes/routes.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ import CallOutExample
2929
import CodeExample
3030
from '../../views/code/code_example';
3131

32+
import CodeEditorExample
33+
from '../../views/code_editor/code_editor_example';
34+
3235
import ContextMenuExample
3336
from '../../views/context_menu/context_menu_example';
3437

@@ -134,6 +137,9 @@ const components = [{
134137
}, {
135138
name: 'Code',
136139
component: CodeExample,
140+
}, {
141+
name: 'CodeEditor',
142+
component: CodeEditorExample,
137143
}, {
138144
name: 'ContextMenu',
139145
component: ContextMenuExample,
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import React, { Component } from 'react';
2+
3+
import 'brace/mode/less';
4+
import 'brace/theme/github';
5+
6+
import {
7+
EuiCodeEditor,
8+
} from '../../../../src/components';
9+
10+
export default class extends Component {
11+
state = {
12+
value: ''
13+
};
14+
15+
onChange = (value) => {
16+
this.setState({ value });
17+
};
18+
19+
render() {
20+
return (
21+
<EuiCodeEditor
22+
mode="less"
23+
theme="github"
24+
width="100%"
25+
value={this.state.value}
26+
onChange={this.onChange}
27+
setOptions={{ fontSize: '14px' }}
28+
onBlur={() => { console.log('blur'); }}
29+
/>
30+
);
31+
}
32+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import React from 'react';
2+
3+
import { renderToHtml } from '../../services';
4+
5+
import {
6+
GuidePage,
7+
GuideSection,
8+
GuideSectionTypes,
9+
} from '../../components';
10+
11+
import {
12+
EuiCode,
13+
} from '../../../../src/components';
14+
15+
import CodeEditor from './code_editor';
16+
const codeEditorSource = require('!!raw-loader!./code_editor');
17+
const codeEditorHtml = renderToHtml(CodeEditor);
18+
19+
export default props => (
20+
<GuidePage title={props.route.name}>
21+
<GuideSection
22+
title="Code Editor"
23+
source={[{
24+
type: GuideSectionTypes.JS,
25+
code: codeEditorSource,
26+
}, {
27+
type: GuideSectionTypes.HTML,
28+
code: codeEditorHtml,
29+
}]}
30+
text={
31+
<div>
32+
<p>
33+
The KuiCodeEditor component is a wrapper around <EuiCode>react-ace</EuiCode> (which
34+
itself wraps the ACE code editor), that adds an accessible keyboard mode
35+
to it. You should always use this component instead of <EuiCode>AceEditor</EuiCode>.
36+
</p>
37+
<p>
38+
All parameters, that you specify are passed down to the
39+
underlying <EuiCode>AceEditor</EuiCode> component.
40+
</p>
41+
</div>
42+
}
43+
demo={
44+
<CodeEditor />
45+
}
46+
/>
47+
</GuidePage>
48+
);
49+

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"alias": "eui"
2424
},
2525
"dependencies": {
26+
"brace": "0.10.0",
2627
"classnames": "2.2.5",
2728
"core-js": "2.5.1",
2829
"focus-trap-react": "3.0.4",
@@ -32,6 +33,7 @@
3233
"lodash": "4.17.4",
3334
"prop-types": "15.6.0",
3435
"react": "16.0.0",
36+
"react-ace": "5.5.0",
3537
"react-router": "3.2.0",
3638
"serve": "6.3.1",
3739
"tabbable": "1.1.0",

src/components/accessibility/keyboard_accessible.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ export class EuiKeyboardAccessible extends Component {
3333
if (e.keyCode === keyCodes.SPACE) {
3434
e.preventDefault();
3535
}
36+
37+
if (this.props.children.props.onKeyDown) {
38+
this.props.children.props.onKeyDown(e);
39+
}
3640
}
3741

3842
onKeyUp = e => {
@@ -41,6 +45,10 @@ export class EuiKeyboardAccessible extends Component {
4145
// Delegate to the click handler on the element.
4246
this.props.children.props.onClick(e);
4347
}
48+
49+
if (this.props.children.props.onKeyUp) {
50+
this.props.children.props.onKeyUp(e);
51+
}
4452
}
4553

4654
applyKeyboardAccessibility(child) {
@@ -86,14 +94,6 @@ const keyboardInaccessibleElement = (props, propName, componentName) => {
8694
if (typeof child.props.onClick !== 'function') {
8795
throw new Error(`${componentName}'s child's onClick prop needs to be a function.`);
8896
}
89-
90-
if (child.props.onKeyDown) {
91-
throw new Error(`${componentName}'s child can't have an onKeyDown prop because the implementation will override it.`);
92-
}
93-
94-
if (child.props.onKeyUp) {
95-
throw new Error(`${componentName}'s child can't have an onKeyUp prop because the implementation will override it.`);
96-
}
9797
};
9898

9999
EuiKeyboardAccessible.propTypes = {

src/components/accessibility/keyboard_accessible.test.js

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -83,32 +83,6 @@ describe('EuiKeyboardAccessible', () => {
8383
`child's onClick prop needs to be a function.`
8484
);
8585
});
86-
87-
test(`when the child has an onKeyDown prop`, () => {
88-
const component = ( // eslint-disable-line no-unused-vars
89-
<EuiKeyboardAccessible>
90-
<div onClick={() => {}} onKeyDown={() => {}} />
91-
</EuiKeyboardAccessible>
92-
);
93-
94-
expect(consoleStub.calledOnce).toBe(true);
95-
expect(consoleStub.getCall(0).args[0]).toContain(
96-
`child can't have an onKeyDown prop because the implementation will override it.`
97-
);
98-
});
99-
100-
test(`when the child has an onKeyUp prop`, () => {
101-
const component = ( // eslint-disable-line no-unused-vars
102-
<EuiKeyboardAccessible>
103-
<div onClick={() => {}} onKeyUp={() => {}} />
104-
</EuiKeyboardAccessible>
105-
);
106-
107-
expect(consoleStub.calledOnce).toBe(true);
108-
expect(consoleStub.getCall(0).args[0]).toContain(
109-
`child can't have an onKeyUp prop because the implementation will override it.`
110-
);
111-
});
11286
});
11387

11488
describe(`doesn't throw an error`, () => {
@@ -195,4 +169,38 @@ describe('EuiKeyboardAccessible', () => {
195169
sinon.assert.calledOnce(onClickHandler);
196170
});
197171
});
172+
173+
describe(`child's props`, () => {
174+
test(`onKeyUp handler is called`, () => {
175+
const onKeyUpHandler = sinon.stub();
176+
177+
const $button = shallow(
178+
<EuiKeyboardAccessible>
179+
<div data-div onKeyUp={onKeyUpHandler} />
180+
</EuiKeyboardAccessible>
181+
);
182+
183+
$button.find('[data-div]').simulate('keyup', {
184+
keyCode: 0,
185+
});
186+
187+
sinon.assert.calledOnce(onKeyUpHandler);
188+
});
189+
190+
test(`onKeyDown handler is called`, () => {
191+
const onKeyDownHandler = sinon.stub();
192+
193+
const $button = shallow(
194+
<EuiKeyboardAccessible>
195+
<div data-div onKeyDown={onKeyDownHandler} />
196+
</EuiKeyboardAccessible>
197+
);
198+
199+
$button.find('[data-div]').simulate('keydown', {
200+
keyCode: 0,
201+
});
202+
203+
sinon.assert.calledOnce(onKeyDownHandler);
204+
});
205+
});
198206
});

0 commit comments

Comments
 (0)