@instructure/quiz-interactions
Version:
A React UI component Library for quiz interaction types.
264 lines (263 loc) • 12 kB
JavaScript
/** @jsx jsx */ import React, { useEffect, useImperativeHandle, useLayoutEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import { jsx } from '@instructure/emotion';
import { Flex } from '@instructure/quiz-common/components/Flex/index';
import { withStyleOverrides } from '@instructure/quiz-common/util/withStyleOverrides';
import { FormFieldGroup } from '@instructure/quiz-common/components/FormFieldGroup/index';
import { Text } from '@instructure/ui-text';
import ChoiceInput from '../../../common/edit/components/ChoiceInput';
import AnswerInput from '../../../common/edit/components/AnswerInput';
import RemoveChoiceButton from '../../../common/edit/components/RemoveChoiceButton';
import Footer from '../../../common/edit/components/Footer';
import generateStyle from './styles';
import generateComponentTheme from './theme';
import t from '@instructure/quiz-i18n/format-message';
import { normalizeErrors } from '../../../../util/normalizeErrors';
var BaseCategoryForm = /*#__PURE__*/ React.forwardRef(function(props, ref) {
var categoriesErrors = props.categoriesErrors, getCategoryErrors = props.getCategoryErrors, categoryScoringData = props.categoryScoringData, disabled = props.disabled, distractors = props.distractors, distractorErrors = props.distractorErrors, notifyScreenreader = props.notifyScreenreader, onCategoryInputChange = props.onCategoryInputChange, onCreateAnswer = props.onCreateAnswer, onCreateCategory = props.onCreateCategory, onDistractorInputChange = props.onDistractorInputChange, onModalClose = props.onModalClose, onModalOpen = props.onModalOpen, onRemoveAnswer = props.onRemoveAnswer, onRemoveCategory = props.onRemoveCategory, sortedCategories = props.sortedCategories, styles = props.styles;
var answerRefsRef = useRef({});
var categoryRefsRef = useRef({});
var categoryAnswerInputRefsRef = useRef({});
var answerRemoveHandlersRef = useRef({});
var categoryRemoveHandlersRef = useRef({});
var createAnswerHandlersRef = useRef({});
var isMountedRef = useRef(false);
var footerRef = useRef(null);
var prevPropsRef = useRef({
onCreateAnswer: onCreateAnswer,
onRemoveAnswer: onRemoveAnswer,
onRemoveCategory: onRemoveCategory
});
useEffect(function() {
// focusOnMount is conditional on this component being mounted so that focus
// is not stolen on initial render when creating a new categorization question
isMountedRef.current = true;
}, []);
useLayoutEffect(function() {
if (prevPropsRef.current.onCreateAnswer !== onCreateAnswer) {
createAnswerHandlersRef.current = {};
}
if (prevPropsRef.current.onRemoveAnswer !== onRemoveAnswer) {
answerRemoveHandlersRef.current = {};
}
if (prevPropsRef.current.onRemoveCategory !== onRemoveCategory) {
categoryRemoveHandlersRef.current = {};
}
prevPropsRef.current = {
onCreateAnswer: onCreateAnswer,
onRemoveAnswer: onRemoveAnswer,
onRemoveCategory: onRemoveCategory
};
}, [
onCreateAnswer,
onRemoveAnswer,
onRemoveCategory
]);
// Expose refs to parent component
useImperativeHandle(ref, function() {
return {
footerRef: footerRef.current,
categoryRefs: categoryRefsRef.current,
categoryAnswerInputRefs: categoryAnswerInputRefsRef.current,
answerRefs: answerRefsRef.current
};
});
var getCreateAnswerHandler = function(categoryId) {
var createAnswerHandler = createAnswerHandlersRef.current[categoryId];
if (createAnswerHandler) return createAnswerHandler;
createAnswerHandler = onCreateAnswer.bind(null, categoryId);
createAnswerHandlersRef.current[categoryId] = createAnswerHandler;
return createAnswerHandler;
};
var getAnswerRemoveHandler = function(answerId, categoryId) {
var answerRemoveHandler = answerRemoveHandlersRef.current[categoryId];
if (answerRemoveHandler) return answerRemoveHandler;
answerRemoveHandler = onRemoveAnswer.bind(null, answerId, categoryId);
answerRemoveHandlersRef.current[answerId] = answerRemoveHandler;
return answerRemoveHandler;
};
var getCategoryRemoveHandler = function(categoryId) {
var categoryRemoveHandler = categoryRemoveHandlersRef.current[categoryId];
if (categoryRemoveHandler) return categoryRemoveHandler;
categoryRemoveHandler = onRemoveCategory.bind(null, categoryId);
categoryRemoveHandlersRef.current[categoryId] = categoryRemoveHandler;
return categoryRemoveHandler;
};
var handleFooterRef = function(node) {
footerRef.current = node;
};
// ===========
// RENDER
// ===========
var renderAnswer = function(answer, categoryId, focusOnMount, index) {
var onRemoveAnswer = getAnswerRemoveHandler(answer.id, categoryId);
return /*#__PURE__*/ jsx(ChoiceInput, {
key: "answerInput".concat(answer.id),
ref: function(node) {
answerRefsRef.current[answer.id] = node;
},
disabledFields: disabled ? [
'answerInput'
] : [],
id: answer.id,
itemBody: answer.itemBody,
errors: distractorErrors(answer.id),
onInputChange: onDistractorInputChange,
onModalClose: onModalClose,
onModalOpen: onModalOpen,
onRemoveChoice: onRemoveAnswer,
shouldRenderRemoveChoice: !disabled,
focusOnMount: focusOnMount && isMountedRef.current,
noRCE: true,
notifyScreenreader: notifyScreenreader,
isRequired: true,
automationData: "sdk-category-".concat(index + 1, "-answer")
});
};
var renderRemoveButton = function(category, index) {
var onRemoveCategory = getCategoryRemoveHandler(category.id);
var singleCategory = sortedCategories.length === 1;
if (!singleCategory && !disabled) {
return /*#__PURE__*/ jsx(RemoveChoiceButton, {
choiceId: category.id,
screenReaderText: t('Remove Category {index}: {category}', {
index: index,
category: category.itemBody
}),
onRemoveChoice: onRemoveCategory,
automationData: "sdk-remove-category-".concat(category.id)
});
}
};
var renderCategory = function(category, index, focusOnMount) {
var onCreateAnswer = getCreateAnswerHandler(category.id);
var categoryAnswerInputRefs = function(node) {
categoryAnswerInputRefsRef.current[category.id] = node;
};
var categoryRef = function(node) {
categoryRefsRef.current[category.id] = node;
};
return /*#__PURE__*/ jsx("div", {
key: category.id,
className: "categoryContainer",
css: styles.categoryContainer
}, /*#__PURE__*/ jsx("div", {
css: styles.categoryContainerWrapper
}, /*#__PURE__*/ jsx("div", {
css: styles.itemContainer
}, /*#__PURE__*/ jsx("div", {
css: styles.itemContainerBody
}, /*#__PURE__*/ jsx(Text, {
color: "primary"
}, t('Category {position, number}', {
position: index + 1
}))), renderRemoveButton(category, index + 1)), /*#__PURE__*/ jsx("div", {
css: styles.categoryBody
}, /*#__PURE__*/ jsx("div", {
css: styles.categoryBodyDescriptionInput
}, /*#__PURE__*/ jsx(AnswerInput, {
ref: categoryAnswerInputRefs,
disabled: disabled,
onChangeHandler: onCategoryInputChange,
onModalClose: onModalClose,
onModalOpen: onModalOpen,
id: category.id,
itemBody: category.itemBody,
errors: getCategoryErrors(category.id),
renderLabel: t('Description'),
screenReaderText: t('Category Description'),
focusOnMount: focusOnMount && isMountedRef.current,
noRCE: true,
automationData: "sdk-category-".concat(index + 1, "-description"),
isRequired: true
})), /*#__PURE__*/ jsx(Flex, {
padding: "x-small",
direction: "column",
gap: "small"
}, categoryScoringData(category.id).map(function(answerId, answerIndex, source) {
var answer = distractors[answerId];
var answerFocusOnMount = answerIndex + 1 === source.length && !answer.itemBody;
return /*#__PURE__*/ jsx(Flex.Item, {
key: "answerInput".concat(answer.id)
}, renderAnswer(answer, category.id, answerFocusOnMount, answerIndex));
})), /*#__PURE__*/ jsx("div", {
css: styles.categoryBodyAnswers
}), !disabled && /*#__PURE__*/ jsx(Footer, {
onCreateChoice: onCreateAnswer,
ref: categoryRef,
screenReaderText: t('Add answer to category {position, number}', {
position: index + 1
}),
notifyScreenreader: notifyScreenreader,
automationData: "sdk-add-answer-to-category-".concat(index + 1, "-button")
}))));
};
return /*#__PURE__*/ jsx("div", {
css: styles.categoriesWrapper
}, /*#__PURE__*/ jsx(FormFieldGroup, {
description: t('Categories'),
messages: normalizeErrors(categoriesErrors)
}, /*#__PURE__*/ jsx("div", {
css: styles.categoriesContainer
}, sortedCategories.map(function(category, index, source) {
var focusOnMount = index + 1 === source.length && !category.itemBody;
return renderCategory(category, index, focusOnMount);
}))), !disabled && /*#__PURE__*/ jsx(Footer, {
ref: handleFooterRef,
onCreateChoice: onCreateCategory,
buttonText: t('Category'),
screenReaderText: t('Add Category'),
notifyScreenreader: notifyScreenreader,
automationData: "sdk-add-category-button"
}));
});
BaseCategoryForm.displayName = 'CategoryForm';
BaseCategoryForm.componentId = 'QuizzesCategoryForm';
BaseCategoryForm.propTypes = {
categoriesErrors: PropTypes.arrayOf(PropTypes.shape({
text: PropTypes.string,
type: PropTypes.string
})),
getCategoryErrors: PropTypes.func,
categoryScoringData: PropTypes.func,
disabled: PropTypes.bool,
distractors: PropTypes.objectOf(PropTypes.shape({
id: PropTypes.string.isRequired,
itemBody: PropTypes.string.isRequired
})).isRequired,
distractorErrors: PropTypes.func,
notifyScreenreader: PropTypes.func.isRequired,
onCategoryInputChange: PropTypes.func,
onCreateAnswer: PropTypes.func,
onCreateCategory: PropTypes.func,
onDistractorInputChange: PropTypes.func,
onModalClose: PropTypes.func,
onModalOpen: PropTypes.func,
onRemoveAnswer: PropTypes.func,
onRemoveCategory: PropTypes.func,
sortedCategories: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.string.isRequired,
itemBody: PropTypes.string
})).isRequired,
styles: PropTypes.object
};
BaseCategoryForm.defaultProps = {
categoriesErrors: [],
getCategoryErrors: void 0,
categoryScoringData: void 0,
disabled: void 0,
distractorErrors: void 0,
onCategoryInputChange: void 0,
onCreateAnswer: void 0,
onCreateCategory: void 0,
onDistractorInputChange: void 0,
onModalClose: void 0,
onModalOpen: void 0,
onRemoveAnswer: void 0,
onRemoveCategory: void 0
};
var CategoryForm = withStyleOverrides(generateStyle, generateComponentTheme)(BaseCategoryForm);
CategoryForm.displayName = 'CategoryForm';
CategoryForm.componentId = 'QuizzesCategoryForm';
export { BaseCategoryForm };
export default CategoryForm;