@instructure/quiz-interactions
Version:
A React UI component Library for quiz interaction types.
207 lines (189 loc) • 6.5 kB
JavaScript
/** @jsx jsx */
import {Component} from 'react'
import PropTypes from 'prop-types'
import map from 'lodash/fp/map'
import sortBy from 'lodash/fp/sortBy'
import flow from 'lodash/fp/flow'
import striptags from 'striptags'
import {RadioInputGroup, RadioInput} from '@instructure/ui-radio-input'
import {CondensedButton, IconButton} from '@instructure/ui-buttons'
import {IconStrikethroughLine} from '@instructure/ui-icons'
import {jsx} from '@instructure/emotion'
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: MultipleChoice
---
Multiple Choice Take component
```jsx_example
function Example (props) {
const exampleProps = {
itemId: 'mc_type',
itemBody: 'Who was the first President of the United States?',
interactionData: {
choices: [
{ id: 'uuid1', itemBody: 'George Washington', position: 1 },
{ id: 'uuid2', itemBody: 'Alexander Hamilton.', position: 2 },
{
id: 'uuid3',
itemBody: `
<p>
John Adams
<iframe
src="//www.youtube.com/embed/nrvpZxMfKaU"
width="560"
height="314"
allowfullscreen="allowfullscreen"
></iframe>
</p>
`,
position: 3
},
{ id: 'uuid4', itemBody: 'Thomas Jefferson', position: 4 }
]
},
userResponse: { value: 'uuid1' }
}
return (
<MultipleChoiceTake {...exampleProps} {...props} />
)
}
<SettingsSwitcher locales={LOCALES}>
<TakeStateProvider>
<Example />
</TakeStateProvider>
</SettingsSwitcher>
```
**/
(generateStyle, generateComponentTheme)
export default class MultipleChoiceTake extends Component {
static displayName = 'MultipleChoiceTake'
static componentId = `Quizzes${this.displayName}`
static propTypes = {
allowClearMcSelection: PropTypes.bool,
choiceElimination: PropTypes.bool,
handleResponseUpdate: PropTypes.func,
interactionData: PropTypes.shape({
choices: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string,
itemBody: PropTypes.string,
position: PropTypes.number,
}),
),
}).isRequired,
itemBody: PropTypes.string.isRequired,
itemId: PropTypes.string,
userResponse: PropTypes.shape({
value: PropTypes.string,
properties: PropTypes.shape({
eliminated: PropTypes.array,
}),
}).isRequired,
readOnly: PropTypes.bool,
styles: PropTypes.object,
}
static defaultProps = {
allowClearMcSelection: false,
choiceElimination: false,
handleResponseUpdate: Function.prototype,
itemId: void 0,
readOnly: false,
}
getEliminations() {
return this.props.userResponse?.properties?.eliminated || []
}
handleChosenValChange = (event, choiceId) => {
this.props.handleResponseUpdate(choiceId, {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],
})
}
}
clearSelection = () => {
this.props.handleResponseUpdate(null)
}
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 (
<RadioInput
key={choice.id}
value={choice.id}
aria-label={showEliminations ? ariaEliminationLabel : ariaLabel}
label={showEliminations ? eliminationLabel : itemBody}
/>
)
}
render() {
const chosenVal = this.props.userResponse?.value
return (
<ItemBodyWrapper>
<div className="fs-mask" css={this.props.styles.fs_mask}>
<RadioInputGroup
defaultValue={chosenVal}
value={chosenVal}
onChange={this.handleChosenValChange}
name={`interaction_${this.props.itemId}`}
readOnly={this.props.readOnly}
description={
<RichContentRenderer
content={this.props.itemBody}
customStyles={this.props.styles.itemDescription}
/>
}
>
{flow(sortBy('position'), map(this.renderChoice))(this.props.interactionData.choices)}
</RadioInputGroup>
{this.props.allowClearMcSelection && (
<CondensedButton onClick={this.clearSelection} margin="small 0 0 medium">
{t('Clear my selection')}
</CondensedButton>
)}
</div>
</ItemBodyWrapper>
)
}
}