Skip to content

Commit

Permalink
Modify preview-card styles (#88)
Browse files Browse the repository at this point in the history
* modify preview card styles

* change color of card when is included

* resolve comments - fix style issues

* change props in storybook of Exercise

* Add prop method for details button

* Refactor props of ExercisePreview
  • Loading branch information
jomcarvajal authored Jan 31, 2025
1 parent 1265cd5 commit 946c936
Show file tree
Hide file tree
Showing 14 changed files with 935 additions and 82 deletions.
3 changes: 0 additions & 3 deletions src/components/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,6 @@ const StepCardHeader = styled.div`
button.ox-icon-angle-left, button.ox-icon-angle-right {
display: none;
}
.separator {
display: inherit;
}
`}
/*
Expand Down
36 changes: 32 additions & 4 deletions src/components/Exercise.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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(<button>Overlay</button>),
questionStates: {
'1': {
Expand Down Expand Up @@ -920,7 +921,7 @@ export const OverlayCard = () => {
const [buttonVariant, setButtonVariant] = React.useState<'include' | 'remove'>('include');

const props2: ExerciseWithQuestionStatesProps = {
...exerciseWithQuestionStatesProps(),
...exerciseWithQuestionStatesProps('1@321'),
...exerciseWithOverlayProps(
<IncludeRemoveQuestion
buttonVariant={buttonVariant}
Expand Down Expand Up @@ -948,10 +949,37 @@ export const OverlayCard = () => {
},
};

const [selectedQuestions, setSelectedQuestions] = useState<string[]>([]);
const [showDetails1, setShowDetails1] = useState<boolean>(false);
const [showDetails2, setShowDetails2] = useState<boolean>(false);

const includeHandler = (exerciseUid: string) => setSelectedQuestions(previous => previous.concat(exerciseUid));
const removeHandler = (exerciseUid: string) => setSelectedQuestions(previous => previous.filter((id) => id !== exerciseUid));

return (
<TextResizerProvider>
<h2>Exercise cards</h2>
<Exercise {...props1} className='preview-card' />
<Exercise {...props2} className='preview-card' />
<h2>Exercise Preview cards</h2>
{showDetails1 && <h2>Details 1!</h2>}
<ExercisePreview
selected={selectedQuestions.includes(props1.exercise.uid)}
onIncludeHandler={()=> includeHandler(props1.exercise.uid)}
onRemoveHandler={()=> removeHandler(props1.exercise.uid)}
onClickDetails={()=> setShowDetails1((previous) => !previous)}
enableOverlay
exercise={props1.exercise}
/>
{showDetails2 && <h2>Details 2!</h2>}
<ExercisePreview
selected={selectedQuestions.includes(props2.exercise.uid)}
onIncludeHandler={()=> includeHandler(props2.exercise.uid)}
onRemoveHandler={()=> removeHandler(props2.exercise.uid)}
onClickDetails={()=> setShowDetails2((previous) => !previous)}
enableOverlay
exercise={props2.exercise}
/>
</TextResizerProvider>
);
};
44 changes: 42 additions & 2 deletions src/components/Exercise/styles.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -44,6 +58,7 @@ export const exerciseStyles = css`
.openstax-question {
.openstax-answer {
padding: 0;
border: none;
.answer-label {
padding-top: var(--spacing);
Expand All @@ -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 {
Expand All @@ -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;
}
}
`;
69 changes: 69 additions & 0 deletions src/components/ExercisePreview.spec.tsx
Original file line number Diff line number Diff line change
@@ -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: '<b>Stimulus HTML</b>',
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(
<ExercisePreview
exercise={exercise}
enableOverlay={enableOverlay}
selected={selected}
onIncludeHandler={onIncludeMock}
onRemoveHandler={onRemoveMock}
onClickDetails={onDetailsMock}
/>
).toJSON();
expect(tree).toMatchSnapshot();
});
});
});
95 changes: 95 additions & 0 deletions src/components/ExercisePreview.tsx
Original file line number Diff line number Diff line change
@@ -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(() =>
<IncludeRemoveQuestion
buttonVariant={selected ? 'remove' : 'include'}
onIncludeHandler={onIncludeHandler}
onRemoveHandler={onRemoveHandler}
onClickDetails={onClickDetails}
/>
, [selected, onIncludeHandler, onRemoveHandler, onClickDetails]);

return (
<Exercise
exercise={exercise}
className={selected ? 'preview-card is-selected' : 'preview-card'}
{
...(enableOverlay
? {
overlayChildren: includeRemoveQuestionComponent,
}
: {})
}
{...exercisePreviewProps(exercise)}
/>
);
};
10 changes: 9 additions & 1 deletion src/components/IncludeRemoveQuestion.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<IncludeRemoveQuestion buttonVariant={buttonVariant} onIncludeHandler={mockIncludeHandler} onRemoveHandler={mockRemoveHandler}/>
<IncludeRemoveQuestion
buttonVariant={buttonVariant}
onIncludeHandler={mockIncludeHandler}
onRemoveHandler={mockRemoveHandler}
onClickDetails={mockOnClickDetails}
/>
);

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();
});
});
Loading

0 comments on commit 946c936

Please sign in to comment.