Skip to content

Commit

Permalink
feat(qna): add multiline support, ux adjustments
Browse files Browse the repository at this point in the history
  • Loading branch information
allardy committed Jan 31, 2019
1 parent 6e70f4c commit 91cc5f4
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 29 deletions.
11 changes: 6 additions & 5 deletions modules/qna/src/views/FormModal.jsx
Expand Up @@ -254,12 +254,13 @@ export default class FormModal extends Component {
</span>

<ElementsList
placeholder="Type and press enter to add an answer"
invalid={this.state.invalidFields.answer}
placeholder="Type and press enter to add an answer. Use ALT+Enter for a new line"
elements={this.state.item.answers}
create={this.createAnswer}
update={this.updateAnswer}
delete={this.deleteAnswer}
allowMultiline={true}
onInvalid={this.state.invalidFields.answer}
onCreate={this.createAnswer}
onUpdate={this.updateAnswer}
onDelete={this.deleteAnswer}
/>
</div>

Expand Down
Expand Up @@ -3,59 +3,78 @@ import React from 'react'
import classnames from 'classnames'
import style from './style.scss'

const DEFAULT_ROW_HEIGHT = 32

export class InputElement extends React.Component {
elementInputRef = React.createRef()

state = {
error: undefined
rowHeight: DEFAULT_ROW_HEIGHT,
error: undefined,
text: ''
}

get inputValue() {
return get(this.elementInputRef, 'current.value', '')
componentDidMount() {
this.setState({ text: this.props.defaultValue }, this.updateTextareaHeight)
}

get inputNotEmpty() {
return this.inputValue.length > 0
return this.state.text && this.state.text.trim().length > 0
}

handleOnEnter = event => {
if (event.key === 'Enter' && this.inputNotEmpty) {
this.tryAddElement(this.inputValue)
handleKeyDown = event => {
if (event.key === 'Enter') {
if (event.altKey && this.props.allowMultiline) {
this.setState({ text: this.state.text + '\r\n' }, this.updateTextareaHeight)
} else if (!event.altKey && this.inputNotEmpty) {
this.tryAddElement()
}

event.preventDefault()
}
}

handleOnChange = event => {
this.setState({ text: event.target.value }, this.updateTextareaHeight)
}

updateTextareaHeight = () => {
this.setState({ rowHeight: this.elementInputRef.current.scrollHeight })
}

handleOnBlur = _event => {
if (this.inputNotEmpty && this.inputNotEmpty) {
this.tryAddElement()
}
this.inputNotEmpty && this.tryAddElement()
}

tryAddElement = () => {
const elementUnique = !this.props.elements.includes(this.inputValue)
const elementUnique = !this.props.elements.includes(this.state.text)
if (!this.inputNotEmpty || !elementUnique) {
return this.setState({ error: new Error('The element must be unique and cannot be an empty string.') })
}

const value = this.inputValue // make a copy because of async
const value = this.state.text // make a copy because of async
this.setState({ error: undefined }, () => this.props.onElementAdd(value))

if (this.props.cleanInputAfterEnterPressed) {
this.elementInputRef.current.value = ''
this.setState({ text: '', rowHeight: DEFAULT_ROW_HEIGHT })
}
}

render() {
return (
<React.Fragment>
<input
<textarea
autoFocus
className={classnames('form-control', (this.state.error || this.props.invalid) && style.inputError)}
className={classnames('form-control', style.inputArea, {
[style.inputError]: this.state.error || this.props.invalid
})}
ref={this.elementInputRef}
type="text"
style={{ height: this.state.rowHeight }}
value={this.state.text}
placeholder={this.props.placeholder || ''}
defaultValue={this.props.defaultValue || ''}
onBlur={this.handleOnBlur}
onKeyDown={this.handleOnEnter}
onKeyDown={this.handleKeyDown}
onChange={this.handleOnChange}
/>
{this.state.error && <div>{this.state.error.message}</div>}
</React.Fragment>
Expand Down
Expand Up @@ -17,7 +17,7 @@ export default class ElementsList extends React.Component {

handleNewElement = (element, index) => {
this.setState({ editElementIndex: undefined })
this.props.update(element, index)
this.props.onUpdate(element, index)
}

renderElement = (element, index) => {
Expand All @@ -26,7 +26,8 @@ export default class ElementsList extends React.Component {
<InputElement
key={`elements_edit_element_${index}`}
defaultValue={element}
elements={this.props.elements}
allowMultiline={this.props.allowMultiline}
elements={this.props.elements.filter(el => el !== element)}
onElementAdd={element => this.handleNewElement(element, index)}
/>
)
Expand All @@ -36,21 +37,23 @@ export default class ElementsList extends React.Component {
<a className={style.listElementValue} onClick={() => this.toggleEditMode(index)}>
{element}
</a>
<Glyphicon glyph="trash" onClick={() => this.props.delete(index)} className={style.listElementIcon} />
<Glyphicon glyph="trash" onClick={() => this.props.onDelete(index)} className={style.listElementIcon} />
</ListGroupItem>
)
}
}

render() {
const multilineHint = this.props.allowMultiline ? ' Use ALT+Enter for a new line' : ''
return (
<div>
<InputElement
placeholder={this.props.placeholder || 'Type and press enter to create an element'}
invalid={this.props.invalid}
placeholder={this.props.placeholder || 'Type and press enter to create an element.' + multilineHint}
onInvalid={this.props.onInvalid}
cleanInputAfterEnterPressed={true}
allowMultiline={this.props.allowMultiline}
elements={this.props.elements}
onElementAdd={this.props.create}
onElementAdd={this.props.onCreate}
/>
{this.props.elements && this.props.elements.map((element, index) => this.renderElement(element, index))}
</div>
Expand Down

0 comments on commit 91cc5f4

Please sign in to comment.