From 11063697a23e824da608a1059e51536873a721a2 Mon Sep 17 00:00:00 2001 From: Aniko Litvanyi Date: Thu, 18 Oct 2018 16:07:50 +0200 Subject: [PATCH] Release/2.3.0 (#148) * [KFI]chore: Set material-ui 3.1.1 as fix dependency because the build fails with material-ui-picker * [KFI]fix: Remove full form validation check from new view * [KFI]chore: Update depencies * [KFI]build: Add allosyntheticdefaultimports into tsconfig * [KFI]refactor: Change react import * [KFI]docs: Add comments to fieldsetting interfaces * [KFI]feat: Add autocomplete fieldcontrol * [KFI]docs: Add comments to fieldsetting interfaces * [KFI]fix: Fix a browse view styling issue * [KFI]fix: Make the controls storybook compatible and improve browse views * [KFI]chore: Update version number --- package.json | 5 +- src/ReactControlMapper.ts | 6 +- .../AutoComplete/AutoComplete.tsx | 241 ++++++++++++++ .../AutoComplete/AutoCompleteStyles.ts | 0 .../AutoComplete/IAutoCompleteSetting.ts | 14 + .../CheckboxGroup/CheckboxGroup.tsx | 62 +++- src/fieldcontrols/ChoiceFieldSetting.ts | 15 + src/fieldcontrols/ClientFieldSetting.ts | 55 +++- src/fieldcontrols/DatePicker/DatePicker.tsx | 70 +++-- src/fieldcontrols/DateTimeFieldSetting.ts | 12 + .../DateTimePicker/DateTimePicker.tsx | 72 +++-- src/fieldcontrols/DisplayName/DisplayName.tsx | 33 +- .../DisplayName/DisplayNameFieldSetting.ts | 4 + .../DropDownList/DropDownList.tsx | 56 ++-- src/fieldcontrols/LongTextFieldSetting.ts | 10 + src/fieldcontrols/Name/FileName.tsx | 61 ++-- src/fieldcontrols/Name/Name.tsx | 80 +++-- src/fieldcontrols/Name/NameFieldSetting.ts | 8 + src/fieldcontrols/Number/Number.tsx | 104 +++--- .../Number/NumberFieldSetting.ts | 39 ++- src/fieldcontrols/Password/Password.tsx | 117 ++++--- .../RadioButtonGroup/RadioButtonGroup.tsx | 73 ++++- src/fieldcontrols/ReferenceFieldSetting.ts | 21 ++ .../RichTextEditor/RichTextEditor.tsx | 31 +- src/fieldcontrols/ShortText/ShortText.tsx | 43 +-- .../ShortText/ShortTextFieldSetting.ts | 9 + .../TagsInput/ITagsInputSetting.ts | 3 + src/fieldcontrols/TagsInput/TagsInput.tsx | 296 ++++++++++-------- .../TagsInput/TagsInputOptions.tsx | 4 +- src/fieldcontrols/Textarea/Textarea.tsx | 49 +-- src/fieldcontrols/TimePicker/TimePicker.tsx | 53 ++-- src/fieldcontrols/index.ts | 1 + src/viewcontrols/BrowseView.tsx | 11 +- src/viewcontrols/BrowseViewStyles.ts | 2 +- src/viewcontrols/EditView.tsx | 8 +- src/viewcontrols/NewView.tsx | 18 +- tsconfig.json | 3 +- 37 files changed, 1160 insertions(+), 529 deletions(-) create mode 100644 src/fieldcontrols/AutoComplete/AutoComplete.tsx create mode 100644 src/fieldcontrols/AutoComplete/AutoCompleteStyles.ts create mode 100644 src/fieldcontrols/AutoComplete/IAutoCompleteSetting.ts diff --git a/package.json b/package.json index 4e15c85..f10922b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sensenet/controls-react", - "version": "2.2.6", + "version": "2.3.0", "description": "React controls for sensenet", "main": "dist/index.js", "scripts": { @@ -57,8 +57,9 @@ "@material-ui/core": "3.1.1", "@sensenet/client-core": "^1.3.0", "@sensenet/control-mapper": "^1.0.1", - "@sensenet/icons-react": "^1.0.2", + "@sensenet/icons-react": "^1.2.3", "@sensenet/redux": "^5.1.1", + "lodash.debounce": "^4.0.8", "material-ui-pickers": "^1.0.0-rc.17", "moment": "^2.22.2", "radium": "^0.25.0", diff --git a/src/ReactControlMapper.ts b/src/ReactControlMapper.ts index e64f3ce..7211da1 100644 --- a/src/ReactControlMapper.ts +++ b/src/ReactControlMapper.ts @@ -7,7 +7,7 @@ import { Repository } from '@sensenet/client-core' import { ControlMapper } from '@sensenet/control-mapper' import { ChoiceFieldSetting, DateTimeFieldSetting, FieldSetting, IntegerFieldSetting, LongTextFieldSetting, NumberFieldSetting, PasswordFieldSetting, ReferenceFieldSetting, ShortTextFieldSetting } from '@sensenet/default-content-types' -import * as React from 'react' +import { Component } from 'react' import * as FieldControls from './fieldcontrols' import { ReactChoiceFieldSetting } from './fieldcontrols/ChoiceFieldSetting' import { ReactClientFieldSettingProps } from './fieldcontrols/ClientFieldSetting' @@ -31,7 +31,7 @@ const clientConfigFactory = (fieldSettings: FieldSetting) => { defaultSetting.required = fieldSettings.Compulsory || false, defaultSetting['data-placeHolderText'] = fieldSettings.DisplayName || '' defaultSetting['data-labelText'] = fieldSettings.DisplayName || '', - defaultSetting['data-typeName'] = fieldSettings.Type || '' + defaultSetting['data-typeName'] = fieldSettings.Type || '' return defaultSetting } @@ -40,7 +40,7 @@ const clientConfigFactory = (fieldSettings: FieldSetting) => { /** * A static Control Mapper instance, used to create the mapping between sensenet ECM ContentTypes and FieldSettings and React components. */ -export const reactControlMapper = (repository: Repository) => new ControlMapper(repository, React.Component, clientConfigFactory, ViewControls.EditView, FieldControls.ShortText) +export const reactControlMapper = (repository: Repository) => new ControlMapper(repository, Component, clientConfigFactory, ViewControls.EditView, FieldControls.ShortText) .setupFieldSettingDefault(NumberFieldSetting, (setting) => { return FieldControls.Number }) diff --git a/src/fieldcontrols/AutoComplete/AutoComplete.tsx b/src/fieldcontrols/AutoComplete/AutoComplete.tsx new file mode 100644 index 0000000..1ee7788 --- /dev/null +++ b/src/fieldcontrols/AutoComplete/AutoComplete.tsx @@ -0,0 +1,241 @@ +import { CircularProgress, InputAdornment, Menu, MenuItem, TextField } from '@material-ui/core' +import FormControl from '@material-ui/core/FormControl' +import FormControlLabel from '@material-ui/core/FormControlLabel' +import FormGroup from '@material-ui/core/FormGroup' +import FormLabel from '@material-ui/core/FormLabel' +import { GenericContent } from '@sensenet/default-content-types' +import { Query, QueryExpression, QueryOperators } from '@sensenet/query' +// tslint:disable-next-line:no-var-requires +import debounce from 'lodash.debounce' +import React, { Component } from 'react' +import { ReactClientFieldSetting, ReactClientFieldSettingProps } from '../ClientFieldSetting' +import { ReactReferenceFieldSetting } from '../ReferenceFieldSetting' + +/** + * Interface for AutoComplete properties + */ +export interface AutoCompleteProps extends ReactClientFieldSettingProps, ReactClientFieldSetting, ReactReferenceFieldSetting { } + +/** + * State object for the AutoComplete component + */ +export interface AutoCompleteState { + inputValue: string, + isLoading: boolean + isOpened: boolean + term?: string + items: T[] + selected: number | number[] | null + anchorEl: HTMLElement + getMenuItem: (item: T, select: (item: T) => void) => JSX.Element +} +/** + * Field control that represents a AutoComplete field. Available values will be populated from the FieldSettings. + */ +export class AutoComplete extends Component> { + /** + * returns a content by its id + */ + public getContentById = (id) => { + return this.props.dataSource ? this.props.dataSource.find((item) => item.Id === id) : null + } + /** + * state initialization + */ + public state = { + inputValue: '', + isOpened: false, + isLoading: false, + items: this.props.dataSource || [], + selected: this.props['data-fieldValue'] || [], + anchorEl: null as any, + getMenuItem: (item: T, select: (item: T) => void) => select(item)}>{item.DisplayName}, + } + private willUnmount: boolean = false + /** + * component will unmount + */ + public componentWillUnmount() { + this.willUnmount = true + } + constructor(props) { + super(props) + const handleInputChange = this.handleInputChange.bind(this) + this.handleInputChange = debounce(handleInputChange, 350) + this.handleSelect = this.handleSelect.bind(this) + this.handleClickAway = this.handleClickAway.bind(this) + } + private async handleInputChange(e) { + // tslint:disable + const term = `*${e.target.value}*` + const query = new Query((q) => q + .query((q2) => + q2.equals('Name', term) + .or + .equals('DisplayName', term))) + + if (this.props['data-allowedTypes']) { + new QueryOperators(query).and.query(q2 => { + (this.props['data-allowedTypes'] as string[]).map((allowedType, index, array) => { + new QueryExpression(q2['queryRef']).term(`TypeIs:${allowedType}`) + if (index < array.length - 1) { + new QueryOperators(q2['queryRef']).or + } + }) + return q2 + }) + } + + if (this.props["data-selectionRoot"] && this.props["data-selectionRoot"].length) { + new QueryOperators(query).and.query((q2) => { + (this.props["data-selectionRoot"] as string[]).forEach((root, index, array) => { + new QueryExpression(q2['queryRef']).inTree(root) + if (index < array.length - 1) { + new QueryOperators(q2['queryRef']).or + } + }) + return q2 + }) + // tslint:enable + } + + this.setState({ + isLoading: true, + // inputValue: [e.target.value], + }) + if (this.props.dataSource && this.props.dataSource.length > 0) { + this.setState({ + items: this.props.dataSource, + isOpened: this.props.dataSource.length > 0 ? true : false, + }) + } else { + try { + const values = await this.props.repository.loadCollection({ + path: '/Root', + oDataOptions: { + query, + select: 'all', + }, + }) + if (this.willUnmount) { + return + } + this.setState({ + items: values, + isOpened: values.length > 0 ? true : false, + }) + + // tslint:disable-next-line:variable-name + } catch (_e) { + /** */ + } finally { + !this.willUnmount && this.setState({ isLoading: false }) + } + } + } + + private handleClickAway() { + this.setState({ isOpened: false }) + } + + private handleSelect(item: T) { + this.setState({ + inputValue: '', + selected: [item.Id], + isOpened: false, + isLoading: false, + }) + const { name, onChange } = this.props + onChange(name, item.Id) + } + /** + * render + * @return {ReactElement} markup + */ + public render() { + const displayName = this.props['data-defaultDisplayName'] || 'DisplayName' + switch (this.props['data-actionName']) { + case 'edit': + case 'new': + return (
ref && this.state.anchorEl !== ref && this.setState({ anchorEl: ref })}> + + 0 ? this.getContentById(this.state.selected[0])[displayName] : ''} + type="text" + onChange={async (e) => { + // tslint:disable-next-line:no-string-literal + this.setState({ inputValue: e.target['value'] }) + e.persist() + await this.handleInputChange(e) + }} + autoFocus + label={this.props['data-errorText'] && this.props['data-errorText'].length > 0 ? this.props['data-errorText'] : this.props['data-labelText']} + placeholder={this.props['data-placeHolderText']} + InputProps={{ + endAdornment: + this.state.isLoading ? + + : null, + }} + name={this.props.name} + id={this.props.name} + className={this.props.className} + style={this.props.style} + required={this.props.required} + disabled={this.props.readOnly} + error={this.props['data-errorText'] && this.props['data-errorText'].length > 0 ? true : false} + fullWidth + helperText={this.props['data-hintText']} + > + + + {this.state.items.length > 0 ? this.state.items.map((i) => this.state.getMenuItem(i, this.handleSelect)) : No hits} + + +
) + case 'browse': + return ( + this.props['data-fieldValue'].length > 0 ? + + + {this.props['data-labelText']} + + + {this.props['data-fieldValue'].map((value) => + + (item.Id === value))[displayName]} control={} key={value} /> + )} + + : null + ) + default: + return ( + this.props['data-fieldValue'].length > 0 ? + + + {this.props['data-labelText']} + + + {this.props['data-fieldValue'].map((value) => + + (item.Id === value))[displayName]} control={} key={value} /> + )} + + : null + ) + } + } +} diff --git a/src/fieldcontrols/AutoComplete/AutoCompleteStyles.ts b/src/fieldcontrols/AutoComplete/AutoCompleteStyles.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/fieldcontrols/AutoComplete/IAutoCompleteSetting.ts b/src/fieldcontrols/AutoComplete/IAutoCompleteSetting.ts new file mode 100644 index 0000000..8a360af --- /dev/null +++ b/src/fieldcontrols/AutoComplete/IAutoCompleteSetting.ts @@ -0,0 +1,14 @@ +/** + * @module FieldControls + * + */ /** */ +import { ReactClientFieldSetting } from '../ClientFieldSetting' +/** + * Interface for TagsInputFieldSetting properties + */ +export interface ReactAutoCompleteFieldSetting extends ReactClientFieldSetting { + /** + * Datasource of a reference field with the optional items that can be chosen + */ + dataSource?: any[], +} diff --git a/src/fieldcontrols/CheckboxGroup/CheckboxGroup.tsx b/src/fieldcontrols/CheckboxGroup/CheckboxGroup.tsx index a43ef51..e9a183b 100644 --- a/src/fieldcontrols/CheckboxGroup/CheckboxGroup.tsx +++ b/src/fieldcontrols/CheckboxGroup/CheckboxGroup.tsx @@ -2,7 +2,7 @@ * @module FieldControls * */ /** */ -import * as React from 'react' +import React, { Component } from 'react' import { ReactChoiceFieldSetting } from '../ChoiceFieldSetting' import { ReactClientFieldSetting, ReactClientFieldSettingProps } from '../ClientFieldSetting' @@ -20,6 +20,7 @@ import FormControlLabel from '@material-ui/core/FormControlLabel' import FormGroup from '@material-ui/core/FormGroup' import FormHelperText from '@material-ui/core/FormHelperText' import FormLabel from '@material-ui/core/FormLabel' +import TextField from '@material-ui/core/TextField' /** * Interface for CheckboxGroup properties @@ -29,7 +30,7 @@ export interface CheckboxGroupProps extends ReactClientFieldSettingProps, ReactC /** * Field control that represents a Choice field. Available values will be populated from the FieldSettings. */ -export class CheckboxGroup extends React.Component { +export class CheckboxGroup extends Component { /** * constructor * @param {object} props @@ -51,13 +52,13 @@ export class CheckboxGroup extends React.Component -1) { - checked[index].splice(index, 1) + checked.splice(index, 1) } else { checked.push(newValue) } } else { if (index > -1) { - checked[index].splice(index, 1) + checked.splice(index, 1) } else { checked[0] = newValue } @@ -72,18 +73,25 @@ export class CheckboxGroup extends React.Component -1 + let checked = false + // tslint:disable-next-line:prefer-for-of + for (let i = 0; i < this.state.value.length; i++) { + if (this.state.value[i].toString() === item.toString()) { + checked = true + break + } + } + return checked } /** * render * @return {ReactElement} markup */ public render() { - const { onChange } = this.props switch (this.props['data-actionName']) { case 'edit': return ( - + 0}> {this.props['data-labelText']} {this.props.options.map((option) => { @@ -93,19 +101,22 @@ export class CheckboxGroup extends React.Component } label={option.Text} /> })} + {this.props['data-allowExtraValue'] ? : null} {this.props['data-hintText']} + {this.props['data-errorText']} ) case 'new': return ( - + 0}> {this.props['data-labelText']} {this.props.options.map((option) => { @@ -114,24 +125,49 @@ export class CheckboxGroup extends React.Component } label={option.Text} /> })} + {this.props['data-allowExtraValue'] ? : null} {this.props['data-hintText']} + {this.props['data-errorText']} ) case 'browse': return ( - under consctruction + this.props['data-fieldValue'].length > 0 ? + + + {this.props['data-labelText']} + + + {this.props['data-fieldValue'].map((value) => + + (item.Value === value)).Text} control={} key={value} /> + )} + + : null ) default: return ( - under consctruction + this.props['data-fieldValue'].length > 0 ? + + + {this.props['data-labelText']} + + + {this.props['data-fieldValue'].map((value) => + + (item.Value === value)).Text} control={} key={value} /> + )} + + : null ) } } diff --git a/src/fieldcontrols/ChoiceFieldSetting.ts b/src/fieldcontrols/ChoiceFieldSetting.ts index 1d66df6..e19e8e5 100644 --- a/src/fieldcontrols/ChoiceFieldSetting.ts +++ b/src/fieldcontrols/ChoiceFieldSetting.ts @@ -8,8 +8,23 @@ import { ReactClientFieldSetting } from './ClientFieldSetting' * Interface for ChoiceFieldSetting properties */ export interface ReactChoiceFieldSetting extends ReactClientFieldSetting { + /** + * Allows multiple selection + * @default false + */ 'data-allowMultiple'?: boolean, + /** + * Allows to add an extra value to the field + * @default false + */ 'data-allowExtraValue'?: boolean, + /** + * Specifies the type of the field control which will handle the current field ('DropDown','RadioButtons','CheckBoxes'). + * @default dropDown + */ 'data-displayChoices'?: 'dropDown' | 'radioButtons' | 'checkBoxes', + /** + * List of the optional options + */ options: any[] } diff --git a/src/fieldcontrols/ClientFieldSetting.ts b/src/fieldcontrols/ClientFieldSetting.ts index f91c97d..84da755 100644 --- a/src/fieldcontrols/ClientFieldSetting.ts +++ b/src/fieldcontrols/ClientFieldSetting.ts @@ -6,13 +6,39 @@ * Interface for ReactClientFieldSetting properties */ export interface ReactClientFieldSettingProps { + /** + * Unique name of the field control + */ name?: string + /** + * Unique key of the field control + */ key?: string + /** + * React style object + */ style?: object - value?: number + /** + * Value of the field control + */ + value?: any + /** + * Defining whether the field's data can be edited + */ readOnly?: boolean + /** + * Defining whether the field has to contain any data + * @default false + */ required?: boolean + /** + * Additional class name + * @default false + */ className?: string, + /** + * Called when the icon is clicked + */ onChange } @@ -20,16 +46,33 @@ export interface ReactClientFieldSettingProps { * Interface for ClientFieldSetting properties */ export interface ReactClientFieldSetting extends ReactClientFieldSettingProps { + /** + * Display mode of the field control + * @default browse + */ 'data-actionName'?: 'new' | 'edit' | 'browse' + /** + * Text of the hint that could be displayed after the field control + */ 'data-hintText'?: string - 'data-hintStyle'?: object + /** + * Text of the placeholder + */ 'data-placeHolderText'?: string - 'data-placeHolderStyle'?: object + /** + * Default value of the empty field control + */ 'data-defaultValue'? - 'data-outPutMethod'?: 'default' | 'raw' | 'text' | 'html', - 'data-labelStyle'?: object, + /** + * Text of the label + */ 'data-labelText'?: string, - 'data-errorStyle'?: object, + /** + * Text of the error message + */ 'data-errorText'?: string, + /** + * Name of the fildcontrol type + */ 'data-typeName'?: string } diff --git a/src/fieldcontrols/DatePicker/DatePicker.tsx b/src/fieldcontrols/DatePicker/DatePicker.tsx index d402b7f..20ab7e2 100644 --- a/src/fieldcontrols/DatePicker/DatePicker.tsx +++ b/src/fieldcontrols/DatePicker/DatePicker.tsx @@ -2,12 +2,13 @@ * @module FieldControls * */ /** */ +import FormHelperText from '@material-ui/core/FormHelperText' +import Typography from '@material-ui/core/Typography' import { DatePicker as MUIDatePicker } from 'material-ui-pickers' import MomentUtils from 'material-ui-pickers/utils/moment-utils' import MuiPickersUtilsProvider from 'material-ui-pickers/utils/MuiPickersUtilsProvider' -import * as moment from 'moment' -import * as React from 'react' -import { Fragment } from 'react' +import moment from 'moment' +import React, { Component, Fragment } from 'react' import { ReactClientFieldSetting, ReactClientFieldSettingProps } from '../ClientFieldSetting' import { ReactDateTimeFieldSetting } from '../DateTimeFieldSetting' @@ -19,7 +20,7 @@ export interface DatePickerProps extends ReactClientFieldSettingProps, ReactClie /** * Field control that represents a Date field. Available values will be populated from the FieldSettings. */ -export class DatePicker extends React.Component { +export class DatePicker extends Component { /** * constructor * @param {object} props @@ -30,10 +31,6 @@ export class DatePicker extends React.Component { - this.setState({ - dateValue: date, - value: moment.utc(date), - }) this.props.onChange(this.props.name, moment.utc(date)) } /** @@ -67,7 +60,6 @@ export class DatePicker extends React.Component + {this.props['data-hintText']} + {this.props['data-errorText']} ) case 'new': @@ -92,7 +87,7 @@ export class DatePicker extends React.Component + {this.props['data-hintText']} + {this.props['data-errorText']} ) case 'browse': + let displayedValue + switch (this.props['data-displayMode']) { + case 'relative': + displayedValue = moment(this.props.value).fromNow() + break + case 'calendar': + displayedValue = moment(this.props.value).format('dddd, MMMM Do YYYY') + break + case 'raw': + displayedValue = this.props.value + break + default: + displayedValue = this.props.value + } return ( -
-
: null ) default: return ( -
-
: null ) } } diff --git a/src/fieldcontrols/DateTimeFieldSetting.ts b/src/fieldcontrols/DateTimeFieldSetting.ts index 7f1e543..6bca805 100644 --- a/src/fieldcontrols/DateTimeFieldSetting.ts +++ b/src/fieldcontrols/DateTimeFieldSetting.ts @@ -8,6 +8,18 @@ import { ReactClientFieldSetting } from './ClientFieldSetting' * Interface for DateTimeFieldSetting properties */ export interface ReactDateTimeFieldSetting extends ReactClientFieldSetting { + /** + * Defines the presentation mode of the stored value: None, Date and DateAndTime. This only controls the behavior of the DatePicker Field Control. + * @default none + */ 'data-dateTimeMode'?: 'none' | 'date' | 'dateAndTime', + /** + * Defines the precision of the indexed value: Millisecond, Second, Minute, Hour, Day (Default is Minute). This does not affect the stored value, only the value stored in the index, making it possible to use different precision levels depending on the nature of the application. Chosing a finer or coarser precision than the optimal may cause slower query running and larger index files than what would be reasonable. + * @default minute + */ 'data-precision'?: 'millisecond' | 'second' | 'minute' | 'hour' | 'day' + /** + * Defines how the date value should be displayed (e.g. relative: '11 hours ago', calendar: 'Tuesday, March 26th 2013, 3:55:00 am', raw: '2013-03-26T03:55:00') + */ + 'data-displayMode'?: 'relative' | 'calendar' | 'raw' } diff --git a/src/fieldcontrols/DateTimePicker/DateTimePicker.tsx b/src/fieldcontrols/DateTimePicker/DateTimePicker.tsx index 0317608..ccb28f8 100644 --- a/src/fieldcontrols/DateTimePicker/DateTimePicker.tsx +++ b/src/fieldcontrols/DateTimePicker/DateTimePicker.tsx @@ -2,12 +2,13 @@ * @module FieldControls * */ /** */ +import FormHelperText from '@material-ui/core/FormHelperText' +import Typography from '@material-ui/core/Typography' import { DateTimePicker as MUIDateTimePicker } from 'material-ui-pickers' import MomentUtils from 'material-ui-pickers/utils/moment-utils' import MuiPickersUtilsProvider from 'material-ui-pickers/utils/MuiPickersUtilsProvider' -import * as moment from 'moment' -import * as React from 'react' -import { Fragment } from 'react' +import moment from 'moment' +import React, { Component, Fragment } from 'react' import { ReactClientFieldSetting, ReactClientFieldSettingProps } from '../ClientFieldSetting' import { ReactDateTimeFieldSetting } from '../DateTimeFieldSetting' @@ -19,7 +20,7 @@ export interface DateTimePickerProps extends ReactClientFieldSettingProps, React /** * Field control that represents a Date field. Available values will be populated from the FieldSettings. */ -export class DateTimePicker extends React.Component { +export class DateTimePicker extends Component { /** * constructor * @param {object} props @@ -30,10 +31,6 @@ export class DateTimePicker extends React.Component { - this.setState({ - dateValue: date, - value: moment.utc(date), - }) this.props.onChange(this.props.name, moment.utc(date)) } /** @@ -67,7 +60,6 @@ export class DateTimePicker extends React.Component + {this.props['data-hintText']} + {this.props['data-errorText']} ) case 'new': @@ -92,7 +87,7 @@ export class DateTimePicker extends React.Component + {this.props['data-hintText']} + {this.props['data-errorText']} ) - case 'browse': + case 'browse': + let displayedValue + switch (this.props['data-displayMode']) { + case 'relative': + displayedValue = moment(this.props.value).fromNow() + break + case 'calendar': + displayedValue = moment(this.props.value).format('dddd, MMMM Do YYYY, h:mm:ss a') + break + case 'raw': + displayedValue = this.props.value + break + default: + displayedValue = this.props.value + } return ( -
-
: null ) default: return ( -
-
: null ) } } diff --git a/src/fieldcontrols/DisplayName/DisplayName.tsx b/src/fieldcontrols/DisplayName/DisplayName.tsx index 344c947..c49f1c7 100644 --- a/src/fieldcontrols/DisplayName/DisplayName.tsx +++ b/src/fieldcontrols/DisplayName/DisplayName.tsx @@ -2,11 +2,12 @@ * @module FieldControls * */ /** */ -import * as React from 'react' +import React, { Component } from 'react' import { ReactClientFieldSetting, ReactClientFieldSettingProps } from '../ClientFieldSetting' import { ReactDisplayNameFieldSetting } from './DisplayNameFieldSetting' import TextField from '@material-ui/core/TextField' +import Typography from '@material-ui/core/Typography' import Radium from 'radium' /** @@ -18,7 +19,7 @@ export interface DisplayNameProps extends ReactClientFieldSettingProps, ReactCli * Field control that represents a ShortText field. Available values will be populated from the FieldSettings. */ @Radium -export class DisplayName extends React.Component { +export class DisplayName extends Component { /** * constructor * @param {object} props @@ -103,25 +104,25 @@ export class DisplayName extends React.Component { ) case 'browse': return ( -
-
: null ) default: return ( -
-
: null ) } diff --git a/src/fieldcontrols/DisplayName/DisplayNameFieldSetting.ts b/src/fieldcontrols/DisplayName/DisplayNameFieldSetting.ts index bcb8e9b..1cc8639 100644 --- a/src/fieldcontrols/DisplayName/DisplayNameFieldSetting.ts +++ b/src/fieldcontrols/DisplayName/DisplayNameFieldSetting.ts @@ -8,5 +8,9 @@ import { ReactShortTextFieldSetting } from '../ShortText/ShortTextFieldSetting' * Interface for DisplayNameFieldSetting properties */ export interface ReactDisplayNameFieldSetting extends ReactShortTextFieldSetting { + /** + * Sets whether the Name should be updated according to input text regardless of using the control in a rename scenario or not + * @default false + */ alwaysUpdateName?: boolean } diff --git a/src/fieldcontrols/DropDownList/DropDownList.tsx b/src/fieldcontrols/DropDownList/DropDownList.tsx index 0f24ea9..811f72e 100644 --- a/src/fieldcontrols/DropDownList/DropDownList.tsx +++ b/src/fieldcontrols/DropDownList/DropDownList.tsx @@ -2,11 +2,15 @@ * @module FieldControls * *//** */ -import * as React from 'react' +import React, { Component } from 'react' import { ReactChoiceFieldSetting } from '../ChoiceFieldSetting' import { ReactClientFieldSetting, ReactClientFieldSettingProps } from '../ClientFieldSetting' import FormControl from '@material-ui/core/FormControl' +import FormControlLabel from '@material-ui/core/FormControlLabel' +import FormGroup from '@material-ui/core/FormGroup' +import FormHelperText from '@material-ui/core/FormHelperText' +import FormLabel from '@material-ui/core/FormLabel' import InputLabel from '@material-ui/core/InputLabel' import MenuItem from '@material-ui/core/MenuItem' import Select from '@material-ui/core/Select' @@ -19,7 +23,7 @@ export interface DropDownListProps extends ReactClientFieldSettingProps, ReactCl /** * Field control that represents a Choice field. Available values will be populated from the FieldSettings. */ -export class DropDownList extends React.Component { +export class DropDownList extends Component { /** * constructor * @param {object} props @@ -30,7 +34,7 @@ export class DropDownList extends React.Component * @type {object} */ this.state = { - value: this.props['data-defaultValue'] || '', + value: this.props['data-fieldValue'] || this.props['data-defaultValue'] || [], } } /** @@ -95,7 +99,7 @@ export class DropDownList extends React.Component name: this.props.name, id: this.props.name, }} - value={this.props['data-fieldValue'][0]} + value={this.state.value[0]} multiple={this.props['data-allowMultiple']} autoWidth={true} fullWidth={true} @@ -106,6 +110,8 @@ export class DropDownList extends React.Component ) })} + {this.props['data-hintText']} + {this.props['data-errorText']}
) case 'new': @@ -133,29 +139,39 @@ export class DropDownList extends React.Component ) })} + {this.props['data-hintText']} + {this.props['data-errorText']}
) case 'browse': return ( -
- -
- {this.getTextByValue(this.props['data-fieldValue'])} -
-
+ this.props['data-fieldValue'].length > 0 ? + + + {this.props['data-labelText']} + + + {this.props['data-fieldValue'].map((value) => + + (item.Value === value)).Text} control={} key={value} /> + )} + + : null ) default: return ( -
- -
- {this.getTextByValue(this.props['data-fieldValue'])} -
-
+ this.props['data-fieldValue'].length > 0 ? + + + {this.props['data-labelText']} + + + {this.props['data-fieldValue'].map((value) => + + (item.Value === value)).Text} control={} key={value} /> + )} + + : null ) } } diff --git a/src/fieldcontrols/LongTextFieldSetting.ts b/src/fieldcontrols/LongTextFieldSetting.ts index 02495a9..b4180a9 100644 --- a/src/fieldcontrols/LongTextFieldSetting.ts +++ b/src/fieldcontrols/LongTextFieldSetting.ts @@ -8,7 +8,17 @@ import { ReactClientFieldSetting } from './ClientFieldSetting' * Interface for LongTextFieldSetting properties */ export interface ReactLongTextFieldSetting extends ReactClientFieldSetting { + /** + * Defines the maximum length of the inserted text: 0 to infinite. + */ 'data-maxLength'?: number, + /** + * Defines the minimum length of the inserted text: 0 to infinite. + */ 'data-minLength'?: number, + /** + * defines the rendering mode of the input box. + * @default LongText + */ 'data-textType': 'LongText' | 'RichText' | 'AdvancedRichText' } diff --git a/src/fieldcontrols/Name/FileName.tsx b/src/fieldcontrols/Name/FileName.tsx index 6d49068..4ab6acc 100644 --- a/src/fieldcontrols/Name/FileName.tsx +++ b/src/fieldcontrols/Name/FileName.tsx @@ -2,13 +2,13 @@ * @module FieldControls * */ /** */ -import * as React from 'react' +import React, { Component } from 'react' import { ReactClientFieldSetting, ReactClientFieldSettingProps } from '../ClientFieldSetting' import { ReactFileNameFieldSetting } from './FileNameFieldSetting' -import FormControl from '@material-ui/core/FormControl' import InputAdornment from '@material-ui/core/InputAdornment' import TextField from '@material-ui/core/TextField' +import Typography from '@material-ui/core/Typography' import Radium from 'radium' /** @@ -20,7 +20,7 @@ export interface FileNameProps extends ReactClientFieldSettingProps, ReactClient * Field control that represents a ShortText field. Available values will be populated from the FieldSettings. */ @Radium -export class FileName extends React.Component { +export class FileName extends Component { /** * constructor * @param {object} props @@ -36,7 +36,8 @@ export class FileName extends React.Component - 1 ? this.getExtensionFromValue(this.props.value) : null, } this.handleChange = this.handleChange.bind(this) @@ -81,11 +82,6 @@ export class FileName extends React.Component 0 ? true : false} - required={this.props.required} - disabled={this.props.readOnly} - fullWidth> this.handleChange(e)} InputProps={{ // tslint:disable-next-line:no-string-literal - endAdornment: {`.${this.getExtensionFromValue(this.props['content'].Name)}`}, + endAdornment: {`.${this.state.extension}`}, }} autoFocus + error={this.props['data-errorText'] && this.props['data-errorText'].length > 0 ? true : false} + required={this.props.required} + disabled={this.props.readOnly} + fullWidth + helperText={this.props['data-hintText']} /> - ) case 'new': return ( - 0 ? true : false} - required={this.props.required} - disabled={this.props.readOnly} - fullWidth> {`.${this.props['data-extension']}`}, }} autoFocus + error={this.props['data-errorText'] && this.props['data-errorText'].length > 0 ? true : false} + required={this.props.required} + disabled={this.props.readOnly} + fullWidth + helperText={this.props['data-hintText']} /> - ) case 'browse': return ( -
-
: null ) default: return ( -
-
: null ) } diff --git a/src/fieldcontrols/Name/Name.tsx b/src/fieldcontrols/Name/Name.tsx index 5de3d8f..26a9bb4 100644 --- a/src/fieldcontrols/Name/Name.tsx +++ b/src/fieldcontrols/Name/Name.tsx @@ -2,14 +2,13 @@ * @module FieldControls * */ /** */ -import * as React from 'react' +import React, { Component } from 'react' import { ReactClientFieldSetting, ReactClientFieldSettingProps } from '../ClientFieldSetting' import { ReactShortTextFieldSetting } from '../ShortText/ShortTextFieldSetting' import { ReactNameFieldSetting } from './NameFieldSetting' -import FormControl from '@material-ui/core/FormControl' -import FormHelperText from '@material-ui/core/FormHelperText' import TextField from '@material-ui/core/TextField' +import Typography from '@material-ui/core/Typography' import Radium from 'radium' /** @@ -21,7 +20,7 @@ export interface NameProps extends ReactClientFieldSettingProps, ReactClientFiel * Field control that represents a ShortText field. Available values will be populated from the FieldSettings. */ @Radium -export class Name extends React.Component { +export class Name extends Component { /** * constructor * @param {object} props @@ -73,65 +72,60 @@ export class Name extends React.Component switch (this.props['data-actionName']) { case 'edit': return ( - 0 ? true : false} - required={this.props.required} - disabled={this.props.readOnly} - fullWidth> this.handleChange(e)} + name={this.props.name} + id={this.props.name} + label={this.props['data-errorText'] && this.props['data-errorText'].length > 0 ? this.props['data-errorText'] : this.props['data-labelText']} + className={this.props.className} + placeholder={this.props['data-placeHolderText']} + style={this.props.style} + value={this.props.value} + required={this.props.required} + disabled={this.props.readOnly} + error={this.props['data-errorText'] && this.props['data-errorText'].length > 0 ? true : false} + fullWidth + onChange={(e) => this.handleChange(e)} + helperText={this.props['data-hintText']} /> -
Error
-
) case 'new': return ( - 0 ? true : false} - required={this.props.required} - disabled={this.props.readOnly} - fullWidth> 0 ? this.props['data-errorText'] : this.props['data-labelText']} className={this.props.className} placeholder={this.props['data-placeHolderText']} style={this.props.style} - defaultValue={this.state.value} - onChange={(e) => this.handleChange(e)} + defaultValue={this.props['data-defaultValue']} + required={this.props.required} + disabled={this.props.readOnly} + error={this.props['data-errorText'] && this.props['data-errorText'].length > 0 ? true : false} + fullWidth + helperText={this.props['data-hintText']} /> -
Error
-
) case 'browse': return ( -
-
: null ) default: return ( -
-
: null ) } diff --git a/src/fieldcontrols/Name/NameFieldSetting.ts b/src/fieldcontrols/Name/NameFieldSetting.ts index 1a976c0..3483c43 100644 --- a/src/fieldcontrols/Name/NameFieldSetting.ts +++ b/src/fieldcontrols/Name/NameFieldSetting.ts @@ -8,6 +8,14 @@ import { ReactShortTextFieldSetting } from '../ShortText/ShortTextFieldSetting' * Interface for NameFieldSetting properties */ export interface ReactNameFieldSetting extends ReactShortTextFieldSetting { + /** + * When set to "true", the Field Control will not set the Content's DisplayName even if the DisplayName Field Control is not present in the Content View + * @default false + */ neverOverrideDisplayName?: boolean + /** + * When set to "true", the control is rendered as editable (and the user does not have to explicitely click on the edit button to change the Field Control's value + * @default false + */ alwaysEditable?: boolean } diff --git a/src/fieldcontrols/Number/Number.tsx b/src/fieldcontrols/Number/Number.tsx index 40a6ad5..1d13c45 100644 --- a/src/fieldcontrols/Number/Number.tsx +++ b/src/fieldcontrols/Number/Number.tsx @@ -4,8 +4,9 @@ */ /** */ import InputAdornment from '@material-ui/core/InputAdornment' import TextField from '@material-ui/core/TextField' +import Typography from '@material-ui/core/Typography' import Radium from 'radium' -import * as React from 'react' +import React, { Component } from 'react' import NumberFormat from 'react-number-format' import { ReactClientFieldSetting, ReactClientFieldSettingProps } from '../ClientFieldSetting' import { ReactNumberFieldSetting } from './NumberFieldSetting' @@ -19,7 +20,7 @@ export interface NumberProps extends ReactClientFieldSettingProps, ReactClientFi * Field control that represents a Number field. Available values will be populated from the FieldSettings. */ @Radium -export class Number extends React.Component { +export class Number extends Component { constructor(props) { super(props) @@ -35,11 +36,10 @@ export class Number extends React.Component ) } + /** + * Returns steps value by decimal and step settings + */ + public defineStepValue = () => { + return this.props['data-decimal'] && this.props['data-step'] === undefined ? 0.1 : this.props['data-step'] ? this.props['data-step'] : 1 + } + /** + * Returns inputadornment by currency and percentage settings + */ + public defineCurrency = () => { + let currency = null + if (this.props['data-isCurrency']) { + currency = this.props['data-currency'] ? {this.props['data-currency']} : $ + } else { + currency = this.props['data-isPercentage'] ? % : null + } + return currency + } /** * render * @return {ReactElement} markup */ public render() { - const { value } = this.state + console.log(this.props['data-fieldValue']) switch (this.props['data-actionName']) { case 'edit': return ( this.props.onChange(this.props.name, e.target.value)} InputProps={{ - startAdornment: this.props['data-isPercentage'] ? % : null, - }} + startAdornment: this.defineCurrency(), + }} + inputProps={{ + step: this.defineStepValue(), + max: this.props.max ? this.props.max : null, + min: this.props.min ? this.props.min : null, + }} id={this.props.name} + error={this.props['data-errorText'] && this.props['data-errorText'].length > 0 ? true : false} fullWidth + onChange={(e) => this.handleChange(e)} + helperText={this.props['data-hintText']} /> ) case 'new': return ( this.props.onChange(this.props.name, e.target.value)} InputProps={{ - startAdornment: this.props['data-isPercentage'] ? % : null, - }} + startAdornment: this.defineCurrency(), + }} + inputProps={{ + step: this.defineStepValue(), + max: this.props.max ? this.props.max : null, + min: this.props.min ? this.props.min : null, + }} id={this.props.name} + error={this.props['data-errorText'] && this.props['data-errorText'].length > 0 ? true : false} fullWidth + onChange={(e) => this.handleChange(e)} + helperText={this.props['data-hintText']} /> ) case 'browse': return ( -
-
: null ) default: return ( -
-
: null ) } diff --git a/src/fieldcontrols/Number/NumberFieldSetting.ts b/src/fieldcontrols/Number/NumberFieldSetting.ts index bd846fa..dc4393d 100644 --- a/src/fieldcontrols/Number/NumberFieldSetting.ts +++ b/src/fieldcontrols/Number/NumberFieldSetting.ts @@ -4,15 +4,52 @@ */ /** */ import { ReactClientFieldSetting } from '../ClientFieldSetting' +enum currencies { + USD = '$', + EUR = '€', + BTC = '฿', + JPY = '¥', +} + /** * Interface for NumberFieldSetting properties */ export interface ReactNumberFieldSetting extends ReactClientFieldSetting { + /** + * Defines the allowed maximum value of the input data + */ max?: number, + /** + * Defines the allowed minimum value of the input data + */ min?: number, + /** + * Sets wether the number will be a decimal or a simple integer + * @default false + */ 'data-decimal'?: boolean, + /** + * Specifies the number of the displayed decimals + * @default 2 + */ 'data-digits'?: number, + /** + * Specifies the value used to increment or decrement fieldcontrol's value + */ 'data-step'?: number, + /** + * Specifies wether the control displays a percentage + * @default false + */ 'data-isPercentage'?: boolean, - 'data-isCurrency'?: boolean + /** + * Specifies wether the control displays a currency + * @default false + */ + 'data-isCurrency'?: boolean, + /** + * Specifies currency + * @default USD + */ + 'data-currency'?: currencies } diff --git a/src/fieldcontrols/Password/Password.tsx b/src/fieldcontrols/Password/Password.tsx index f04ae4e..ad6e5f7 100644 --- a/src/fieldcontrols/Password/Password.tsx +++ b/src/fieldcontrols/Password/Password.tsx @@ -2,11 +2,17 @@ * @module FieldControls * */ /** */ -import * as React from 'react' +import React, { Component } from 'react' import { ReactClientFieldSetting, ReactClientFieldSettingProps } from '../ClientFieldSetting' import { ReactPasswordFieldSetting } from './PasswordFieldSetting' -import TextField from '@material-ui/core/TextField' +import FormControl from '@material-ui/core/FormControl' +import FormHelperText from '@material-ui/core/FormHelperText' +import IconButton from '@material-ui/core/IconButton' +import Input from '@material-ui/core/Input' +import InputAdornment from '@material-ui/core/InputAdornment' +import InputLabel from '@material-ui/core/InputLabel' +import { Icon } from '@sensenet/icons-react' import Radium from 'radium' /** @@ -18,7 +24,7 @@ export interface PasswordProps extends ReactClientFieldSettingProps, ReactClient * Field control that represents a Password field. Available values will be populated from the FieldSettings. */ @Radium -export class Password extends React.Component { +export class Password extends Component { /** * constructor * @param {object} props @@ -31,6 +37,7 @@ export class Password extends React.Component { */ this.state = { value: '', + showPassword: false, } this.handleChange = this.handleChange.bind(this) @@ -57,6 +64,12 @@ export class Password extends React.Component { public handleChange(event) { this.setState({ value: event.target.value }) } + /** + * handle clicking on show password icon + */ + public handleClickShowPassword = () => { + this.setState((state) => ({ showPassword: !state.showPassword })) + } /** * render * @return {ReactElement} markup @@ -65,48 +78,65 @@ export class Password extends React.Component { switch (this.props['data-actionName']) { case 'edit': return ( - 0 ? true : false} - fullWidth - /> + + {this.props['data-labelText']} + 0 ? true : false} + fullWidth + endAdornment={ + + + {this.state.showPassword ? : } + + + } + /> + {this.props['data-hintText']} + {this.props['data-errorText']} + ) case 'new': return ( - 0 ? true : false} - fullWidth - /> - ) - case 'browse': - return ( -
- -

- {this.props['data-fieldValue']} -

-
+ + {this.props['data-labelText']} + 0 ? true : false} + fullWidth + endAdornment={ + + + {this.state.showPassword ? : } + + + } + /> + {this.props['data-hintText']} + {this.props['data-errorText']} + ) default: return ( @@ -114,9 +144,6 @@ export class Password extends React.Component { -

- {this.props['data-fieldValue']} -

) } diff --git a/src/fieldcontrols/RadioButtonGroup/RadioButtonGroup.tsx b/src/fieldcontrols/RadioButtonGroup/RadioButtonGroup.tsx index 9080285..f476847 100644 --- a/src/fieldcontrols/RadioButtonGroup/RadioButtonGroup.tsx +++ b/src/fieldcontrols/RadioButtonGroup/RadioButtonGroup.tsx @@ -4,11 +4,12 @@ */ /** */ import FormControl from '@material-ui/core/FormControl' import FormControlLabel from '@material-ui/core/FormControlLabel' +import FormGroup from '@material-ui/core/FormGroup' import FormHelperText from '@material-ui/core/FormHelperText' import FormLabel from '@material-ui/core/FormLabel' import Radio from '@material-ui/core/Radio' import RadioGroup from '@material-ui/core/RadioGroup' -import * as React from 'react' +import React, { Component } from 'react' import { ReactChoiceFieldSetting } from '../ChoiceFieldSetting' import { ReactClientFieldSetting, ReactClientFieldSettingProps } from '../ClientFieldSetting' @@ -20,7 +21,7 @@ export interface RadioButtonGroupProps extends ReactClientFieldSettingProps, Rea /** * Field control that represents a Choice field. Available values will be populated from the FieldSettings. */ -export class RadioButtonGroup extends React.Component { +export class RadioButtonGroup extends Component { /** * constructor * @param {object} props @@ -28,7 +29,7 @@ export class RadioButtonGroup extends React.Component { - this.setState({ value: event.target.value }) - this.props.onChange(this.props.name, event.target.value) + const { value } = this.state + const newValue = event.target.value + const checked = value + const index = value.indexOf(newValue.toString()) + + if (index > -1) { + checked.splice(index, 1) + } else { + checked[0] = newValue + } + this.setState({ + value: checked, + }) + this.props.onChange(this.props.name, checked) } /** * render @@ -47,59 +60,93 @@ export class RadioButtonGroup extends React.Component 0 ? true : false} required={this.props.required}> + 0 ? true : false} + required={this.props.required} + className={this.props.className}> {this.props['data-labelText']} this.handleChange(e)} > {this.props.options.map((option) => { return } label={option.Text} + disabled={this.props.readOnly} /> })} {this.props['data-hintText']} + {this.props['data-errorText']} ) case 'new': return ( - 0 ? true : false} required={this.props.required}> + 0 ? true : false} + required={this.props.required} + className={this.props.className}> {this.props['data-labelText']} this.handleChange(e)} > {this.props.options.map((option) => { return } label={option.Text} + disabled={this.props.readOnly} /> })} {this.props['data-hintText']} + {this.props['data-errorText']} ) case 'browse': return ( - under consctruction + this.props['data-fieldValue'].length > 0 ? + + + {this.props['data-labelText']} + + + {this.props['data-fieldValue'].map((value) => + + (item.Value === value)).Text} control={} key={value} /> + )} + + : null ) default: return ( - under consctruction + this.props['data-fieldValue'].length > 0 ? + + + {this.props['data-labelText']} + + + {this.props['data-fieldValue'].map((value) => + + (item.Value === value)).Text} control={} key={value} /> + )} + + : null ) } } diff --git a/src/fieldcontrols/ReferenceFieldSetting.ts b/src/fieldcontrols/ReferenceFieldSetting.ts index 82e131c..2e217d0 100644 --- a/src/fieldcontrols/ReferenceFieldSetting.ts +++ b/src/fieldcontrols/ReferenceFieldSetting.ts @@ -8,10 +8,31 @@ import { ReactClientFieldSetting } from './ClientFieldSetting' * Interface for ReferenceFieldSetting properties */ export interface ReactReferenceFieldSetting extends ReactClientFieldSetting { + /** + * Defines whether multiple references are allowed or only a single content can be referenced + * @default false + */ 'data-allowMultiple'?: boolean, + /** + * Allowed content types can be defined by explicitely listing type names in Type elements + * @default all + */ 'data-allowedTypes'?, + /** + * Allowed location of referable content can be defined by listing paths in Path elements + * @default /Root + */ 'data-selectionRoot'?, + /** + * Default name of content items displayed in a reference field + */ 'data-defaultDisplayName'?, + /** + * Datasource of a reference field with the optional items that can be chosen + */ dataSource: any[], + /** + * Connected repository + */ repository } diff --git a/src/fieldcontrols/RichTextEditor/RichTextEditor.tsx b/src/fieldcontrols/RichTextEditor/RichTextEditor.tsx index ad73aed..7dd87d4 100644 --- a/src/fieldcontrols/RichTextEditor/RichTextEditor.tsx +++ b/src/fieldcontrols/RichTextEditor/RichTextEditor.tsx @@ -2,7 +2,8 @@ * @module FieldControls * */ /** */ -import * as React from 'react' +import Typography from '@material-ui/core/Typography' +import React, { Component } from 'react' import { ReactClientFieldSetting, ReactClientFieldSettingProps } from '../ClientFieldSetting' import { ReactRichTextEditorFieldSetting } from './RichTextEditorFieldSetting' @@ -21,7 +22,7 @@ export interface RichTextEditorProps extends ReactClientFieldSettingProps, React * Field control that represents a LongText field. Available values will be populated from the FieldSettings. */ @Radium -export class RichTextEditor extends React.Component { +export class RichTextEditor extends Component { /** * constructor * @param {object} props @@ -80,7 +81,7 @@ export class RichTextEditor extends React.Component -