@khanacademy/perseus 17.0.0 → 17.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +66 -0
- package/dist/dependencies.d.ts +1 -0
- package/dist/es/index.css +20 -0
- package/dist/es/index.js +443 -338
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +20 -0
- package/dist/index.js +444 -340
- package/dist/index.js.map +1 -1
- package/dist/perseus-types.d.ts +1 -0
- package/dist/strings.js +1 -0
- package/dist/widgets/label-image/answer-pill.d.ts +10 -0
- package/dist/widgets/label-image/hide-answers-toggle.d.ts +6 -0
- package/dist/widgets/label-image/marker.d.ts +6 -9
- package/dist/widgets/label-image/types.d.ts +1 -0
- package/dist/widgets/label-image.d.ts +101 -12
- package/package.json +30 -22
- package/src/__tests__/__snapshots__/renderer.test.tsx.snap +3 -3
- package/src/dependencies.ts +4 -0
- package/src/perseus-types.ts +2 -0
- package/src/styles/widgets/label-image.less +32 -0
- package/src/widgets/__stories__/label-image.stories.tsx +73 -3
- package/src/widgets/__testdata__/label-image.testdata.ts +118 -1
- package/src/widgets/__tests__/__snapshots__/dropdown.test.ts.snap +2 -2
- package/src/widgets/label-image/__stories__/answer-pill.stories.tsx +62 -0
- package/src/widgets/label-image/__tests__/answer-pill.test.tsx +83 -0
- package/src/widgets/label-image/__tests__/hide-answers-toggle.test.tsx +39 -0
- package/src/widgets/label-image/answer-choices.tsx +7 -1
- package/src/widgets/label-image/answer-pill.tsx +89 -0
- package/src/widgets/label-image/hide-answers-toggle.tsx +40 -0
- package/src/widgets/label-image/marker.tsx +116 -174
- package/src/widgets/label-image/types.ts +1 -0
- package/src/widgets/label-image.test.ts +142 -52
- package/src/widgets/label-image.tsx +212 -135
- package/tsconfig-build.tsbuildinfo +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type {PerseusRenderer} from "../../perseus-types";
|
|
2
2
|
|
|
3
|
-
export const
|
|
3
|
+
export const textQuestion: PerseusRenderer = {
|
|
4
4
|
content:
|
|
5
5
|
"Carol created a chart and a bar graph to show how many of each type of vehicle were in her supermarket parking lot.\n\nVehicle Type | Number in the parking lot\n:- | :-: \nTrucks| $25$ \nVans | $5$ \nCars| $40$ \nSUVs | $10$ \n\n**Label each bar on the bar graph.**\n\n[[☃ label-image 1]]\n\n",
|
|
6
6
|
images: {
|
|
@@ -61,3 +61,120 @@ export const question: PerseusRenderer = {
|
|
|
61
61
|
},
|
|
62
62
|
},
|
|
63
63
|
};
|
|
64
|
+
|
|
65
|
+
export const mathQuestion: PerseusRenderer = {
|
|
66
|
+
content:
|
|
67
|
+
"Carol created a chart and a bar graph to show how many of each type of vehicle were in her supermarket parking lot.\n\nVehicle Type | Number in the parking lot\n:- | :-: \nTrucks| $25$ \nVans | $5$ \nCars| $40$ \nSUVs | $10$ \n\n**Label each bar on the bar graph.**\n\n[[☃ label-image 1]]\n\n",
|
|
68
|
+
images: {
|
|
69
|
+
"web+graphie://ka-perseus-graphie.s3.amazonaws.com/1e28332fd2321975639ab50c9ce442e568c18421":
|
|
70
|
+
{
|
|
71
|
+
width: 341,
|
|
72
|
+
height: 310,
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
widgets: {
|
|
76
|
+
"label-image 1": {
|
|
77
|
+
type: "label-image",
|
|
78
|
+
alignment: "default",
|
|
79
|
+
static: false,
|
|
80
|
+
graded: true,
|
|
81
|
+
options: {
|
|
82
|
+
static: false,
|
|
83
|
+
choices: [
|
|
84
|
+
"$\\dfrac{1}{2}$",
|
|
85
|
+
"$\\dfrac{3}{4}$",
|
|
86
|
+
"$\\dfrac{5}{6}$",
|
|
87
|
+
"$\\dfrac{7}{8}$",
|
|
88
|
+
],
|
|
89
|
+
imageAlt:
|
|
90
|
+
"A bar graph with four bar lines that shows the horizontal axis labeled Number in the parking lot and the vertical axis labeled Vehicle Type. The horizontal axis is labeled, from left to right: 0, 10, 20, 30, 40, and 50. The vertical axis has, from the bottom to the top, four unlabeled bar lines as follows: the first unlabeled bar line extends to the middle of 0 and 10, the second unlabeled bar line extends to 40, the third unlabeled bar line extends to the middle of 20 and 30, and fourth unlabeled bar line extends to 10.",
|
|
91
|
+
imageUrl:
|
|
92
|
+
"web+graphie://ka-perseus-graphie.s3.amazonaws.com/56c60c72e96cd353e4a8b5434506cd3a21e717af",
|
|
93
|
+
imageWidth: 415,
|
|
94
|
+
imageHeight: 314,
|
|
95
|
+
markers: [
|
|
96
|
+
{
|
|
97
|
+
answers: ["$\\dfrac{1}{2}$"],
|
|
98
|
+
label: "The fourth unlabeled bar line.",
|
|
99
|
+
x: 25,
|
|
100
|
+
y: 17.7,
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
answers: ["$\\dfrac{3}{4}$"],
|
|
104
|
+
label: "The third unlabeled bar line.",
|
|
105
|
+
x: 25,
|
|
106
|
+
y: 35.3,
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
answers: ["$\\dfrac{5}{6}$"],
|
|
110
|
+
label: "The second unlabeled bar line.",
|
|
111
|
+
x: 25,
|
|
112
|
+
y: 53,
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
answers: ["$\\dfrac{7}{8}$"],
|
|
116
|
+
label: "The first unlabeled bar line.",
|
|
117
|
+
x: 25,
|
|
118
|
+
y: 70.3,
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
multipleAnswers: false,
|
|
122
|
+
hideChoicesFromInstructions: true,
|
|
123
|
+
},
|
|
124
|
+
version: {
|
|
125
|
+
major: 0,
|
|
126
|
+
minor: 0,
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export const numberline: PerseusRenderer = {
|
|
133
|
+
content:
|
|
134
|
+
"**Label each point on the number line with the correct value.**\n\n[[☃ label-image 1]]",
|
|
135
|
+
images: {},
|
|
136
|
+
widgets: {
|
|
137
|
+
"label-image 1": {
|
|
138
|
+
type: "label-image",
|
|
139
|
+
alignment: "default",
|
|
140
|
+
static: false,
|
|
141
|
+
graded: true,
|
|
142
|
+
options: {
|
|
143
|
+
static: false,
|
|
144
|
+
choices: ["$-\\dfrac{7}{3}$", "$-2\\dfrac{5}{8}$", "$-2.9$"],
|
|
145
|
+
imageAlt:
|
|
146
|
+
"A number line from negative 6 halves to negative 3 halves, labeled in increments of 1 half. There are three points on the line, labeled from left to right with a, b, and c.",
|
|
147
|
+
imageUrl:
|
|
148
|
+
"web+graphie://ka-perseus-graphie.s3.amazonaws.com/05faa925d02e5effd3069bf24da4777e3ae1a28b",
|
|
149
|
+
imageWidth: 360,
|
|
150
|
+
imageHeight: 160,
|
|
151
|
+
markers: [
|
|
152
|
+
{
|
|
153
|
+
answers: ["$-2.9$"],
|
|
154
|
+
label: "Point a is the leftmost of two points between negative 6 halves and negative 5 halves.",
|
|
155
|
+
x: 14.25,
|
|
156
|
+
y: 50,
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
answers: ["$-2\\dfrac{5}{8}$"],
|
|
160
|
+
label: "Point b is the rightmost of two points between negative 6 halves and negative 5 halves.",
|
|
161
|
+
x: 29.5,
|
|
162
|
+
y: 50,
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
answers: ["$-\\dfrac{7}{3}$"],
|
|
166
|
+
label: "Point c is between negative 5 halves and negative 4 halves.",
|
|
167
|
+
x: 45.5,
|
|
168
|
+
y: 50,
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
multipleAnswers: false,
|
|
172
|
+
hideChoicesFromInstructions: false,
|
|
173
|
+
},
|
|
174
|
+
version: {
|
|
175
|
+
major: 0,
|
|
176
|
+
minor: 0,
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
};
|
|
@@ -42,7 +42,7 @@ exports[`Dropdown widget should snapshot when opened: dropdown open 1`] = `
|
|
|
42
42
|
</span>
|
|
43
43
|
<span
|
|
44
44
|
aria-hidden="true"
|
|
45
|
-
class="
|
|
45
|
+
class="svg_1q6jc65-o_O-icon_eocf2p-o_O-caret_ntkmq-o_O-inlineStyles_u7ovp6"
|
|
46
46
|
/>
|
|
47
47
|
</button>
|
|
48
48
|
</div>
|
|
@@ -107,7 +107,7 @@ exports[`Dropdown widget should snapshot: initial render 1`] = `
|
|
|
107
107
|
</span>
|
|
108
108
|
<span
|
|
109
109
|
aria-hidden="true"
|
|
110
|
-
class="
|
|
110
|
+
class="svg_1q6jc65-o_O-icon_eocf2p-o_O-caret_ntkmq-o_O-inlineStyles_u7ovp6"
|
|
111
111
|
/>
|
|
112
112
|
</button>
|
|
113
113
|
</div>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import {AnswerPill} from "../answer-pill";
|
|
4
|
+
|
|
5
|
+
import type {Meta} from "@storybook/react";
|
|
6
|
+
|
|
7
|
+
const story: Meta<React.ComponentProps<typeof AnswerPill>> = {
|
|
8
|
+
title: "Perseus/Widgets/Label Image/Answer Pill",
|
|
9
|
+
component: AnswerPill,
|
|
10
|
+
render: (args) => <AnswerPill {...args} />,
|
|
11
|
+
argTypes: {
|
|
12
|
+
showCorrectness: {
|
|
13
|
+
options: ["not answered", "correct", "incorrect"],
|
|
14
|
+
control: {
|
|
15
|
+
type: "radio",
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
export default story;
|
|
21
|
+
|
|
22
|
+
export const SingleAnswer = {
|
|
23
|
+
args: {
|
|
24
|
+
id: "1",
|
|
25
|
+
selectedAnswers: ["Answer Pill!"],
|
|
26
|
+
markerRef: null,
|
|
27
|
+
side: "top",
|
|
28
|
+
onClick: () => {},
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const MultipleAnswers = {
|
|
33
|
+
args: {
|
|
34
|
+
id: "1",
|
|
35
|
+
selectedAnswers: ["Answer 1", "Answer 2"],
|
|
36
|
+
markerRef: null,
|
|
37
|
+
side: "top",
|
|
38
|
+
onClick: () => {},
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const Correct = {
|
|
43
|
+
args: {
|
|
44
|
+
id: "1",
|
|
45
|
+
selectedAnswers: ["Right Answer!"],
|
|
46
|
+
showCorrectness: "correct",
|
|
47
|
+
markerRef: null,
|
|
48
|
+
side: "top",
|
|
49
|
+
onClick: () => {},
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const Incorrect = {
|
|
54
|
+
args: {
|
|
55
|
+
id: "1",
|
|
56
|
+
selectedAnswers: ["Wrong Answer!"],
|
|
57
|
+
showCorrectness: "incorrect",
|
|
58
|
+
markerRef: null,
|
|
59
|
+
side: "top",
|
|
60
|
+
onClick: () => {},
|
|
61
|
+
},
|
|
62
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import {RenderStateRoot} from "@khanacademy/wonder-blocks-core";
|
|
2
|
+
import {render} from "@testing-library/react";
|
|
3
|
+
import "@testing-library/jest-dom";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
|
|
6
|
+
import {testDependencies} from "../../../../../../testing/test-dependencies";
|
|
7
|
+
import * as Dependencies from "../../../dependencies";
|
|
8
|
+
import {AnswerPill} from "../answer-pill";
|
|
9
|
+
|
|
10
|
+
describe("AnswerPill", () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
jest.spyOn(Dependencies, "getDependencies").mockReturnValue(
|
|
13
|
+
testDependencies,
|
|
14
|
+
);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("should display the correct answer string when there is only one selected answer", () => {
|
|
18
|
+
const selectedAnswers = ["Answer 1"];
|
|
19
|
+
const screen = render(
|
|
20
|
+
<AnswerPill
|
|
21
|
+
selectedAnswers={selectedAnswers}
|
|
22
|
+
side={"top"}
|
|
23
|
+
onClick={() => {}}
|
|
24
|
+
/>,
|
|
25
|
+
{wrapper: RenderStateRoot},
|
|
26
|
+
);
|
|
27
|
+
expect(screen.getByText("Answer 1")).toBeInTheDocument();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("should display the correct answer string when there are multiple selected answers", () => {
|
|
31
|
+
const selectedAnswers = ["Answer 1", "Answer 2", "Answer 3"];
|
|
32
|
+
const screen = render(
|
|
33
|
+
<AnswerPill
|
|
34
|
+
selectedAnswers={selectedAnswers}
|
|
35
|
+
side={"top"}
|
|
36
|
+
onClick={() => {}}
|
|
37
|
+
/>,
|
|
38
|
+
{wrapper: RenderStateRoot},
|
|
39
|
+
);
|
|
40
|
+
expect(screen.getByText("3 answers")).toBeInTheDocument();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("should be interactable when no right answer has been chosen", () => {
|
|
44
|
+
// arrange
|
|
45
|
+
const clickSpy = jest.fn();
|
|
46
|
+
const selectedAnswers = ["Answer 1", "Answer 2", "Answer 3"];
|
|
47
|
+
const screen = render(
|
|
48
|
+
<AnswerPill
|
|
49
|
+
selectedAnswers={selectedAnswers}
|
|
50
|
+
side={"top"}
|
|
51
|
+
onClick={clickSpy}
|
|
52
|
+
/>,
|
|
53
|
+
{wrapper: RenderStateRoot},
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
// act
|
|
57
|
+
screen.getByText("3 answers").click();
|
|
58
|
+
|
|
59
|
+
// assert
|
|
60
|
+
expect(clickSpy).toHaveBeenCalled();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("should not be interactable when a correct answer has been chosen", () => {
|
|
64
|
+
// arrange
|
|
65
|
+
const clickSpy = jest.fn();
|
|
66
|
+
const selectedAnswers = ["Answer 1", "Answer 2", "Answer 3"];
|
|
67
|
+
const screen = render(
|
|
68
|
+
<AnswerPill
|
|
69
|
+
selectedAnswers={selectedAnswers}
|
|
70
|
+
side={"top"}
|
|
71
|
+
onClick={clickSpy}
|
|
72
|
+
showCorrectness="correct"
|
|
73
|
+
/>,
|
|
74
|
+
{wrapper: RenderStateRoot},
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
// act
|
|
78
|
+
screen.getByText("3 answers").click();
|
|
79
|
+
|
|
80
|
+
// assert
|
|
81
|
+
expect(clickSpy).not.toHaveBeenCalled();
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {RenderStateRoot} from "@khanacademy/wonder-blocks-core";
|
|
2
|
+
import {render} from "@testing-library/react";
|
|
3
|
+
import "@testing-library/jest-dom";
|
|
4
|
+
import userEvent from "@testing-library/user-event";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
|
|
7
|
+
import {HideAnswersToggle} from "../hide-answers-toggle";
|
|
8
|
+
import {strings} from "../strings";
|
|
9
|
+
|
|
10
|
+
const labelText = strings.hideAnswersToggleLabel;
|
|
11
|
+
|
|
12
|
+
describe("HideAnswersToggle", () => {
|
|
13
|
+
it("should render the toggle switch", () => {
|
|
14
|
+
const screen = render(
|
|
15
|
+
<HideAnswersToggle areAnswersHidden={false} onChange={undefined} />,
|
|
16
|
+
{
|
|
17
|
+
wrapper: RenderStateRoot,
|
|
18
|
+
},
|
|
19
|
+
);
|
|
20
|
+
const toggleSwitch = screen.getByLabelText(
|
|
21
|
+
labelText,
|
|
22
|
+
) as HTMLInputElement;
|
|
23
|
+
expect(toggleSwitch).toBeInTheDocument();
|
|
24
|
+
expect(toggleSwitch.checked).toBe(false);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("should call the onChange callback when the toggle switch is clicked", async () => {
|
|
28
|
+
const onChange = jest.fn((checked) => expect(checked).toBe(true));
|
|
29
|
+
const screen = render(
|
|
30
|
+
<HideAnswersToggle areAnswersHidden={false} onChange={onChange} />,
|
|
31
|
+
{wrapper: RenderStateRoot},
|
|
32
|
+
);
|
|
33
|
+
const toggleSwitchBefore = screen.getByLabelText(
|
|
34
|
+
labelText,
|
|
35
|
+
) as HTMLInputElement;
|
|
36
|
+
userEvent.click(toggleSwitchBefore);
|
|
37
|
+
expect(onChange).toHaveBeenCalledTimes(1);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -236,7 +236,13 @@ export default class AnswerChoices extends React.Component<
|
|
|
236
236
|
const {groupName} = this.state;
|
|
237
237
|
|
|
238
238
|
return (
|
|
239
|
-
<fieldset
|
|
239
|
+
<fieldset
|
|
240
|
+
style={{
|
|
241
|
+
border: "none",
|
|
242
|
+
margin: 0,
|
|
243
|
+
padding: 0,
|
|
244
|
+
}}
|
|
245
|
+
>
|
|
240
246
|
{choices.map((choice, index) => (
|
|
241
247
|
<AnswerChoice
|
|
242
248
|
{...choice}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import Color from "@khanacademy/wonder-blocks-color";
|
|
2
|
+
import {useUniqueIdWithMock} from "@khanacademy/wonder-blocks-core";
|
|
3
|
+
import * as i18n from "@khanacademy/wonder-blocks-i18n";
|
|
4
|
+
import Pill from "@khanacademy/wonder-blocks-pill";
|
|
5
|
+
import {StyleSheet, type CSSProperties} from "aphrodite";
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
import {Popper} from "react-popper";
|
|
8
|
+
|
|
9
|
+
import Renderer from "../../renderer";
|
|
10
|
+
|
|
11
|
+
export const AnswerPill = (props: {
|
|
12
|
+
selectedAnswers: readonly string[];
|
|
13
|
+
showCorrectness?: "correct" | "incorrect";
|
|
14
|
+
markerRef?: HTMLElement;
|
|
15
|
+
side: "top" | "bottom" | "left" | "right";
|
|
16
|
+
onClick: () => void;
|
|
17
|
+
style?: CSSProperties;
|
|
18
|
+
}) => {
|
|
19
|
+
const {selectedAnswers, showCorrectness, markerRef, side, onClick, style} =
|
|
20
|
+
props;
|
|
21
|
+
|
|
22
|
+
const idFactory = useUniqueIdWithMock();
|
|
23
|
+
|
|
24
|
+
const answerString =
|
|
25
|
+
selectedAnswers.length > 1
|
|
26
|
+
? // always need `ngettext` for variable numbers even if we don't use the singular, see https://khanacademy.slack.com/archives/C0918TZ5G/p1700163024293079
|
|
27
|
+
i18n.ngettext(
|
|
28
|
+
"%(num)s answer",
|
|
29
|
+
"%(num)s answers",
|
|
30
|
+
selectedAnswers.length,
|
|
31
|
+
)
|
|
32
|
+
: selectedAnswers[0];
|
|
33
|
+
|
|
34
|
+
// It should no longer be possible to interact with an answer after it
|
|
35
|
+
// has been labeled (answered) correctly.
|
|
36
|
+
const correct = showCorrectness === "correct";
|
|
37
|
+
const incorrect = showCorrectness === "incorrect";
|
|
38
|
+
|
|
39
|
+
const bringToFront = {
|
|
40
|
+
boxShadow: `0 8px 8px ${Color.offBlack64}`,
|
|
41
|
+
zIndex: 1000,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<Popper
|
|
46
|
+
placement={side}
|
|
47
|
+
referenceElement={markerRef}
|
|
48
|
+
modifiers={[
|
|
49
|
+
{
|
|
50
|
+
name: "preventOverflow",
|
|
51
|
+
options: {
|
|
52
|
+
rootBoundary: "viewport",
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
]}
|
|
56
|
+
>
|
|
57
|
+
{({ref, style: popperStyle}) => (
|
|
58
|
+
<Pill
|
|
59
|
+
size="large"
|
|
60
|
+
kind="accent"
|
|
61
|
+
id={idFactory.get("perseus-label-image-widget-answer-pill")}
|
|
62
|
+
onClick={correct ? undefined : onClick}
|
|
63
|
+
ref={ref}
|
|
64
|
+
style={[
|
|
65
|
+
style,
|
|
66
|
+
popperStyle,
|
|
67
|
+
{
|
|
68
|
+
":hover": bringToFront,
|
|
69
|
+
":focus": bringToFront,
|
|
70
|
+
},
|
|
71
|
+
correct && styles.correct,
|
|
72
|
+
incorrect && styles.incorrect,
|
|
73
|
+
]}
|
|
74
|
+
>
|
|
75
|
+
<Renderer content={answerString} />
|
|
76
|
+
</Pill>
|
|
77
|
+
)}
|
|
78
|
+
</Popper>
|
|
79
|
+
);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const styles = StyleSheet.create({
|
|
83
|
+
correct: {
|
|
84
|
+
backgroundColor: "#00880b", // WB green darkened by 18%
|
|
85
|
+
},
|
|
86
|
+
incorrect: {
|
|
87
|
+
backgroundColor: Color.offBlack64,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import {View, useUniqueIdWithMock} from "@khanacademy/wonder-blocks-core";
|
|
2
|
+
import Switch from "@khanacademy/wonder-blocks-switch";
|
|
3
|
+
import {LabelMedium} from "@khanacademy/wonder-blocks-typography";
|
|
4
|
+
import {StyleSheet} from "aphrodite";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
|
|
7
|
+
import {strings} from "./strings";
|
|
8
|
+
|
|
9
|
+
export const HideAnswersToggle = (props: {
|
|
10
|
+
areAnswersHidden: boolean;
|
|
11
|
+
onChange: React.ComponentPropsWithoutRef<typeof Switch>["onChange"];
|
|
12
|
+
}) => {
|
|
13
|
+
const idFactory = useUniqueIdWithMock();
|
|
14
|
+
const switchId = idFactory.get("hide-answers-toggle");
|
|
15
|
+
const labelId = idFactory.get("hide-answers-label");
|
|
16
|
+
return (
|
|
17
|
+
<View style={styles.switchWrapper}>
|
|
18
|
+
<Switch
|
|
19
|
+
id={switchId}
|
|
20
|
+
checked={props.areAnswersHidden}
|
|
21
|
+
onChange={props.onChange}
|
|
22
|
+
aria-labelledby={labelId}
|
|
23
|
+
/>
|
|
24
|
+
<LabelMedium id={labelId} htmlFor={switchId}>
|
|
25
|
+
{strings.hideAnswersToggleLabel}
|
|
26
|
+
</LabelMedium>
|
|
27
|
+
</View>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const styles = StyleSheet.create({
|
|
32
|
+
switchWrapper: {
|
|
33
|
+
display: "flex",
|
|
34
|
+
flexDirection: "row",
|
|
35
|
+
flexWrap: "wrap-reverse",
|
|
36
|
+
alignItems: "center",
|
|
37
|
+
gap: "0.5em",
|
|
38
|
+
marginTop: "1em",
|
|
39
|
+
},
|
|
40
|
+
});
|