@instructure/quiz-interactions
Version:
A React UI component Library for quiz interaction types.
140 lines (119 loc) • 4.15 kB
JavaScript
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {SELECT_EMPTY_OPTION, SELECT_EMPTY_OPTION_TEXT} from '@instructure/quiz-common/constants'
import {Select} from '@instructure/quiz-common/components/Select/index'
export default class MatchSelect extends Component {
static propTypes = {
questionId: PropTypes.string.isRequired,
options: PropTypes.arrayOf(PropTypes.string).isRequired,
handleResponseUpdate: PropTypes.func,
selectedAnswers: PropTypes.shape({
value: PropTypes.objectOf(PropTypes.string),
}).isRequired,
interaction: PropTypes.string,
renderLabel: PropTypes.element.isRequired,
}
static defaultProps = {
handleResponseUpdate: void 0,
interaction: 'enabled',
}
state = {
inputValue: this.props.selectedAnswers.value[this.props.questionId] || '',
isShowingOptions: false,
highlightedOptionId: null,
filteredOptions: this.props.options,
}
filterOptions = value => {
return this.props.options.filter(option => option.startsWith(value))
}
handleShowOptions = () => {
this.setState({
isShowingOptions: true,
})
}
handleHideOptions = () => {
const {filteredOptions, inputValue} = this.state
const index = filteredOptions.findIndex(option => option === inputValue)
this.handleSelectOption(null, {
id: index === -1 ? undefined : this.getIdFromText(filteredOptions[index]),
})
}
handleBlur = () => {
this.setState({highlightedOptionId: null})
}
handleHighlightOption = (event, option) => {
event.persist()
if (!option || !option.id) return // prevent highlighting of empty option
this.setState({
highlightedOptionId: option.id,
inputValue: event.type === 'keydown' ? this.getTextFromId(option.id) : this.state.inputValue,
})
}
handleSelectOption = (event, option) => {
if (!option || option.id === SELECT_EMPTY_OPTION) return
const value = option.id ? this.getTextFromId(option.id) : ''
this.props.handleResponseUpdate({
...(this.props.selectedAnswers && this.props.selectedAnswers.value),
[this.props.questionId]: option && value,
})
this.setState({
inputValue: value,
isShowingOptions: false,
filteredOptions: this.props.options,
})
}
handleInputChange = event => {
const value = event.target.value
const newOptions = this.filterOptions(value)
this.setState({
isShowingOptions: true,
inputValue: value,
highlightedOptionId: newOptions.length > 0 ? this.getIdFromText(newOptions[0]) : null,
})
}
getIdFromText = text => {
return btoa(encodeURIComponent(text))
}
getTextFromId = id => {
return decodeURIComponent(atob(id))
}
render() {
const {inputValue, isShowingOptions, highlightedOptionId, filteredOptions} = this.state
return (
<Select
interaction={this.props.interaction}
renderLabel={this.props.renderLabel}
inputValue={inputValue}
isShowingOptions={isShowingOptions}
onBlur={this.handleBlur}
optionsMaxHeight="50vh"
onInputChange={this.handleInputChange}
onRequestShowOptions={this.handleShowOptions}
onRequestHideOptions={this.handleHideOptions}
onRequestHighlightOption={this.handleHighlightOption}
onRequestSelectOption={this.handleSelectOption}
mountNode={() => document.body}
>
{filteredOptions.length > 0 ? (
filteredOptions.map(option => {
const normalized = this.getIdFromText(option)
return (
<Select.Option
id={normalized}
key={option}
isHighlighted={normalized === highlightedOptionId}
isSelected={option === this.props.selectedAnswers.value[this.props.questionId]}
>
{option}
</Select.Option>
)
})
) : (
<Select.Option id={SELECT_EMPTY_OPTION} key={SELECT_EMPTY_OPTION}>
{SELECT_EMPTY_OPTION_TEXT}
</Select.Option>
)}
</Select>
)
}
}