diff --git a/src/components/Card.tsx b/src/components/Card.tsx index 4c8afd54..e1d9641a 100644 --- a/src/components/Card.tsx +++ b/src/components/Card.tsx @@ -1,4 +1,4 @@ -import { ReactNode } from "react"; +import { ReactNode, useState, useRef, useEffect, useCallback } from "react"; import { breakpoints, colors, layouts, mixins } from "../theme"; import { AvailablePoints, StepBase, StepWithData } from "../types"; import styled from "styled-components"; @@ -194,6 +194,21 @@ const StepCardQuestion = styled.div<{ unpadded?: boolean }>` } `; +export const StyledOverlay = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 100%; + height: 100%; + background-color: #FFFFFF80; + z-index: 2; +`; + interface SharedProps { questionNumber: number; numberOfQuestions: number; @@ -212,6 +227,7 @@ export interface StepCardProps extends SharedProps { questionId?: string; multipartBadge?: ReactNode; isHomework: boolean; + overlayChildren?: React.ReactNode; } const StepCard = ({ @@ -229,35 +245,104 @@ const StepCard = ({ leftHeaderChildren, rightHeaderChildren, headerTitleChildren, + overlayChildren, ...otherProps }: StepCardProps) => { + // Helps to stop focusing first child when is already focused + const [previousFocusedElement, setPreviousFocusedElement] = useState(null); + const overlayRef = useRef(null); + const [showOverlay, setShowOverlay] = useState(false); + const formattedQuestionNumber = numberOfQuestions > 1 ? `Questions ${questionNumber} - ${questionNumber + numberOfQuestions - 1}` : `Question ${questionNumber}`; + const handleOverlayBlur = (event: React.FocusEvent) => { + if (overlayRef.current && !overlayRef.current.contains(event.relatedTarget as Node)) { + setShowOverlay(false); + } + }; + + const handleOverlayFocus = useCallback((event: FocusEvent) => { + setShowOverlay(true); + const firstOverlayFocusableElement = document.getElementById('overlay-element')?.querySelector( + 'button, [href], input, select, textarea' + ) as HTMLElement; + + if ( + (firstOverlayFocusableElement !== previousFocusedElement) && + (event.target === overlayRef.current) + ) { + setPreviousFocusedElement(firstOverlayFocusableElement); + firstOverlayFocusableElement.focus(); + } + }, [overlayRef, previousFocusedElement]); + + const hideFocusableElements = useCallback(() => { + const focusableElements = Array.from(document.getElementById("step-card")?.querySelectorAll( + 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' + ) || []); + + focusableElements.forEach((el) => { + (el as HTMLElement).setAttribute('tabindex', '-1'); + }); + }, []); + + useEffect(() => { + const currentOverlayRef = overlayRef.current; + if (currentOverlayRef && overlayChildren) { + currentOverlayRef.addEventListener('focus', handleOverlayFocus); + hideFocusableElements(); + } + return () => { + currentOverlayRef?.removeEventListener('focus', handleOverlayFocus); + }; + }, [overlayChildren, overlayRef, handleOverlayFocus, hideFocusableElements]); + return ( {multipartBadge} - {questionNumber && isHomework && stepType === 'exercise' && - -
- {leftHeaderChildren} -

- {headerTitleChildren} - {formattedQuestionNumber} - {showTotalQuestions ?  / {numberOfQuestions} : null} - | - ID: {questionId} -

-
- {availablePoints || rightHeaderChildren ?
- {availablePoints &&
{availablePoints} Points
} - {rightHeaderChildren} -
: null} -
- } - {children} +
setShowOverlay(true), + onMouseLeave: () => setShowOverlay(false), + onBlur: handleOverlayBlur, + tabIndex: 0, + } + : {}) + } + > + {(overlayChildren && showOverlay) && + + {overlayChildren} + + } +
+ {questionNumber && isHomework && stepType === 'exercise' && + +
+ {leftHeaderChildren} +

+ {headerTitleChildren} + {formattedQuestionNumber} + {showTotalQuestions ?  / {numberOfQuestions} : null} + | + ID: {questionId} +

+
+ {availablePoints || rightHeaderChildren ?
+ {availablePoints &&
{availablePoints} Points
} + {rightHeaderChildren} +
: null} +
+ } + {children} +
+
) @@ -267,9 +352,11 @@ StepCard.displayName = 'OSStepCard'; export interface TaskStepCardProps extends SharedProps { className?: string; children?: ReactNode; + tabIndex?: number; step: StepBase | StepWithData; questionNumber: number; numberOfQuestions: number; + overlayChildren?: React.ReactNode; } const TaskStepCard = ({ @@ -278,6 +365,7 @@ const TaskStepCard = ({ numberOfQuestions, children, className, + overlayChildren, ...otherProps }: TaskStepCardProps) => ( {children} ); diff --git a/src/components/Exercise.spec.tsx b/src/components/Exercise.spec.tsx index aff0bcdd..42a89bb8 100644 --- a/src/components/Exercise.spec.tsx +++ b/src/components/Exercise.spec.tsx @@ -1,4 +1,4 @@ -import { Exercise, ExerciseWithStepDataProps, ExerciseWithQuestionStatesProps } from './Exercise'; +import { Exercise, ExerciseWithStepDataProps, ExerciseWithQuestionStatesProps, OverlayProps } from './Exercise'; import renderer from 'react-test-renderer'; import React from 'react'; @@ -299,4 +299,82 @@ describe('Exercise', () => { expect(tree.toJSON()).toMatchSnapshot(); }); }); + + describe('with overlay rendering', () => { + + let props: ExerciseWithStepDataProps & OverlayProps; + + beforeEach(() => { + props = { + overlayChildren: Overlay, + exercise: { + uid: '1@1', + uuid: 'e4e27897-4abc-40d3-8565-5def31795edc', + group_uuid: '20e82bf6-232e-40c8-ba68-2d22c6498f69', + number: 1, + version: 1, + published_at: '2022-09-06T20:32:21.981Z', + context: 'Context', + stimulus_html: 'Stimulus HTML', + tags: [], + authors: [{ user_id: 1, name: 'OpenStax' }], + copyright_holders: [{ user_id: 1, name: 'OpenStax' }], + derived_from: [], + is_vocab: false, + solutions_are_public: false, + versions: [1], + questions: [{ + id: '1234@5', + collaborator_solutions: [], + formats: ['true-false'], + stimulus_html: '', + stem_html: '', + is_answer_order_important: false, + answers: [{ + id: '1', + correctness: undefined, + content_html: 'True', + }, { + id: '2', + correctness: undefined, + content_html: 'False', + }], + }], + }, + questionNumber: 1, + hasMultipleAttempts: false, + onAnswerChange: () => null, + onAnswerSave: () => null, + onNextStep: () => null, + canAnswer: false, + needsSaved: false, + apiIsPending: false, + canUpdateCurrentStep: false, + step: { + uid: '1234@4', + id: 1, + available_points: '1.0', + is_completed: false, + answer_id_order: ['1', '2'], + answer_id: '1', + free_response: '', + feedback_html: '', + correct_answer_id: '', + correct_answer_feedback_html: '', + is_feedback_available: true, + attempts_remaining: 0, + attempt_number: 1, + incorrectAnswerId: 0 + }, + numberOfQuestions: 1 + } + }); + + it('matches snapshot', () => { + const tree = renderer.create( + + ).toJSON(); + expect(tree).toMatchSnapshot(); + }); + }); }); diff --git a/src/components/Exercise.stories.tsx b/src/components/Exercise.stories.tsx index 06281507..fd0592dc 100644 --- a/src/components/Exercise.stories.tsx +++ b/src/components/Exercise.stories.tsx @@ -5,6 +5,7 @@ import { ExerciseWithQuestionStatesProps, } from './Exercise'; import { Answer } from '../types'; +import { IncludeRemoveQuestion } from './IncludeRemoveQuestion'; import styled from 'styled-components'; const exerciseWithStepDataProps: ExerciseWithStepDataProps = { @@ -144,6 +145,12 @@ const exerciseWithQuestionStatesProps = (): ExerciseWithQuestionStatesProps => { } }; +const exerciseWithOverlayProps = (overlayChildren: React.ReactNode) => { + return { + overlayChildren, + }; + } + type TextResizerValue = -2 | -1 | 0 | 1 | 2 | 3; const textResizerScales = [0.75, 0.9, 1, 1.25, 1.5, 2]; const textResizerValues: TextResizerValue[] = [-2, -1, 0, 1, 2, 3]; @@ -522,7 +529,6 @@ export const MathJax = () => { const [correctAnswerId, setCorrectAnswerId] = useState( undefined, ); - const props1: ExerciseWithQuestionStatesProps = { ...exerciseWithQuestionStatesProps(), questionStates: { @@ -807,3 +813,145 @@ export const PreviewCard = () => { ); }; + +export const OverlayCard = () => { + const randomlyCorrectAnswer = Math.floor(Math.random() * 3) + 1; + const props1: ExerciseWithQuestionStatesProps = { + ...exerciseWithQuestionStatesProps(), + ...exerciseWithOverlayProps(), + questionStates: { + '1': { + available_points: '1.0', + is_completed: true, + answer_id_order: ['1', '2', '3', '4'], + answer_id: randomlyCorrectAnswer, + free_response: '', + feedback_html: '', + correct_answer_id: randomlyCorrectAnswer.toString(), + correct_answer_feedback_html: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.', + attempts_remaining: 0, + attempt_number: 0, + incorrectAnswerId: 0, + canAnswer: false, + needsSaved: false, + apiIsPending: false, + solution: { + content_html: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', + solution_type: 'detailed', + }, + }, + }, + exercise: { + tags: [ + 'book:stax-anp', + 'context-cnxmod:bbaedbf4-4d78-4b7c-bc94-2a742f0f2f8c', + 'lo:stax-anp:22-3-6', + 'exid:stax-anp:1786', + 'dok:1', + 'time:short', + 'book-slug:anatomy-and-physiology', + 'book-slug:anatomy-and-physiology-2e', + 'module-slug:anatomy-and-physiology:22-3-the-process-of-breathing', + 'module-slug:anatomy-and-physiology-2e:22-3-the-process-of-breathing', + 'assignment-type:reading', + 'blooms:1', + 'ost-type:concept-coach', + 'assessment:preparedness:https://openstax.org/orn/book:page/4fd99458-6fdf-49bc-8688-a6dc17a1268d:11673dd9-55e6-46d9-8b78-b06df85246bd', + ], + uuid: '8ae2b252-8943-4a1a-a123-2e6d9eeef4p5', + group_uuid: '19bd7035-d50b-42d8-8f8c-a4d72588a7aa', + number: 3030, + version: 4, + uid: '3030@4', + published_at: '2022-15-05T21:24:12.207Z', + solutions_are_public: false, + authors: [ + { + user_id: 1, + name: 'OpenStax Exercises', + }, + ], + copyright_holders: [ + { + user_id: 2, + name: 'Rice University', + }, + ], + derived_from: [], + is_vocab: false, + stimulus_html: '', + questions: [ + { + id: 320733, + is_answer_order_important: true, + stimulus_html: '', + stem_html: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.', + answers: [ + { + id: 832312, + content_html: 'Option 1', + }, + { + id: 832313, + content_html: 'Option 2', + }, + { + id: 832314, + content_html: 'Option 3', + }, + { + id: 832315, + content_html: 'Option 4', + }, + ], + hints: [], + formats: ['free-response', 'multiple-choice'], + combo_choices: [], + }, + ], + + versions: [4, 3, 2, 1], + }, + }; + + const [buttonVariant, setButtonVariant] = React.useState<'include' | 'remove'>('include'); + + const props2: ExerciseWithQuestionStatesProps = { + ...exerciseWithQuestionStatesProps(), + ...exerciseWithOverlayProps( + setButtonVariant('remove')} + onRemoveHandler={() => setButtonVariant('include')} + /> + ), + questionStates: { + '1': { + available_points: '1.0', + is_completed: true, + answer_id_order: ['1', '2'], + answer_id: undefined, + free_response: 'Free response', + feedback_html: 'Feedback', + correct_answer_id: '1', + correct_answer_feedback_html: 'Feedback for the correct answer', + attempts_remaining: 0, + attempt_number: 1, + incorrectAnswerId: 0, + canAnswer: false, + needsSaved: false, + apiIsPending: false, + }, + }, + }; + + return ( + + + + + ); +}; diff --git a/src/components/Exercise/index.tsx b/src/components/Exercise/index.tsx index afa3ea8f..45d3c542 100644 --- a/src/components/Exercise/index.tsx +++ b/src/components/Exercise/index.tsx @@ -59,14 +59,17 @@ const TaskStepCardWithToolbar = (props: React.PropsWithChildren & { desktopToolbarEnabled: boolean; mobileToolbarEnabled: boolean; + overlayChildren?: React.ReactNode; } -) => - - - ; +) => ( + + + + +); const Preamble = ({ exercise }: { exercise: ExerciseData }) => { return ( @@ -159,9 +162,21 @@ export interface ExerciseWithQuestionStatesProps extends ExerciseBaseProps { onAnswerChange: (answer: Omit & { id: number, question_id: number }) => void; } +export interface OverlayProps { + overlayChildren?: React.ReactNode; +} + export const Exercise = styled(({ - numberOfQuestions, questionNumber, step, exercise, show_all_feedback, scrollToQuestion, exerciseIcons, ...props -}: { className?: string } & (ExerciseWithStepDataProps | ExerciseWithQuestionStatesProps)) => { + numberOfQuestions, + questionNumber, + step, + exercise, + show_all_feedback, + scrollToQuestion, + exerciseIcons, + overlayChildren, + ...props +}: { className?: string } & (ExerciseWithStepDataProps | ExerciseWithQuestionStatesProps) & OverlayProps) => { const legacyStepRender = 'feedback_html' in step; const questionsRef = React.useRef>([]); const container = React.useRef(null); @@ -194,8 +209,9 @@ export const Exercise = styled(({ mobileToolbarEnabled={mobileToolbarEnabled} {...(exerciseIcons ? { exerciseIcons: exerciseIcons } : null)} className={props.className} + overlayChildren={overlayChildren} > -
+
{exercise.questions.map((q, i) => { @@ -203,7 +219,7 @@ export const Exercise = styled(({ return ( questionsRef.current[questionNumber + i] = el} exercise_uid={exercise.uid} key={q.id} diff --git a/src/components/IncludeRemoveQuestion.spec.tsx b/src/components/IncludeRemoveQuestion.spec.tsx new file mode 100644 index 00000000..40c539ba --- /dev/null +++ b/src/components/IncludeRemoveQuestion.spec.tsx @@ -0,0 +1,23 @@ +import renderer from 'react-test-renderer'; +import { IncludeRemoveQuestion } from './IncludeRemoveQuestion/index'; + +describe('IncludeRemoveQuestion', () => { + it.each` + buttonVariant + ${'include'} + ${'remove'} + `('matches snapshot', ({ buttonVariant }: { buttonVariant: 'include' | 'remove' }) => { + const mockIncludeHandler = jest.fn(); + const mockRemoveHandler = jest.fn(); + const component = renderer.create( + + ); + + renderer.act(() => { + component.root.findAllByType('button')[0].props.onClick(); + }); + + expect(buttonVariant === 'include' ? mockIncludeHandler : mockRemoveHandler).toHaveBeenCalled(); + expect(component).toMatchSnapshot(); + }); +}); diff --git a/src/components/IncludeRemoveQuestion.stories.tsx b/src/components/IncludeRemoveQuestion.stories.tsx new file mode 100644 index 00000000..af8bfe21 --- /dev/null +++ b/src/components/IncludeRemoveQuestion.stories.tsx @@ -0,0 +1,13 @@ +import React from "react"; +import { IncludeRemoveQuestion } from "./IncludeRemoveQuestion"; + +export const Default = () => { + const [buttonVariant, setButtonVariant] = React.useState<'include' | 'remove'>('include'); + return ( + setButtonVariant('remove')} + onRemoveHandler={() => setButtonVariant('include')} + /> + ); +}; \ No newline at end of file diff --git a/src/components/IncludeRemoveQuestion/index.tsx b/src/components/IncludeRemoveQuestion/index.tsx new file mode 100644 index 00000000..3a7b104b --- /dev/null +++ b/src/components/IncludeRemoveQuestion/index.tsx @@ -0,0 +1,46 @@ +import React from "react"; +import { faPlus, faMinus, faEllipsisH } from "@fortawesome/free-solid-svg-icons"; +import { StyledContainer, StyledButton, StyledIcon } from "./styles"; + +export interface IncludeRemoveQuestionProps { + // Prop that defines the variant of the button + buttonVariant: 'include' | 'remove'; + // Method invoked when the include button is clicked + onIncludeHandler: () => void; + // Method invoked when the remove button is clicked + onRemoveHandler: () => void; +} + +export const IncludeRemoveQuestion = ({buttonVariant, onIncludeHandler, onRemoveHandler }: IncludeRemoveQuestionProps) => { + + const buttonIcon = React.useMemo(() => buttonVariant === 'include' ? faPlus : faMinus, [buttonVariant]); + const onClickHandler = (variant: 'include' | 'remove') => { + switch (variant) { + case 'include': + onIncludeHandler(); + break; + case 'remove': + onRemoveHandler(); + break; + default: + break; + } + }; + + const generateButtonText = (string: string) => { + return string.charAt(0).toUpperCase() + string.slice(1) + ' question'; + }; + + return ( + + onClickHandler(buttonVariant)} aria-label={buttonVariant}> + + {generateButtonText(buttonVariant)} + + + + Details + + + ); +}; \ No newline at end of file diff --git a/src/components/IncludeRemoveQuestion/styles.ts b/src/components/IncludeRemoveQuestion/styles.ts new file mode 100644 index 00000000..7038e278 --- /dev/null +++ b/src/components/IncludeRemoveQuestion/styles.ts @@ -0,0 +1,63 @@ +import { colors } from '../../theme'; +import styled from "styled-components"; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; + + +export const StyledContainer = styled.div` + display: flex; + width: fit-content; +`; + +export const StyledButton = styled.button` + width: 7.4rem; + height: 8.7rem; + color: white; + border: none; + + &:hover { + box-shadow: 0 0.1rem 0.4rem 0 #00000066; + } + + span { + line-height: 1.3rem; + font-size: 1.4rem; + font-weight: bold; + } + + &.include { + background-color: ${colors.palette.mediumBlue}; + padding-top: 1.4rem; + } + + &.remove { + background-color: ${colors.palette.orange}; + padding-top: 1.4rem; + } + + &.details { + background-color: ${colors.palette.neutralDarker}; + } +`; + +export const StyledIcon = styled(FontAwesomeIcon)` + border-radius: 50%; + background-color: ${colors.palette.white}; + vertical-align: middle; + position: relative; + bottom: 0.5rem; + font-weight: 900; + line-height: 1.4rem; + + + &.include { + color: ${colors.palette.mediumBlue}; + } + + &.remove { + color: ${colors.palette.orange}; + } + + &.details { + color: ${colors.palette.neutralDarker}; + } +`; diff --git a/src/components/__snapshots__/Card.spec.tsx.snap b/src/components/__snapshots__/Card.spec.tsx.snap index c2731f0f..1882d238 100644 --- a/src/components/__snapshots__/Card.spec.tsx.snap +++ b/src/components/__snapshots__/Card.spec.tsx.snap @@ -7,42 +7,48 @@ exports[`StepCard matches snapshot 1`] = `
-
-
-

+
+
- - Question 1 - - - | - - - ID: - -

-
-
+
+

+ + Question 1 + + + | + + + ID: + +

+
+
+
+ 1.0 + Points +
+
+
- 1.0 - Points + Question content
-
- Question content -
`; @@ -54,48 +60,54 @@ exports[`StepCard matches snapshot with more than one question 1`] = `
-
-
-

+
+
- - Questions 1 - 3 - - -  / - 3 - - - | - - - ID: - -

-
-
+
+

+ + Questions 1 - 3 + + +  / + 3 + + + | + + + ID: + +

+
+
+
+ 1.0 + Points +
+
+
- 1.0 - Points + Question content
-
- Question content -
`; @@ -108,33 +120,39 @@ exports[`TaskStepCard can optionally provide task 1`] = `
-
-
-

+
+
- - Question 1 - - - | - - - ID: - 1234@1 - -

+
+

+ + Question 1 + + + | + + + ID: + 1234@1 + +

+
+
+
-
`; @@ -147,33 +165,39 @@ exports[`TaskStepCard can optionally provide type 1`] = `
-
-
-

+
+
- - Question 1 - - - | - - - ID: - 1234@1 - -

+
+

+ + Question 1 + + + | + + + ID: + 1234@1 + +

+
+
+
-
`; @@ -186,33 +210,39 @@ exports[`TaskStepCard matches snapshot 1`] = `
-
-
-

+
+
- - Question 1 - - - | - - - ID: - 1234@1 - -

+
+

+ + Question 1 + + + | + + + ID: + 1234@1 + +

+
+
+
-
`; diff --git a/src/components/__snapshots__/CompletionStatus.spec.tsx.snap b/src/components/__snapshots__/CompletionStatus.spec.tsx.snap index 00c3d301..2f19115d 100644 --- a/src/components/__snapshots__/CompletionStatus.spec.tsx.snap +++ b/src/components/__snapshots__/CompletionStatus.spec.tsx.snap @@ -2,10 +2,10 @@ exports[`CompletionStatus matches snapshot 1`] = `

No questions have been answered.

@@ -13,7 +13,7 @@ exports[`CompletionStatus matches snapshot 1`] = ` Begin working on the quiz.

- - - - - -
-
-
- -
-
- + Next + +
+
@@ -191,180 +197,388 @@ exports[`Exercise using step data matches snapshot 1`] = ` `; -exports[`Exercise with question state data matches snapshot 1`] = ` +exports[`Exercise with overlay rendering matches snapshot 1`] = `
-
-

- - Question 1 - - - | - - - ID: - 1234@5 - -

-
-
-
-
+
-
Stimulus HTML", - } - } - /> + className="sc-dkzDqf kooVfX step-card-header" + > +
+

+ + Question 1 + + +  / + 1 + + + | + + + ID: + 1234@4 + +

+
+
-
+
+
Stimulus HTML", + } + } + /> +
-
- - -
+ + +
+
+
+ + +
+
+
-
- -
+ Next + +
+
-
+
+
+
+
+
+
+`; + +exports[`Exercise with question state data matches snapshot 1`] = ` +
+
+
+
+
+
+
+

+ + Question 1 + + + | + + + ID: + 1234@5 + +

+
+
+
+
+
Stimulus HTML", + } + } + /> +
- +
+
+
+ + +
+
+
+
+ + +
+
+
- +
+ +
+
+ +
+
@@ -378,10 +592,10 @@ exports[`Exercise with question state data matches snapshot 1`] = ` exports[`Exercise with question state data renders header icons with multiple choice explanation 1`] = `
-
-
-

- - Question 1 - - - | - - - ID: - 1234@5 - -

-
-
+
+
-
-
-
-
Stimulus HTML", - } - } - /> -
-
-
-
-
- -
-
-
+
+ + -
- -
-
+
+
+
-
+
+
+
+
Stimulus HTML", + } + } + /> +
- +
+
+
+ + +
+
+
+
+ + +
+
+
- +
+ +
+
+ +
+
@@ -703,10 +923,10 @@ exports[`Exercise with question state data renders header icons with multiple ch exports[`Exercise with question state data renders header icons with two-step explanation 1`] = `
-
-
-

- - Question 1 - - - | - - - ID: - 1234@5 - -

-
-
+
+
- - +
- -
-
- Suggest a correction + +
+
+
+ View topic in textbook +
+
-
-
-
-
-
- +
+ +
+
+
+ Suggest a correction +
+
+
+
-
- In a two-step question, OpenStax asks for your own answer first, then gives multiple-choice options to help you assess your learnings. Recalling the answer to a question from memory helps you to retain things longer. + +
+
+
+ In a two-step question, OpenStax asks for your own answer first, then gives multiple-choice options to help you assess your learnings. Recalling the answer to a question from memory helps you to retain things longer. +
+
-
-
-
-
-
Stimulus HTML", - } - } - /> -
-
+
+
Stimulus HTML", + } + } + /> +
-
- - -
+ + +
+
+
+ + +
+
+
-
- -
-
-
-
-
-
-
- -
-
- + Next + +
+
@@ -1068,192 +1294,198 @@ exports[`Exercise with question state data renders header icons with two-step ex exports[`Exercise with question state data shows a detailed solution 1`] = `
-
-
-

- - Question 1 - - - | - - - ID: - 1234@5 - -

-
-
-
-
-
+
+
Stimulus HTML", - } - } - /> + className="sc-dkzDqf kooVfX step-card-header" + > +
+

+ + Question 1 + + + | + + + ID: + 1234@5 + +

+
+
-
+
+
+
Stimulus HTML", + } + } + />
-
- - -
+ + +
+
+
+ + +
+
+
-
- -
-
-
-
-
-
-
- -
- - Detailed solution: - - - +
+
+ +
-
- -
diff --git a/src/components/__snapshots__/IncludeRemoveQuestion.spec.tsx.snap b/src/components/__snapshots__/IncludeRemoveQuestion.spec.tsx.snap new file mode 100644 index 00000000..e1d396ac --- /dev/null +++ b/src/components/__snapshots__/IncludeRemoveQuestion.spec.tsx.snap @@ -0,0 +1,119 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`IncludeRemoveQuestion matches snapshot 1`] = ` +
+ + +
+`; + +exports[`IncludeRemoveQuestion matches snapshot 2`] = ` +
+ + +
+`;