UNPKG

@instructure/quiz-interactions

Version:

A React UI component Library for quiz interaction types.

173 lines (143 loc) 4.2 kB
/** @jsx jsx */ import {useCallback, useEffect, useRef} from 'react' import PropTypes from 'prop-types' import {useDrop} from 'react-dnd' import {Menu} from '@instructure/ui-menu' import {Text} from '@instructure/ui-text' import {jsx} from '@instructure/emotion' import {processNewMathInElem} from '@instructure/quiz-rce/components/RichContentRenderer/mathRenderer' import BlankAnswer from '../BlankAnswer' import generateStyle from './styles' import generateComponentTheme from './theme' import t from '@instructure/quiz-i18n/format-message' import {withStyleOverrides} from '@instructure/quiz-common/util/withStyleOverrides' const ENTER_KEY = 'Enter' const SPACE_KEY = ' ' const hasAnswerOf = choice => Boolean(choice && choice.id) function BlankDropTarget(props) { const {choices, choice, blankId, readOnly, onChange, onDrop, returnChoiceToWordbank, styles} = props const blankAnswerRef = useRef(null) const dragTargetRef = useRef(null) const parentNodeRef = useRef(null) const popoverRef = useRef(null) const hasAnswer = hasAnswerOf(choice) const prevHasAnswerRef = useRef(hasAnswer) const [, drop] = useDrop( () => ({ accept: 'fitb', drop(item) { if (onDrop) onDrop(item) }, }), [onDrop], ) useEffect(() => { const prevHasAnswer = prevHasAnswerRef.current if (hasAnswer && !prevHasAnswer) { blankAnswerRef.current?.focus() } else if (!hasAnswer && prevHasAnswer) { dragTargetRef.current?.focus() } processNewMathInElem(parentNodeRef.current) prevHasAnswerRef.current = hasAnswer }) const setParentRef = useCallback( node => { parentNodeRef.current = node drop(node) }, [drop], ) const setPopoverRef = useCallback(node => { popoverRef.current = node }, []) function toggleDragDropdown() { setTimeout(() => { if (popoverRef.current?._contentElement) { processNewMathInElem(popoverRef.current._contentElement) } }) } function handleRemoveAnswer() { returnChoiceToWordbank(choice.id) } function handleDragTargetKeyDown(event) { if (event.key === ENTER_KEY || event.key === SPACE_KEY) { event.preventDefault() event.target.click() } } function makeOptionSelectHandler(selectedChoice) { return function onOptionSelect() { onChange(selectedChoice) } } function renderAnswer() { return ( <BlankAnswer ref={blankAnswerRef} choiceId={choice.id} blankId={blankId} itemBody={choice.itemBody} onRemoveAnswer={handleRemoveAnswer} readOnly={readOnly} /> ) } function renderDragTarget() { return ( <div css={styles.dragTarget} onKeyDown={handleDragTargetKeyDown} ref={dragTargetRef} role="button" tabIndex="0" > <Text color="secondary">{t('Answer')}</Text> </div> ) } function renderOptions() { return choices.map(c => ( <Menu.Item key={c.id} value={c.id} onSelect={makeOptionSelectHandler(c)}> {c.itemBody} </Menu.Item> )) } function renderDragDropdown() { return ( <Menu trigger={renderDragTarget()} onToggle={toggleDragDropdown} popoverRef={setPopoverRef}> {renderOptions()} </Menu> ) } return ( <div ref={setParentRef} role="presentation" tabIndex="-1"> {hasAnswer ? renderAnswer() : renderDragDropdown()} </div> ) } BlankDropTarget.displayName = 'BlankDropTarget' BlankDropTarget.componentId = `Quizzes${BlankDropTarget.displayName}` BlankDropTarget.propTypes = { choices: PropTypes.array, choice: PropTypes.object, blankId: PropTypes.string.isRequired, value: PropTypes.string, readOnly: PropTypes.bool, onChange: PropTypes.func, onDrop: PropTypes.func, returnChoiceToWordbank: PropTypes.func, styles: PropTypes.object, } BlankDropTarget.defaultProps = { choices: undefined, choice: undefined, value: undefined, readOnly: false, onChange: undefined, onDrop: undefined, returnChoiceToWordbank: undefined, } export default withStyleOverrides(generateStyle, generateComponentTheme)(BlankDropTarget)