diff --git a/src/components/Card.tsx b/src/components/Card.tsx index e1d9641a..dc98fc7d 100644 --- a/src/components/Card.tsx +++ b/src/components/Card.tsx @@ -87,9 +87,6 @@ const StepCardHeader = styled.div` button.ox-icon-angle-left, button.ox-icon-angle-right { display: none; } - .separator { - display: inherit; - } `} /* diff --git a/src/components/Exercise.stories.tsx b/src/components/Exercise.stories.tsx index fd0592dc..9dc26d21 100644 --- a/src/components/Exercise.stories.tsx +++ b/src/components/Exercise.stories.tsx @@ -7,6 +7,7 @@ import { import { Answer } from '../types'; import { IncludeRemoveQuestion } from './IncludeRemoveQuestion'; import styled from 'styled-components'; +import { ExercisePreview } from './ExercisePreview'; const exerciseWithStepDataProps: ExerciseWithStepDataProps = { exercise: { @@ -76,10 +77,10 @@ const exerciseWithStepDataProps: ExerciseWithStepDataProps = { canUpdateCurrentStep: false, }; -const exerciseWithQuestionStatesProps = (): ExerciseWithQuestionStatesProps => { +const exerciseWithQuestionStatesProps = (uid?: string): ExerciseWithQuestionStatesProps => { return { exercise: { - uid: '1@1', + uid: uid || '1@1', uuid: 'e4e27897-4abc-40d3-8565-5def31795edc', group_uuid: '20e82bf6-232e-40c8-ba68-2d22c6498f69', number: 1, @@ -817,7 +818,7 @@ export const PreviewCard = () => { export const OverlayCard = () => { const randomlyCorrectAnswer = Math.floor(Math.random() * 3) + 1; const props1: ExerciseWithQuestionStatesProps = { - ...exerciseWithQuestionStatesProps(), + ...exerciseWithQuestionStatesProps('1@123'), ...exerciseWithOverlayProps(), questionStates: { '1': { @@ -920,7 +921,7 @@ export const OverlayCard = () => { const [buttonVariant, setButtonVariant] = React.useState<'include' | 'remove'>('include'); const props2: ExerciseWithQuestionStatesProps = { - ...exerciseWithQuestionStatesProps(), + ...exerciseWithQuestionStatesProps('1@321'), ...exerciseWithOverlayProps( { }, }; + const [selectedQuestions, setSelectedQuestions] = useState([]); + const [showDetails1, setShowDetails1] = useState(false); + const [showDetails2, setShowDetails2] = useState(false); + + const includeHandler = (exerciseUid: string) => setSelectedQuestions(previous => previous.concat(exerciseUid)); + const removeHandler = (exerciseUid: string) => setSelectedQuestions(previous => previous.filter((id) => id !== exerciseUid)); + return ( +

Exercise cards

+

Exercise Preview cards

+ {showDetails1 &&

Details 1!

} + includeHandler(props1.exercise.uid)} + onRemoveHandler={()=> removeHandler(props1.exercise.uid)} + onClickDetails={()=> setShowDetails1((previous) => !previous)} + enableOverlay + exercise={props1.exercise} + /> + {showDetails2 &&

Details 2!

} + includeHandler(props2.exercise.uid)} + onRemoveHandler={()=> removeHandler(props2.exercise.uid)} + onClickDetails={()=> setShowDetails2((previous) => !previous)} + enableOverlay + exercise={props2.exercise} + />
); }; diff --git a/src/components/Exercise/styles.ts b/src/components/Exercise/styles.ts index 48e17bb4..adc1f803 100644 --- a/src/components/Exercise/styles.ts +++ b/src/components/Exercise/styles.ts @@ -1,11 +1,25 @@ +import { colors } from "../../theme"; import { css } from "styled-components"; export const exerciseStyles = css` + + &.is-selected { + background-color: ${colors.card.header.background}; + + .step-card-footer-inner, + .step-card-body, + .step-card-header, + .answer-letter-wrapper::before { + background-color: ${colors.card.header.background} !important; + } + } + &.preview-card { --spacing: 0.8rem; .step-card-header, .step-card-body { + background-color: ${colors.palette.white}; padding: var(--spacing); font-size: 1.6rem; line-height: 2rem; @@ -44,6 +58,7 @@ export const exerciseStyles = css` .openstax-question { .openstax-answer { padding: 0; + border: none; .answer-label { padding-top: var(--spacing); @@ -53,6 +68,13 @@ export const exerciseStyles = css` .answer-answer { margin-left: var(--spacing); } + + &::before { + min-width: 2.3rem; + min-height: 2.3rem; + width: 2.3rem; + height: 2.3rem; + } } .answer-letter { @@ -74,13 +96,31 @@ export const exerciseStyles = css` } } - .step-card-footer { + .step-card-footer, .detailed-solution { display: none; } .question-stem, - .question-feedback-content { + .question-feedback-content, + .question-info, + .exercise-context { line-height: 2rem; } + + .question-info { + font-weight: bold; + font-size: 1.2rem; + } + + .question-id { + font-weight: 400; + font-size: 1.2rem; + } + + .question-stem { + color: ${colors.palette.neutralDarker}; + font-weight: bold; + font-size: 1.6rem; + } } `; diff --git a/src/components/ExercisePreview.spec.tsx b/src/components/ExercisePreview.spec.tsx new file mode 100644 index 00000000..edb6c7ef --- /dev/null +++ b/src/components/ExercisePreview.spec.tsx @@ -0,0 +1,69 @@ +import { ExercisePreview } from './ExercisePreview'; +import renderer from 'react-test-renderer'; +import { ExerciseData } from 'src/types'; + +describe('ExercisePreview', () => { + describe('using step data', () => { + + let exercise: ExerciseData; + + beforeEach(() => { + 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', + }], + }], + }; + }); + + it.each` + enableOverlay | selected | description + ${true} | ${true} | ${'with overlay and selected true'} + ${true} | ${true} | ${'with overlay and selected false'} + ${false} | ${false} | ${'without overlay'} + `('matches snapshot %description', ({ enableOverlay, selected }: { enableOverlay: boolean, selected: boolean }) => { + const onIncludeMock = jest.fn(); + const onRemoveMock = jest.fn(); + const onDetailsMock = jest.fn(); + const tree = renderer.create( + + ).toJSON(); + expect(tree).toMatchSnapshot(); + }); + }); +}); diff --git a/src/components/ExercisePreview.tsx b/src/components/ExercisePreview.tsx new file mode 100644 index 00000000..b75bed2a --- /dev/null +++ b/src/components/ExercisePreview.tsx @@ -0,0 +1,95 @@ +import React from "react"; +import { ExerciseData, ExerciseQuestionData, StepBase } from "src/types"; +import { IncludeRemoveQuestion } from "./IncludeRemoveQuestion"; +import { Exercise } from "./Exercise"; + +export interface ExercisePreviewProps { + exercise: ExerciseData; + selected: boolean; + onIncludeHandler: () => void; + onRemoveHandler: () => void; + onClickDetails: () => void; + enableOverlay?: boolean; +} + +/** + * An Exercise version with less interaction with card and grants an Overlay with Include/Remove component + */ +export const ExercisePreview = ( + { + exercise, + selected, + onIncludeHandler, + onRemoveHandler, + onClickDetails, + enableOverlay = false, + }: ExercisePreviewProps) => { + + const exercisePreviewProps = (exercise: ExerciseData) => { + const formatAnswerData = (questions: ExerciseQuestionData[]) => questions.map((q) => ( + { id: q.id, correct_answer_id: (q.answers.find((a) => a.correctness === '1.0')?.id || '') })); + + const questionStateFields = { + available_points: '1.0', + is_completed: true, + answer_id: '1', + free_response: '', + feedback_html: '', + correct_answer_feedback_html: '', + attempts_remaining: 0, + attempt_number: 1, + incorrectAnswerId: 0 + } + + const questionStates = formatAnswerData(exercise.questions).reduce((acc, answer) => { + const { id, correct_answer_id } = answer; + return { ...acc, [id]: { ...questionStateFields, correct_answer_id } }; + }, {}); + + const step: StepBase = { + id: 1, + uid: exercise.uid, + available_points: '1.0', + }; + + return { + canAnswer: true, + needsSaved: true, + hasMultipleAttempts: false, + onAnswerChange: () => undefined, + onAnswerSave: () => undefined, + onNextStep: () => undefined, + apiIsPending: false, + canUpdateCurrentStep: false, + step: step, + questionNumber: exercise.number as number, + numberOfQuestions: exercise.questions.length, + questionStates: questionStates, + show_all_feedback: false, // Hide all feedback + }; + }; + + const includeRemoveQuestionComponent = React.useMemo(() => + + , [selected, onIncludeHandler, onRemoveHandler, onClickDetails]); + + return ( + + ); +}; \ No newline at end of file diff --git a/src/components/IncludeRemoveQuestion.spec.tsx b/src/components/IncludeRemoveQuestion.spec.tsx index 40c539ba..aa5794dd 100644 --- a/src/components/IncludeRemoveQuestion.spec.tsx +++ b/src/components/IncludeRemoveQuestion.spec.tsx @@ -9,15 +9,23 @@ describe('IncludeRemoveQuestion', () => { `('matches snapshot', ({ buttonVariant }: { buttonVariant: 'include' | 'remove' }) => { const mockIncludeHandler = jest.fn(); const mockRemoveHandler = jest.fn(); + const mockOnClickDetails = jest.fn(); const component = renderer.create( - + ); renderer.act(() => { component.root.findAllByType('button')[0].props.onClick(); + component.root.findAllByType('button')[1].props.onClick(); }); expect(buttonVariant === 'include' ? mockIncludeHandler : mockRemoveHandler).toHaveBeenCalled(); + expect(mockOnClickDetails).toHaveBeenCalled(); expect(component).toMatchSnapshot(); }); }); diff --git a/src/components/IncludeRemoveQuestion/index.tsx b/src/components/IncludeRemoveQuestion/index.tsx index 3a7b104b..e05e790a 100644 --- a/src/components/IncludeRemoveQuestion/index.tsx +++ b/src/components/IncludeRemoveQuestion/index.tsx @@ -7,40 +7,43 @@ export interface IncludeRemoveQuestionProps { buttonVariant: 'include' | 'remove'; // Method invoked when the include button is clicked onIncludeHandler: () => void; - // Method invoked when the remove button is clicked + // Method invoked when the remove button is clicked onRemoveHandler: () => void; + // Method invoked when the details button is pressed + onClickDetails?: () => void; } -export const IncludeRemoveQuestion = ({buttonVariant, onIncludeHandler, onRemoveHandler }: IncludeRemoveQuestionProps) => { +export const IncludeRemoveQuestion = ( + { buttonVariant, onIncludeHandler, onRemoveHandler, onClickDetails }: 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 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 - - - ); + 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/__snapshots__/Card.spec.tsx.snap b/src/components/__snapshots__/Card.spec.tsx.snap index 1882d238..722a4364 100644 --- a/src/components/__snapshots__/Card.spec.tsx.snap +++ b/src/components/__snapshots__/Card.spec.tsx.snap @@ -12,7 +12,7 @@ exports[`StepCard matches snapshot 1`] = ` id="step-card" >

@@ -206,7 +206,7 @@ exports[`Exercise with overlay rendering matches snapshot 1`] = ` data-task-step-id={1} >

@@ -408,14 +408,14 @@ exports[`Exercise with question state data matches snapshot 1`] = ` data-task-step-id={1} >

@@ -653,14 +653,14 @@ exports[`Exercise with question state data renders header icons with multiple ch data-task-step-id={1} >

@@ -984,14 +984,14 @@ exports[`Exercise with question state data renders header icons with two-step ex data-task-step-id={1} >

@@ -1301,14 +1301,14 @@ exports[`Exercise with question state data shows a detailed solution 1`] = ` data-task-step-id={1} >

diff --git a/src/components/__snapshots__/ExercisePreview.spec.tsx.snap b/src/components/__snapshots__/ExercisePreview.spec.tsx.snap new file mode 100644 index 00000000..8d995569 --- /dev/null +++ b/src/components/__snapshots__/ExercisePreview.spec.tsx.snap @@ -0,0 +1,587 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ExercisePreview using step data matches snapshot %description 1`] = ` +
+
+
+
+
+
+
+

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

+
+
+
+
+
+
Stimulus HTML", + } + } + /> +
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+`; + +exports[`ExercisePreview using step data matches snapshot %description 2`] = ` +
+
+
+
+
+
+
+

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

+
+
+
+
+
+
Stimulus HTML", + } + } + /> +
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+`; + +exports[`ExercisePreview using step data matches snapshot %description 3`] = ` +
+
+
+
+
+
+
+

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

+
+
+
+
+
+
Stimulus HTML", + } + } + /> +
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+`; diff --git a/src/components/__snapshots__/ExerciseQuestion.spec.tsx.snap b/src/components/__snapshots__/ExerciseQuestion.spec.tsx.snap index 4e23af6f..8f6ff4db 100644 --- a/src/components/__snapshots__/ExerciseQuestion.spec.tsx.snap +++ b/src/components/__snapshots__/ExerciseQuestion.spec.tsx.snap @@ -5,7 +5,7 @@ exports[`ExerciseQuestion matches snapshot 1`] = ` data-test-id="student-exercise-question" >
@@ -145,7 +145,7 @@ exports[`ExerciseQuestion renders Re-submit button 1`] = ` data-test-id="student-exercise-question" >
@@ -292,7 +292,7 @@ exports[`ExerciseQuestion renders Save button 1`] = ` data-test-id="student-exercise-question" >
@@ -439,7 +439,7 @@ exports[`ExerciseQuestion renders Submit & continue button 1`] = ` data-test-id="student-exercise-question" >
@@ -586,7 +586,7 @@ exports[`ExerciseQuestion renders all attempts remaining 1`] = ` data-test-id="student-exercise-question" >
@@ -733,7 +733,7 @@ exports[`ExerciseQuestion renders continue button (unused?) 1`] = ` data-test-id="student-exercise-question" >
@@ -879,7 +879,7 @@ exports[`ExerciseQuestion renders detailed solution and published comments 1`] = data-test-id="student-exercise-question" >
@@ -1050,7 +1050,7 @@ exports[`ExerciseQuestion renders free response 1`] = ` data-test-id="student-exercise-question" >
@@ -1195,7 +1195,7 @@ exports[`ExerciseQuestion renders no attempts remaining 1`] = ` data-test-id="student-exercise-question" >
@@ -1341,7 +1341,7 @@ exports[`ExerciseQuestion renders some attempts remaining 1`] = ` data-test-id="student-exercise-question" >
diff --git a/src/components/__snapshots__/IncludeRemoveQuestion.spec.tsx.snap b/src/components/__snapshots__/IncludeRemoveQuestion.spec.tsx.snap index e1d396ac..3e3022ea 100644 --- a/src/components/__snapshots__/IncludeRemoveQuestion.spec.tsx.snap +++ b/src/components/__snapshots__/IncludeRemoveQuestion.spec.tsx.snap @@ -34,6 +34,19 @@ exports[`IncludeRemoveQuestion matches snapshot 1`] = `