Skip to content

Commit 8f2ccc3

Browse files
committed
[UI Framework] Add KuiCodeEditor as react-ace replacement/wrapper (#14026)
* Create KuiCodeEditor component * Add additional tests * Add PropTypes for KuiCodeEditor * Rename hintInactive to isHintActive * Rename enableOverlay to stopEditing * Rename and move configureAce method * Rename onHintKeyDown to onKeyDownHint * Fix broken configureAce call * Add onBlur to editor example * Regroup test cases * Don't lose value in KuiCodeEditor example * Remove window.alert, due to annoying behavior when switching tabs * Remove unnecessary constructor * Replace string ref by callback ref * Add a snapshot test * Move stop editing method * Use mount to render editor during test * Extract setState into method in example
1 parent 404e12c commit 8f2ccc3

File tree

14 files changed

+322
-4
lines changed

14 files changed

+322
-4
lines changed

src/core_plugins/metrics/public/components/markdown_editor.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import PropTypes from 'prop-types';
77
import React, { Component } from 'react';
88
import tickFormatter from './lib/tick_formatter';
99
import convertSeriesToVars from './lib/convert_series_to_vars';
10-
import AceEditor from 'react-ace';
10+
import { KuiCodeEditor } from 'ui_framework/components';
1111
import _ from 'lodash';
1212
import 'brace/mode/markdown';
1313
import 'brace/theme/github';
@@ -96,7 +96,7 @@ class MarkdownEditor extends Component {
9696
return (
9797
<div className="vis_editor__markdown">
9898
<div className="vis_editor__markdown-editor">
99-
<AceEditor
99+
<KuiCodeEditor
100100
onLoad={this.handleOnLoad}
101101
mode="markdown"
102102
theme="github"

src/core_plugins/metrics/public/components/panel_config/markdown.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import PropTypes from 'prop-types';
22
import React, { Component } from 'react';
33
import SeriesEditor from '../series_editor';
44
import { IndexPattern } from '../index_pattern';
5-
import AceEditor from 'react-ace';
65
import 'brace/mode/less';
76
import Select from 'react-select';
87
import createSelectHandler from '../lib/create_select_handler';
@@ -11,6 +10,7 @@ import ColorPicker from '../color_picker';
1110
import YesNo from '../yes_no';
1211
import MarkdownEditor from '../markdown_editor';
1312
import less from 'less/lib/less-browser';
13+
import { KuiCodeEditor } from 'ui_framework/components';
1414
import { htmlIdGenerator } from 'ui_framework/services';
1515
const lessC = less(window, { env: 'production' });
1616

@@ -124,7 +124,7 @@ class MarkdownPanelConfig extends Component {
124124
<div className="vis_editor__label">Custom CSS (supports Less)</div>
125125
</div>
126126
<div className="vis_editor__ace-editor">
127-
<AceEditor
127+
<KuiCodeEditor
128128
mode="less"
129129
theme="github"
130130
width="100%"

ui_framework/dist/ui_framework.css

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,42 @@ main {
666666
.kuiCardGroup--united .kuiCard + .kuiCard {
667667
border-left: 1px solid #E0E0E0; }
668668

669+
.kuiCodeEditorWrapper {
670+
position: relative; }
671+
672+
.kuiCodeEditorKeyboardHint {
673+
position: absolute;
674+
top: 0;
675+
bottom: 0;
676+
right: 0;
677+
left: 0;
678+
background: rgba(255, 255, 255, 0.7);
679+
display: -webkit-box;
680+
display: -webkit-flex;
681+
display: -ms-flexbox;
682+
display: flex;
683+
-webkit-box-orient: vertical;
684+
-webkit-box-direction: normal;
685+
-webkit-flex-direction: column;
686+
-ms-flex-direction: column;
687+
flex-direction: column;
688+
-webkit-box-pack: center;
689+
-webkit-justify-content: center;
690+
-ms-flex-pack: center;
691+
justify-content: center;
692+
-webkit-box-align: center;
693+
-webkit-align-items: center;
694+
-ms-flex-align: center;
695+
align-items: center;
696+
text-align: center;
697+
opacity: 0; }
698+
.kuiCodeEditorKeyboardHint:focus {
699+
opacity: 1;
700+
border: 2px solid #0079a5;
701+
z-index: 1000; }
702+
.kuiCodeEditorKeyboardHint.kuiCodeEditorKeyboardHint-isInactive {
703+
display: none; }
704+
669705
.kuiCollapseButton {
670706
-webkit-appearance: none;
671707
-moz-appearance: none;

ui_framework/doc_site/src/services/routes/routes.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ import ButtonExample
1818
import CardExample
1919
from '../../views/card/card_example';
2020

21+
import CodeEditor
22+
from '../../views/code_editor/code_editor_example';
23+
2124
import CollapseButtonExample
2225
from '../../views/collapse_button/collapse_button_example';
2326

@@ -141,6 +144,10 @@ const components = [{
141144
name: 'Card',
142145
component: CardExample,
143146
hasReact: true,
147+
}, {
148+
name: 'CodeEditor',
149+
component: CodeEditor,
150+
hasReact: true
144151
}, {
145152
name: 'ColorPicker',
146153
component: ColorPickerExample,
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import React, { Component } from 'react';
2+
3+
import {
4+
KuiCodeEditor
5+
} from '../../../../components';
6+
7+
export default class extends Component {
8+
state = {
9+
value: ''
10+
};
11+
12+
onChange = (value) => {
13+
this.setState({ value });
14+
};
15+
16+
render() {
17+
return (
18+
<KuiCodeEditor
19+
mode="less"
20+
theme="github"
21+
width="100%"
22+
value={this.state.value}
23+
onChange={this.onChange}
24+
setOptions={{ fontSize: '14px' }}
25+
onBlur={() => console.log('KuiCodeEditor.onBlur() called')}
26+
/>
27+
);
28+
}
29+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import React from 'react';
2+
3+
import {
4+
GuideDemo,
5+
GuidePage,
6+
GuideSection,
7+
GuideSectionTypes,
8+
GuideText,
9+
} from '../../components';
10+
11+
import CodeEditor from './code_editor';
12+
const codeEditorSource = require('!!raw!./code_editor');
13+
14+
export default props => (
15+
<GuidePage title={props.route.name}>
16+
<GuideSection
17+
title="Code Editor"
18+
source={[{
19+
type: GuideSectionTypes.JS,
20+
code: codeEditorSource,
21+
}]}
22+
>
23+
<GuideText>
24+
<p>
25+
The KuiCodeEditor component is a wrapper around <code>react-ace</code> (which
26+
itself wraps the ACE code editor), that adds an accessible keyboard mode
27+
to it. You should always use this component instead of <code>AceReact</code>.
28+
</p>
29+
<p>
30+
All parameters, that you specify are passed down to the
31+
underlying <code>AceReact</code> component.
32+
</p>
33+
</GuideText>
34+
35+
<GuideDemo>
36+
<CodeEditor />
37+
</GuideDemo>
38+
</GuideSection>
39+
</GuidePage>
40+
);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`KuiCodeEditor is rendered 1`] = `"<div class=\\"kuiCodeEditorWrapper\\"><div class=\\"kuiCodeEditorKeyboardHint\\" id=\\"42\\" tabindex=\\"0\\" role=\\"button\\"><p class=\\"kuiText kuiVerticalRhythmSmall\\">Press Enter to start editing.</p><p class=\\"kuiText kuiVerticalRhythmSmall\\">When you’re done, press Escape to stop editing.</p></div><div id=\\"brace-editor\\" style=\\"width: 500px; height: 500px;\\" class=\\" ace_editor ace-tm testClass1 testClass2\\"><textarea class=\\"ace_text-input\\" wrap=\\"off\\" autocorrect=\\"off\\" autocapitalize=\\"off\\" spellcheck=\\"false\\" style=\\"opacity: 0;\\" tabindex=\\"-1\\"></textarea><div class=\\"ace_gutter\\"><div class=\\"ace_layer ace_gutter-layer ace_folding-enabled\\"></div><div class=\\"ace_gutter-active-line\\"></div></div><div class=\\"ace_scroller\\"><div class=\\"ace_content\\"><div class=\\"ace_layer ace_print-margin-layer\\"><div class=\\"ace_print-margin\\" style=\\"left: 4px; visibility: visible;\\"></div></div><div class=\\"ace_layer ace_marker-layer\\"></div><div class=\\"ace_layer ace_text-layer\\" style=\\"padding: 0px 4px;\\"></div><div class=\\"ace_layer ace_marker-layer\\"></div><div class=\\"ace_layer ace_cursor-layer ace_hidden-cursors\\"><div class=\\"ace_cursor\\"></div></div></div></div><div class=\\"ace_scrollbar ace_scrollbar-v\\" style=\\"display: none; width: 20px;\\"><div class=\\"ace_scrollbar-inner\\" style=\\"width: 20px;\\"></div></div><div class=\\"ace_scrollbar ace_scrollbar-h\\" style=\\"display: none; height: 20px;\\"><div class=\\"ace_scrollbar-inner\\" style=\\"height: 20px;\\"></div></div><div style=\\"height: auto; width: auto; top: 0px; left: 0px; visibility: hidden; position: absolute; white-space: pre; overflow: hidden;\\"><div style=\\"height: auto; width: auto; top: 0px; left: 0px; visibility: hidden; position: absolute; white-space: pre; overflow: visible;\\"></div><div style=\\"height: auto; width: auto; top: 0px; left: 0px; visibility: hidden; position: absolute; white-space: pre; overflow: visible;\\">XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</div></div></div></div>"`;
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
.kuiCodeEditorWrapper {
2+
position: relative;
3+
}
4+
5+
.kuiCodeEditorKeyboardHint {
6+
position: absolute;
7+
top: 0;
8+
bottom: 0;
9+
right: 0;
10+
left: 0;
11+
background: rgba(255, 255, 255, 0.7);
12+
display: flex;
13+
flex-direction: column;
14+
justify-content: center;
15+
align-items: center;
16+
text-align: center;
17+
opacity: 0;
18+
19+
&:focus {
20+
opacity: 1;
21+
border: 2px solid $globalColorBlue;
22+
z-index: 1000;
23+
}
24+
25+
&.kuiCodeEditorKeyboardHint-isInactive {
26+
display: none;
27+
}
28+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@import "code_editor";
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import React, { Component } from 'react';
2+
import PropTypes from 'prop-types';
3+
import classNames from 'classnames';
4+
import AceEditor from 'react-ace';
5+
6+
import { htmlIdGenerator, keyCodes } from '../../../services';
7+
8+
export class KuiCodeEditor extends Component {
9+
10+
state = {
11+
isHintActive: true
12+
};
13+
14+
idGenerator = htmlIdGenerator();
15+
16+
aceEditorRef = (aceEditor) => {
17+
if (aceEditor) {
18+
this.aceEditor = aceEditor;
19+
aceEditor.editor.textInput.getElement().tabIndex = -1;
20+
aceEditor.editor.textInput.getElement().addEventListener('keydown', this.onKeydownAce);
21+
}
22+
};
23+
24+
onKeydownAce = (ev) => {
25+
if (ev.keyCode === keyCodes.ESCAPE) {
26+
ev.preventDefault();
27+
ev.stopPropagation();
28+
this.stopEditing();
29+
this.editorHint.focus();
30+
}
31+
}
32+
33+
onBlurAce = (...args) => {
34+
this.stopEditing();
35+
if (this.props.onBlur) {
36+
this.props.onBlur(...args);
37+
}
38+
};
39+
40+
onKeyDownHint = (ev) => {
41+
if (ev.keyCode === keyCodes.ENTER) {
42+
ev.preventDefault();
43+
this.startEditing();
44+
}
45+
};
46+
47+
startEditing = () => {
48+
this.setState({ isHintActive: false });
49+
this.aceEditor.editor.textInput.focus();
50+
}
51+
52+
stopEditing() {
53+
this.setState({ isHintActive: true });
54+
}
55+
56+
render() {
57+
const { width, height } = this.props;
58+
const classes = classNames('kuiCodeEditorKeyboardHint', {
59+
'kuiCodeEditorKeyboardHint-isInactive': !this.state.isHintActive
60+
});
61+
return (
62+
<div
63+
className="kuiCodeEditorWrapper"
64+
style={{ width, height }}
65+
>
66+
<div
67+
className={classes}
68+
id={this.idGenerator('codeEditor')}
69+
ref={(hint) => { this.editorHint = hint; }}
70+
tabIndex="0"
71+
role="button"
72+
onClick={this.startEditing}
73+
onKeyDown={this.onKeyDownHint}
74+
>
75+
<p className="kuiText kuiVerticalRhythmSmall">
76+
Press Enter to start editing.
77+
</p>
78+
<p className="kuiText kuiVerticalRhythmSmall">
79+
When you&rsquo;re done, press Escape to stop editing.
80+
</p>
81+
</div>
82+
<AceEditor
83+
{...this.props}
84+
ref={this.aceEditorRef}
85+
onBlur={this.onBlurAce}
86+
/>
87+
</div>
88+
);
89+
}
90+
}
91+
92+
KuiCodeEditor.propTypes = {
93+
height: PropTypes.string,
94+
onBlur: PropTypes.func,
95+
width: PropTypes.string,
96+
};

0 commit comments

Comments
 (0)