@instructure/quiz-interactions
Version:
A React UI component Library for quiz interaction types.
181 lines (165 loc) • 5.79 kB
JavaScript
/** @jsx jsx */
import {Component} from 'react'
import PropTypes from 'prop-types'
import map from 'lodash/fp/map'
import striptags from 'striptags'
import {jsx} from '@instructure/emotion'
import {CheckboxGroup, Checkbox} from '@instructure/ui-checkbox'
import {IconButton} from '@instructure/ui-buttons'
import {IconStrikethroughLine} from '@instructure/ui-icons'
import {ItemBodyWrapper} from '@instructure/quiz-rce/components/ItemBodyWrapper/index'
import {RichContentRenderer} from '@instructure/quiz-rce/components/RichContentRenderer/index'
import generateStyle from './styles'
import generateComponentTheme from './theme'
import t from '@instructure/quiz-i18n/format-message'
import {withStyleOverrides} from '@instructure/quiz-common/util/withStyleOverrides'
/**
---
category: MultipleAnswer
---
Multiple Answer Take component
```jsx_example
function Example (props) {
const exampleProps = {
itemBody: 'Who was in the first cabinet of the USA?',
interactionData: {
choices: [
{ id: 'uuid1', itemBody: 'Thomas Jefferson', position: 1 },
{ id: 'uuid2', itemBody: 'John Marshall', position: 2 },
{ id: 'uuid3', itemBody: 'John Knox', position: 3 },
{ id: 'uuid4', itemBody: 'Alexander Hamilton', position: 4 },
{ id: 'uuid5', itemBody: 'Aaron Burr', position: 5 },
{ id: 'uuid6', itemBody: 'Ben Franklin', position: 6 }
]
},
scoringData: { value: ['uuid1', 'uuid3', 'uuid4'] },
userResponse: { value: ['uuid1', 'uuid6'] }
}
return (
<MultipleAnswerTake {...exampleProps} {...props} />
)
}
<SettingsSwitcher locales={LOCALES}>
<TakeStateProvider>
<Example />
</TakeStateProvider>
</SettingsSwitcher>
```
**/
(generateStyle, generateComponentTheme)
export default class MultipleAnswerTake extends Component {
static displayName = 'MultipleAnswerTake'
static componentId = `Quizzes${this.displayName}`
static propTypes = {
choiceElimination: PropTypes.bool,
handleResponseUpdate: PropTypes.func.isRequired,
itemId: PropTypes.string,
itemBody: PropTypes.string.isRequired,
interactionData: PropTypes.shape({
choices: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string,
itemBody: PropTypes.string,
}),
),
}).isRequired,
userResponse: PropTypes.shape({
value: PropTypes.arrayOf(PropTypes.string),
properties: PropTypes.shape({
eliminated: PropTypes.array,
}),
}),
readOnly: PropTypes.bool,
styles: PropTypes.object,
}
static defaultProps = {
choiceElimination: false,
itemId: void 0,
readOnly: false,
userResponse: {},
handleResponseUpdate: Function.prototype,
}
getEliminations() {
return this.props.userResponse?.properties?.eliminated || []
}
handleCheckboxGroupChange = value => {
if (!this.props.readOnly) {
this.props.handleResponseUpdate(value, {eliminated: this.getEliminations()})
}
}
toggleElimination = choiceId => event => {
event.preventDefault()
const currEliminations = this.getEliminations()
if (currEliminations.includes(choiceId)) {
const choiceIndex = currEliminations.indexOf(choiceId)
this.props.handleResponseUpdate(this.props.userResponse?.value, {
eliminated: [
...currEliminations.slice(0, choiceIndex),
...currEliminations.slice(choiceIndex + 1),
],
})
} else {
this.props.handleResponseUpdate(this.props.userResponse?.value, {
eliminated: [...currEliminations, choiceId],
})
}
}
renderChoice = choice => {
const {readOnly, choiceElimination} = this.props
const eliminated = this.getEliminations().includes(choice.id)
const itemBody = (
<RichContentRenderer customStyles={this.props.styles.itemBody} content={choice.itemBody} />
)
const showEliminations = !readOnly && choiceElimination
const screenReaderLabel = eliminated
? t('Undo elimination of answer {choice}', {choice: striptags(choice.itemBody)})
: t('Eliminate answer {choice}', {choice: striptags(choice.itemBody)})
const eliminationLabel = (
<div css={this.props.styles.labelWrapper}>
{eliminated ? <s>{itemBody}</s> : itemBody}
<IconButton
withBackground={false}
withBorder={false}
onClick={this.toggleElimination(choice.id)}
screenReaderLabel={screenReaderLabel}
>
<IconStrikethroughLine />
</IconButton>
</div>
)
const ariaLabel = t('Answer: {choice}', {choice: striptags(choice.itemBody)})
const ariaEliminationLabel = eliminated
? t('Eliminated Answer: {choice}', {choice: striptags(choice.itemBody)})
: ariaLabel
return (
<Checkbox
key={choice.id}
value={choice.id}
aria-label={showEliminations ? ariaEliminationLabel : ariaLabel}
label={showEliminations ? eliminationLabel : itemBody}
/>
)
}
render() {
return (
<ItemBodyWrapper>
<div className="fs-mask" css={this.props.styles.fs_mask}>
<CheckboxGroup
value={this.props.userResponse?.value || []}
name={`interaction_${this.props.itemId}`}
onChange={this.handleCheckboxGroupChange}
readOnly={this.props.readOnly}
description={
<RichContentRenderer
content={this.props.itemBody}
customStyles={this.props.styles.itemDescription}
/>
}
>
{map(this.renderChoice, this.props.interactionData.choices)}
</CheckboxGroup>
</div>
</ItemBodyWrapper>
)
}
}