UNPKG

@instructure/quiz-interactions

Version:

A React UI component Library for quiz interaction types.

195 lines (175 loc) • 6.07 kB
/** @jsx jsx */ import {Component} from 'react' import PropTypes from 'prop-types' import {camelize} from 'humps' import {ScreenReaderContent} from '@instructure/ui-a11y-content' import {RadioInput} from '@instructure/ui-radio-input' import {jsx} from '@instructure/emotion' import t from '@instructure/quiz-i18n/format-message' import {ItemBodyWrapper} from '@instructure/quiz-rce/components/ItemBodyWrapper/index' import {RichContentRenderer} from '@instructure/quiz-rce/components/RichContentRenderer/index' import {FeedbackWrapper, NoResponse} from '@instructure/quiz-results-feedback' import generateStyle from './styles' import generateComponentTheme from './theme' import {withStyleOverrides} from '@instructure/quiz-common/util/withStyleOverrides' export const correctChoice = function ({interactionData, scoredData}) { return ( interactionData.choices.find(item => { const choiceData = (scoredData.value && scoredData.value[item.id]) || {} return choiceData.hasOwnProperty('correct') ? choiceData.correct : choiceData.resultScore }) || {} ) } export const getChoiceData = (scoredData, id) => { if (!scoredData?.value) return {} // Some imported quizzes have non-standard IDs (e.g., "ans-6-4") that get // transformed by camelizeHelpers (e.g., "ans64" or "ans6_4"). To make sure // student selections still match correctly, we check both the original, // dash-stripped, and camelized versions of the ID. return ( scoredData.value[id] || scoredData.value[id.replace(/-/g, '')] || scoredData.value[camelize(id)] || {} ) } /** --- category: MultipleChoice --- Multiple Choice Result component ```jsx_example <SettingsSwitcher locales={LOCALES}> <MultipleChoiceResult itemBody="<p>Who was the first <strong>President</strong> of the United States?</p>" itemId="44" interactionData={{ choices: [{ id: 'uuid1', position: 1, itemBody: '<p>George Washington</p><p>was the first</p><p>president</p>' }, { id: 'uuid2', position: 2, itemBody: '<p>Alexander Hamilton</p>' }, { id: 'uuid3', position: 3, itemBody: '<p>John Adams</p>' }, { id: 'uuid4', position: 4, itemBody: '<p>Thomas Jefferson</p>' } ] }} scoredData={{ correct: false, value: { uuid1: { resultScore: 1, correct: true }, uuid2: { resultScore: 0, userResponded: true } } }} /> </SettingsSwitcher> ``` **/ @withStyleOverrides(generateStyle, generateComponentTheme) export default class MultipleChoiceResult extends Component { static displayName = 'MultipleChoiceResult' static componentId = `Quizzes${this.displayName}` static propTypes = { 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, scoredData: PropTypes.shape({ correct: PropTypes.bool, value: PropTypes.objectOf( PropTypes.shape({ correct: PropTypes.bool, resultScore: PropTypes.number, userResponded: PropTypes.bool, }), ), }).isRequired, styles: PropTypes.object, } static defaultProps = { itemId: void 0, } renderFeedbackChoice = ({id, itemBody}, status) => ( <div key={id} css={this.props.styles.responseWrapper}> <FeedbackWrapper correctAnswer={correctChoice(this.props).itemBody} hiddenCorrectAnswerText={t('Correct answer: ')} hiddenIncorrectAnswerText={t('Incorrect answer: ')} status={status} richContent > <RadioInput value={id} name={`interaction_${this.props.itemId}`} label={<RichContentRenderer content={itemBody} />} checked readOnly /> </FeedbackWrapper> </div> ) renderUngradedChoice = ({id, itemBody}, checked) => ( <div key={id} css={[this.props.styles.responseWrapper, this.props.styles.radioWrapper]}> <RadioInput value={id} name={`interaction_${this.props.itemId}`} label={ <div> <RichContentRenderer content={itemBody} /> {!checked && <ScreenReaderContent>{t(', Not Selected')}</ScreenReaderContent>} </div> } checked={checked} readOnly /> </div> ) render() { const {scoredData} = this.props const choices = this.props?.interactionData?.choices?.map(choice => { const choiceData = getChoiceData(scoredData, choice?.id) const correct = choiceData.hasOwnProperty('correct') ? choiceData.correct : choiceData.resultScore const checked = choiceData.userResponded return {choice, correct, checked} }) const noResponse = choices.every(({checked}) => !checked) const correctnessKnown = typeof scoredData.correct !== 'undefined' return ( <ItemBodyWrapper itemBody={this.props.itemBody}> <div> {choices.map(({choice, correct, checked}) => { let status if (correct === true || correct > 0) { status = 'correct' } else if (correctnessKnown && checked) { status = 'incorrect' } if (!status || !checked) { // Choice has no feedback return this.renderUngradedChoice(choice, checked) } return this.renderFeedbackChoice(choice, status) })} </div> {noResponse && ( <NoResponse css={this.props.styles.noResponseWrapper} correctAnswerLabel={correctChoice(this.props).itemBody} responseHidden={!scoredData.value} richContent status={correctnessKnown ? 'incorrect' : 'unknown'} /> )} </ItemBodyWrapper> ) } }