hbm-react-components
Version:
HBM React components
220 lines (197 loc) • 5.48 kB
JavaScript
import React from 'react'
const keyEnter = 13
const keyBackspace = 8
const keyDelete = 46
const keyArrowLeft = 37
const keyArrowRight = 39
// input fields which handles all logic
export default class Chip extends React.Component {
static propTypes = {
autoFocus: React.PropTypes.bool,
delimiters: React.PropTypes.arrayOf(React.PropTypes.number),
onBlur: React.PropTypes.func,
onChange: React.PropTypes.func,
onFocus: React.PropTypes.func,
placeholder: React.PropTypes.string,
value: React.PropTypes.arrayOf(React.PropTypes.object)
}
static defaultProps = {
onChange: () => {},
delimiters: [keyEnter],
value: []
}
state = {
inputValue: ''
}
onKeyPress = (event) => {
const text = this.state.inputValue.trim()
if ((this.props.delimiters.indexOf(event.which) > -1) && text !== '') {
if (event.which !== keyEnter) {
event.preventDefault()
}
// empty input field
this.setState({
inputValue: ''
})
// notify parent component
this.props.onChange([
...this.props.value,
{text}
])
}
}
// KeyPress event is invoked only for character (printable) keys.
// That's why we need KeyDown event for backspace and arrow keys.
onKeyDown = (event) => {
// toggle through chips when input field is empty and when we have some chips to toggle through
if (this.state.inputValue !== '' || !this.props.value.length) {
return
}
// go left
if (event.which === keyBackspace || event.which === keyArrowLeft) {
return this[`element#${this.props.value.length - 1}`].focus()
}
// go right
if (event.which === keyArrowRight) {
return this[`element#${0}`].focus()
}
}
onChange = (event) => {
this.setState({inputValue: event.target.value})
}
onDelete = (index) => {
// remove focused chip
const value = [
...this.props.value.slice(0, index),
...this.props.value.slice(index + 1)
]
// focus appropriate element
if (value.length === 0) {
// focus input field when all chips are gone
this.input.focus()
} else if (value.length === index) {
// if last chip is deleted select chip to the left
this[`element#${index - 1}`].focus()
} else {
// focus chip to the right
this[`element#${index}`].focus()
}
// notify parent component
this.props.onChange(value)
}
onChipArrowLeft = (index) => {
if (index === 0) {
this.input.focus()
} else {
this[`element#${index - 1}`].focus()
}
}
onChipArrowRight = (index) => {
if (index === this.props.value.length - 1) {
this.input.focus()
} else {
this[`element#${index + 1}`].focus()
}
}
onWrapperClick = (event) => {
// do not handle click events on children, e.g. chips
if (event.target === event.currentTarget) {
this.input.focus()
}
}
render () {
return (
<div
className='Chip-wrapper'
onClick={this.onWrapperClick}
>
{this.props.value.map((chip, i) =>
<Element
key={i}
index={i}
text={chip.text}
onDelete={this.onDelete}
onArrowLeft={this.onChipArrowLeft}
onArrowRight={this.onChipArrowRight}
onFocus={this.props.onFocus}
onBlur={this.props.onBlur}
ref={c => { this[`element#${i}`] = c }}
/>
)}
<input
className='Chip-input'
type='text'
onKeyPress={this.onKeyPress}
onKeyDown={this.onKeyDown}
value={this.state.inputValue}
onChange={this.onChange}
ref={c => { this.input = c }}
onFocus={this.props.onFocus}
onBlur={this.props.onBlur}
autoFocus={this.props.autoFocus}
placeholder={this.props.placeholder}
/>
</div>
)
}
}
class Element extends React.Component {
static propTypes = {
autoFocus: React.PropTypes.bool,
icon: React.PropTypes.string,
onDelete: React.PropTypes.func,
onBlur: React.PropTypes.func,
onFocus: React.PropTypes.func,
text: React.PropTypes.string
}
static defaultProps = {
autoFocus: false,
text: 'Chip'
}
focus = () => {
this.chipRef.focus()
}
render () {
const {text, onDelete, icon, index, onArrowLeft, onArrowRight, onBlur, onFocus} = this.props
const textStyle = {
marginLeft: icon ? 8 : 12,
marginRight: onDelete ? 0 : 12
}
const button = (
<button
tabIndex='-1'
className='Chip-delete'
onClick={() => { onDelete(index) }}
>
×
</button>
)
return (
<div
className='Chip'
tabIndex='0'
onFocus={onFocus}
onBlur={onBlur}
onKeyDown={(event) => {
if (event.which === keyBackspace || event.which === keyDelete) {
event.preventDefault()
return onDelete(index)
}
if (event.which === keyArrowLeft) {
return onArrowLeft(index)
}
if (event.which === keyArrowRight) {
return onArrowRight(index)
}
}}
ref={c => { this.chipRef = c }}
>
{icon ? <div className='Chip-icon'>{icon}</div> : null}
<span style={textStyle}>
{text}
</span>
{onDelete ? button : null}
</div>
)
}
}