mirror of
https://github.com/opensupports/opensupports.git
synced 2025-07-31 01:35:15 +02:00
Ivan - Create Ticket Form - Create Basic TextEditor component using draft.js [skip ci]
This commit is contained in:
parent
8b6266c6c8
commit
95290e97f7
@ -55,6 +55,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"app-module-path": "^1.0.3",
|
"app-module-path": "^1.0.3",
|
||||||
"classnames": "^2.1.3",
|
"classnames": "^2.1.3",
|
||||||
|
"draft-js": "^0.8.1",
|
||||||
"jquery": "^2.1.4",
|
"jquery": "^2.1.4",
|
||||||
"keycode": "^2.1.4",
|
"keycode": "^2.1.4",
|
||||||
"localStorage": "^1.0.3",
|
"localStorage": "^1.0.3",
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReCAPTCHA from 'react-google-recaptcha';
|
||||||
|
|
||||||
|
import i18n from 'lib-app/i18n';
|
||||||
|
import API from 'lib-app/api-call';
|
||||||
|
|
||||||
|
import SubmitButton from 'core-components/submit-button';
|
||||||
|
import Message from 'core-components/message';
|
||||||
|
import Form from 'core-components/form';
|
||||||
|
import Input from 'core-components/input';
|
||||||
|
import TextEditor from 'core-components/text-editor';
|
||||||
|
import DropDown from 'core-components/drop-down';
|
||||||
|
import Widget from 'core-components/widget';
|
||||||
|
|
||||||
|
class CreateTicketForm extends React.Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Form>
|
||||||
|
<DropDown items={[
|
||||||
|
{content: 'Department1'},
|
||||||
|
{content: 'Department2'},
|
||||||
|
{content: 'Department3'}
|
||||||
|
]} />
|
||||||
|
<Input label="Title" name="title" required />
|
||||||
|
<TextEditor label="Content" name="content" required />
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CreateTicketForm;
|
@ -1,11 +1,13 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import CreateTicketForm from 'app/main/dashboard/dashboard-create-ticket/create-ticket-form';
|
||||||
|
|
||||||
class DashboardCreateTicketPage extends React.Component {
|
class DashboardCreateTicketPage extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
DASHBOARD CREATE TICKET
|
<CreateTicketForm />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ class MainLayout extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<div className="main-layout">
|
<div className="main-layout">
|
||||||
<MainHeader />
|
<MainHeader />
|
||||||
<div className="main-layout--content">
|
<div className="main-layout--content row">
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</div>
|
</div>
|
||||||
<MainFooter />
|
<MainFooter />
|
||||||
|
@ -6,6 +6,9 @@ import classNames from 'classnames';
|
|||||||
// CORE LIBS
|
// CORE LIBS
|
||||||
import callback from 'lib-core/callback';
|
import callback from 'lib-core/callback';
|
||||||
|
|
||||||
|
// CORE COMPONENTS
|
||||||
|
import Icon from 'core-components/icon';
|
||||||
|
|
||||||
class Button extends React.Component {
|
class Button extends React.Component {
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
@ -16,6 +19,7 @@ class Button extends React.Component {
|
|||||||
children: React.PropTypes.node,
|
children: React.PropTypes.node,
|
||||||
type: React.PropTypes.oneOf([
|
type: React.PropTypes.oneOf([
|
||||||
'primary',
|
'primary',
|
||||||
|
'primary-icon',
|
||||||
'clean',
|
'clean',
|
||||||
'link'
|
'link'
|
||||||
]),
|
]),
|
||||||
@ -23,7 +27,8 @@ class Button extends React.Component {
|
|||||||
to: React.PropTypes. string.isRequired,
|
to: React.PropTypes. string.isRequired,
|
||||||
params: React.PropTypes.object,
|
params: React.PropTypes.object,
|
||||||
query: React.PropTypes.query
|
query: React.PropTypes.query
|
||||||
})
|
}),
|
||||||
|
iconName: React.PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
@ -33,7 +38,7 @@ class Button extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<button {...this.getProps()}>
|
<button {...this.getProps()}>
|
||||||
{this.props.children}
|
{(this.props.iconName) ? <Icon size="sm" name={this.props.iconName}/> : this.props.children}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
.button {
|
.button {
|
||||||
|
|
||||||
&-primary {
|
&-primary,
|
||||||
|
&-primary-icon {
|
||||||
background-color: $primary-red;
|
background-color: $primary-red;
|
||||||
border: solid transparent;
|
border: solid transparent;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@ -12,6 +13,11 @@
|
|||||||
width: 239px;
|
width: 239px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-primary-icon {
|
||||||
|
width: initial;
|
||||||
|
height: initial;
|
||||||
|
}
|
||||||
|
|
||||||
&-clean {
|
&-clean {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
|
84
client/src/core-components/text-area.js
Normal file
84
client/src/core-components/text-area.js
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
import TextEditor from 'core-components/text-editor';
|
||||||
|
|
||||||
|
class TextArea extends React.Component {
|
||||||
|
|
||||||
|
static contextTypes = {
|
||||||
|
loading: React.PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
value: React.PropTypes.string,
|
||||||
|
validation: React.PropTypes.string,
|
||||||
|
onChange: React.PropTypes.func,
|
||||||
|
required: React.PropTypes.bool,
|
||||||
|
error: React.PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
editorState: EditorState.createEmpty()
|
||||||
|
};
|
||||||
|
this.onChange = (editorState) => this.setState({editorState});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<label className={this.getClass()}>
|
||||||
|
<span className="text-area__label">{this.props.label}</span>
|
||||||
|
<TextEditor />
|
||||||
|
{this.renderError()}
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderError() {
|
||||||
|
let error = null;
|
||||||
|
|
||||||
|
if (this.props.error){
|
||||||
|
error = <span className="text-area__error"> {this.props.error} </span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*getEditorProps() {
|
||||||
|
let props = _.clone(this.props);
|
||||||
|
|
||||||
|
props['aria-required'] = this.props.required;
|
||||||
|
props.className = 'text-area__input';
|
||||||
|
props.ref = 'nativeTextArea';
|
||||||
|
props.disabled = this.context.loading;
|
||||||
|
|
||||||
|
delete props.required;
|
||||||
|
delete props.validation;
|
||||||
|
delete props.error;
|
||||||
|
delete props.password;
|
||||||
|
|
||||||
|
return props;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
getClass() {
|
||||||
|
let classes = {
|
||||||
|
'text-area': true,
|
||||||
|
'text-area_with-error': (this.props.error),
|
||||||
|
|
||||||
|
[this.props.className]: (this.props.className)
|
||||||
|
};
|
||||||
|
|
||||||
|
return classNames(classes);
|
||||||
|
}
|
||||||
|
|
||||||
|
focus() {
|
||||||
|
if (this.refs.nativeTextArea) {
|
||||||
|
this.refs.nativeTextArea.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TextArea;
|
42
client/src/core-components/text-area.scss
Normal file
42
client/src/core-components/text-area.scss
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
@import "../scss/vars";
|
||||||
|
|
||||||
|
.text-area {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
&__editor {
|
||||||
|
border: 1px solid $grey;
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 8px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: $medium-grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: $primary-blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
color: $primary-black;
|
||||||
|
font-size: 15px;
|
||||||
|
display: block;
|
||||||
|
padding: 3px 0;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
&_with-error {
|
||||||
|
.text-area__error {
|
||||||
|
color: $primary-red;
|
||||||
|
font-size: $font-size--sm;
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
.text-area__text {
|
||||||
|
border: 1px solid $primary-red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
94
client/src/core-components/text-editor.js
Normal file
94
client/src/core-components/text-editor.js
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import {Editor, EditorState, RichUtils} from 'draft-js';
|
||||||
|
import Button from 'core-components/button';
|
||||||
|
|
||||||
|
class TextEditor extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
editorState: EditorState.createEmpty(),
|
||||||
|
focused: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className={this.getClass()}>
|
||||||
|
{this.renderEditOptions()}
|
||||||
|
<div className="text-editor__editor" onClick={this.focus.bind(this)}>
|
||||||
|
<Editor {...this.getEditorProps()} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderEditOptions() {
|
||||||
|
const onBoldClick = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
this.onEditorChange(RichUtils.toggleInlineStyle(this.state.editorState, 'BOLD'));
|
||||||
|
};
|
||||||
|
const onItalicsClick = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
this.onEditorChange(RichUtils.toggleInlineStyle(this.state.editorState, 'ITALICS'));
|
||||||
|
};
|
||||||
|
const onUnderlineClick = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
this.onEditorChange(RichUtils.toggleInlineStyle(this.state.editorState, 'UNDERLINE'));
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="text-editor__options">
|
||||||
|
<Button type="primary-icon" iconName="bold" onClick={onBoldClick.bind(this)} onMouseDown={(e) => {e.preventDefault()}} />
|
||||||
|
<Button type="primary-icon" iconName="italic" onClick={onItalicsClick.bind(this)} onMouseDown={(e) => {e.preventDefault()}} />
|
||||||
|
<Button type="primary-icon" iconName="underline" onClick={onUnderlineClick.bind(this)} onMouseDown={(e) => {e.preventDefault()}} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
getClass() {
|
||||||
|
let classes = {
|
||||||
|
'text-editor': true,
|
||||||
|
'text-editor_focused': (this.state.focused),
|
||||||
|
|
||||||
|
[this.props.className]: (this.props.className)
|
||||||
|
};
|
||||||
|
|
||||||
|
return classNames(classes);
|
||||||
|
}
|
||||||
|
|
||||||
|
getEditorProps() {
|
||||||
|
return {
|
||||||
|
editorState: this.state.editorState,
|
||||||
|
ref: 'editor',
|
||||||
|
onChange: this.onEditorChange.bind(this),
|
||||||
|
onFocus: this.onEditorFocus.bind(this),
|
||||||
|
onBlur: this.onBlur.bind(this)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditorChange(editorState) {
|
||||||
|
this.setState({editorState});
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditorFocus() {
|
||||||
|
this.setState({focused: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
onBlur() {
|
||||||
|
this.setState({focused: false});
|
||||||
|
}
|
||||||
|
|
||||||
|
focus() {
|
||||||
|
if (this.refs.editor) {
|
||||||
|
this.refs.editor.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TextEditor;
|
33
client/src/core-components/text-editor.scss
Normal file
33
client/src/core-components/text-editor.scss
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
@import "../scss/vars";
|
||||||
|
|
||||||
|
.text-editor {
|
||||||
|
|
||||||
|
&__editor {
|
||||||
|
border: 1px solid $grey;
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 8px;
|
||||||
|
width: 100%;
|
||||||
|
height: 200px;
|
||||||
|
text-align: left;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: $medium-grey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__options {
|
||||||
|
text-align: left;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
|
||||||
|
.button {
|
||||||
|
margin-right: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&_focused {
|
||||||
|
.text-editor__editor {
|
||||||
|
border-color: $primary-blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user