@khanacademy/perseus 1.4.0 → 2.0.0
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 +22 -0
- package/dist/es/index.css +9 -245
- package/dist/es/index.js +138 -584
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +9 -245
- package/dist/index.js +135 -581
- package/dist/index.js.map +1 -1
- package/package.json +36 -36
- package/src/__stories__/hints-renderer.stories.jsx +9 -1
- package/src/__testdata__/graphie_testdata.js +53 -0
- package/src/__tests__/__snapshots__/perseus-markdown_test.jsx.snap +0 -9
- package/src/__tests__/__snapshots__/renderer_test.jsx.snap +0 -9
- package/src/components/__stories__/graphie.stories.jsx +5 -1
- package/src/hint-renderer.jsx +11 -14
- package/src/perseus-api.jsx +0 -23
- package/src/perseus-markdown.jsx +0 -25
- package/src/styles/constants.js +0 -21
- package/src/styles/global-constants.js +0 -6
- package/src/styles/khan-exercise.css +9 -1
- package/src/styles/perseus-renderer.less +0 -259
- package/src/styles/variables.less +0 -11
- package/src/styles/variables.shared.less +0 -10
- package/src/types.js +0 -16
- package/src/widgets/__stories__/input-number.stories.jsx +0 -14
- package/src/widgets/__stories__/radio.stories.jsx +0 -3
- package/src/widgets/__tests__/__snapshots__/definition_test.js.snap +18 -28
- package/src/widgets/__tests__/__snapshots__/group_test.jsx.snap +0 -9
- package/src/widgets/__tests__/__snapshots__/image_test.jsx.snap +0 -18
- package/src/widgets/__tests__/__snapshots__/numeric-input_test.jsx.snap +2 -440
- package/src/widgets/__tests__/__snapshots__/radio_test.jsx.snap +66 -3726
- package/src/widgets/__tests__/numeric-input_test.jsx +54 -118
- package/src/widgets/__tests__/radio/choice-icon_test.jsx +25 -75
- package/src/widgets/__tests__/radio_test.jsx +362 -394
- package/src/widgets/definition.jsx +27 -14
- package/src/widgets/input-number.jsx +2 -60
- package/src/widgets/numeric-input.jsx +17 -89
- package/src/widgets/radio/__stories__/base-radio.stories.jsx +1 -3
- package/src/widgets/radio/__stories__/choice-icon.stories.jsx +23 -105
- package/src/widgets/radio/__stories__/option-status.stories.jsx +0 -8
- package/src/widgets/radio/base-radio.jsx +13 -96
- package/src/widgets/radio/choice-icon/choice-icon.jsx +177 -50
- package/src/widgets/radio/choice-icon/cross-out-line.jsx +9 -13
- package/src/widgets/radio/choice-icon/shared-styles.js +2 -3
- package/src/widgets/radio/choice.jsx +5 -59
- package/src/widgets/radio/option-status.jsx +2 -14
- package/src/components/__stories__/possible-answers.stories.jsx +0 -22
- package/src/components/possible-answers.jsx +0 -36
- package/src/styles/widgets/definition.less +0 -86
- package/src/widgets/radio/__stories__/sat-styles.stories.jsx +0 -191
- package/src/widgets/radio/choice-icon/library-choice-icon.jsx +0 -225
- package/src/widgets/radio/choice-icon/sat-choice-icon.jsx +0 -120
|
@@ -205,7 +205,6 @@ function BaseRadio(props: Props): React.Node {
|
|
|
205
205
|
|
|
206
206
|
// some commonly used shorthands
|
|
207
207
|
const reviewMode = !!reviewModeRubric;
|
|
208
|
-
const sat = apiOptions.satStyling;
|
|
209
208
|
const isMobile = apiOptions.isMobile;
|
|
210
209
|
|
|
211
210
|
const firstChoiceHighlighted = choices[0].highlighted;
|
|
@@ -216,18 +215,13 @@ function BaseRadio(props: Props): React.Node {
|
|
|
216
215
|
!editMode && "perseus-rendered-radio",
|
|
217
216
|
css(
|
|
218
217
|
styles.radio,
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
!sat && styles.responsiveRadioContainer,
|
|
222
|
-
!sat &&
|
|
223
|
-
firstChoiceHighlighted &&
|
|
218
|
+
styles.responsiveRadioContainer,
|
|
219
|
+
firstChoiceHighlighted &&
|
|
224
220
|
isMobile &&
|
|
225
221
|
styles.radioContainerFirstHighlighted,
|
|
226
|
-
|
|
227
|
-
lastChoiceHighlighted &&
|
|
222
|
+
lastChoiceHighlighted &&
|
|
228
223
|
isMobile &&
|
|
229
224
|
styles.radioContainerLastHighlighted,
|
|
230
|
-
sat && styles.satRadio,
|
|
231
225
|
),
|
|
232
226
|
);
|
|
233
227
|
|
|
@@ -240,21 +234,16 @@ function BaseRadio(props: Props): React.Node {
|
|
|
240
234
|
countChoices,
|
|
241
235
|
numCorrect,
|
|
242
236
|
);
|
|
243
|
-
const shouldShowInstructions = !sat;
|
|
244
237
|
|
|
245
238
|
const responsiveClassName = css(styles.responsiveFieldset);
|
|
246
239
|
const fieldset = (
|
|
247
240
|
<fieldset
|
|
248
241
|
className={`perseus-widget-radio-fieldset ${responsiveClassName}`}
|
|
249
242
|
>
|
|
250
|
-
{
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
<div className={instructionsClassName} aria-hidden="true">
|
|
255
|
-
{instructions}
|
|
256
|
-
</div>
|
|
257
|
-
)}
|
|
243
|
+
<legend className="perseus-sr-only">{instructions}</legend>
|
|
244
|
+
<div className={instructionsClassName} aria-hidden="true">
|
|
245
|
+
{instructions}
|
|
246
|
+
</div>
|
|
258
247
|
<ul className={className} style={{listStyle: "none"}}>
|
|
259
248
|
{choices.map((choice, i) => {
|
|
260
249
|
let Element = Choice;
|
|
@@ -301,47 +290,21 @@ function BaseRadio(props: Props): React.Node {
|
|
|
301
290
|
const aphroditeClassName = (checked) => {
|
|
302
291
|
// Whether or not to show correctness borders
|
|
303
292
|
// for this choice and the next choice.
|
|
304
|
-
const satShowCorrectness = sat && reviewMode && checked;
|
|
305
|
-
const satShowCorrectnessNext =
|
|
306
|
-
sat &&
|
|
307
|
-
reviewMode &&
|
|
308
|
-
nextChoice &&
|
|
309
|
-
nextChoice.checked;
|
|
310
|
-
|
|
311
293
|
return css(
|
|
312
294
|
sharedStyles.aboveScratchpad,
|
|
313
295
|
styles.item,
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
checked &&
|
|
296
|
+
styles.responsiveItem,
|
|
297
|
+
checked && styles.selectedItem,
|
|
298
|
+
checked &&
|
|
318
299
|
choice.highlighted &&
|
|
319
300
|
styles.aboveBackdrop,
|
|
320
|
-
|
|
321
|
-
checked &&
|
|
301
|
+
checked &&
|
|
322
302
|
choice.highlighted &&
|
|
323
303
|
apiOptions.isMobile &&
|
|
324
304
|
styles.aboveBackdropMobile,
|
|
325
|
-
|
|
326
|
-
nextChoiceHighlighted &&
|
|
305
|
+
nextChoiceHighlighted &&
|
|
327
306
|
apiOptions.isMobile &&
|
|
328
307
|
styles.nextHighlighted,
|
|
329
|
-
sat && styles.satRadioOption,
|
|
330
|
-
satShowCorrectness &&
|
|
331
|
-
!choice.correct &&
|
|
332
|
-
styles.satRadioOptionIncorrect,
|
|
333
|
-
satShowCorrectness &&
|
|
334
|
-
choice.correct &&
|
|
335
|
-
styles.satRadioOptionCorrect,
|
|
336
|
-
satShowCorrectnessNext &&
|
|
337
|
-
!nextChoice.correct &&
|
|
338
|
-
styles.satRadioOptionNextIncorrect,
|
|
339
|
-
satShowCorrectnessNext &&
|
|
340
|
-
nextChoice.correct &&
|
|
341
|
-
styles.satRadioOptionNextCorrect,
|
|
342
|
-
sat &&
|
|
343
|
-
reviewModeRubric &&
|
|
344
|
-
styles.satReviewRadioOption,
|
|
345
308
|
);
|
|
346
309
|
};
|
|
347
310
|
|
|
@@ -420,12 +383,7 @@ function BaseRadio(props: Props): React.Node {
|
|
|
420
383
|
|
|
421
384
|
// Allow for horizontal scrolling if content is too wide, which may be
|
|
422
385
|
// an issue especially on phones.
|
|
423
|
-
|
|
424
|
-
return (
|
|
425
|
-
<div className={css(!sat && styles.responsiveContainer)}>
|
|
426
|
-
{fieldset}
|
|
427
|
-
</div>
|
|
428
|
-
);
|
|
386
|
+
return <div className={css(styles.responsiveContainer)}>{fieldset}</div>;
|
|
429
387
|
}
|
|
430
388
|
|
|
431
389
|
BaseRadio.defaultProps = {
|
|
@@ -480,47 +438,6 @@ const styles: StyleDeclaration = StyleSheet.create({
|
|
|
480
438
|
borderBottom: `1px solid rgba(0, 0, 0, 0)`,
|
|
481
439
|
},
|
|
482
440
|
|
|
483
|
-
satRadio: {
|
|
484
|
-
background: "none",
|
|
485
|
-
marginLeft: 0,
|
|
486
|
-
userSelect: "none",
|
|
487
|
-
},
|
|
488
|
-
|
|
489
|
-
satRadioOption: {
|
|
490
|
-
margin: 0,
|
|
491
|
-
padding: 0,
|
|
492
|
-
borderBottom: `1px solid #ccc`,
|
|
493
|
-
":first-child": {
|
|
494
|
-
borderTop: `1px solid #ccc`,
|
|
495
|
-
},
|
|
496
|
-
},
|
|
497
|
-
|
|
498
|
-
satRadioOptionCorrect: {
|
|
499
|
-
borderBottomColor: styleConstants.satCorrectBorderColor,
|
|
500
|
-
":first-child": {
|
|
501
|
-
borderTopColor: styleConstants.satCorrectBorderColor,
|
|
502
|
-
},
|
|
503
|
-
},
|
|
504
|
-
|
|
505
|
-
satRadioOptionIncorrect: {
|
|
506
|
-
borderBottomColor: styleConstants.satIncorrectBorderColor,
|
|
507
|
-
":first-child": {
|
|
508
|
-
borderTopColor: styleConstants.satIncorrectBorderColor,
|
|
509
|
-
},
|
|
510
|
-
},
|
|
511
|
-
|
|
512
|
-
satRadioOptionNextCorrect: {
|
|
513
|
-
borderBottomColor: styleConstants.satCorrectBorderColor,
|
|
514
|
-
},
|
|
515
|
-
|
|
516
|
-
satRadioOptionNextIncorrect: {
|
|
517
|
-
borderBottomColor: styleConstants.satIncorrectBorderColor,
|
|
518
|
-
},
|
|
519
|
-
|
|
520
|
-
satReviewRadioOption: {
|
|
521
|
-
pointerEvents: "none",
|
|
522
|
-
},
|
|
523
|
-
|
|
524
441
|
item: {
|
|
525
442
|
marginLeft: 20,
|
|
526
443
|
},
|
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
// @flow
|
|
2
|
-
/* Component for rendering a letter icon in radio choice */
|
|
2
|
+
/* Component for rendering a letter icon in a library radio choice */
|
|
3
3
|
|
|
4
4
|
import Color from "@khanacademy/wonder-blocks-color";
|
|
5
|
+
import {StyleSheet, css} from "aphrodite";
|
|
5
6
|
import * as React from "react";
|
|
6
7
|
|
|
8
|
+
import InlineIcon from "../../../components/inline-icon.jsx";
|
|
9
|
+
import {iconCheck, iconMinus} from "../../../icon-paths.js";
|
|
10
|
+
import * as styleConstants from "../../../styles/constants.js";
|
|
11
|
+
import FocusRing from "../focus-ring.jsx";
|
|
7
12
|
import {getChoiceLetter} from "../util.js";
|
|
8
13
|
|
|
9
|
-
import
|
|
10
|
-
import
|
|
14
|
+
import CrossOutLine from "./cross-out-line.jsx";
|
|
15
|
+
import sharedStyles, {CHOICE_ICON_SIZE} from "./shared-styles.js";
|
|
11
16
|
|
|
12
|
-
type ChoiceIconProps = {
|
|
17
|
+
type ChoiceIconProps = {
|
|
13
18
|
pos: number,
|
|
14
19
|
checked: boolean,
|
|
15
20
|
crossedOut: boolean,
|
|
@@ -18,70 +23,192 @@ type ChoiceIconProps = {|
|
|
|
18
23
|
pressed: boolean,
|
|
19
24
|
correct: boolean,
|
|
20
25
|
showCorrectness: boolean,
|
|
21
|
-
// TODO(amy): if we go this "product" flag route, define this type
|
|
22
|
-
// somewhere shared
|
|
23
|
-
product: "sat" | "library",
|
|
24
26
|
multipleSelect: boolean,
|
|
25
27
|
reviewMode: boolean,
|
|
26
28
|
previouslyAnswered: boolean,
|
|
27
|
-
|
|
28
|
-
// so I've added this prop. I'm not sure why we have backgrounds in the
|
|
29
|
-
// general case, though? When does the choice container have a
|
|
30
|
-
// non-white background, aside from SAT, which uses a different icon?
|
|
31
|
-
transparentBackground?: boolean,
|
|
29
|
+
};
|
|
32
30
|
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
function ChoiceInner(props: {
|
|
32
|
+
pos: number,
|
|
33
|
+
showCorrectness: boolean,
|
|
34
|
+
correct: ?boolean,
|
|
35
|
+
}) {
|
|
36
|
+
const {pos, showCorrectness, correct} = props;
|
|
37
|
+
const letter = getChoiceLetter(pos);
|
|
38
|
+
|
|
39
|
+
if (!showCorrectness) {
|
|
40
|
+
return letter;
|
|
41
|
+
}
|
|
42
|
+
if (correct) {
|
|
43
|
+
return (
|
|
44
|
+
<InlineIcon
|
|
45
|
+
{...iconCheck}
|
|
46
|
+
style={{
|
|
47
|
+
position: "relative",
|
|
48
|
+
top: -1,
|
|
49
|
+
}}
|
|
50
|
+
/>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
return <InlineIcon {...iconMinus} />;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Handle dynamic styling of the multiple choice icon. Most
|
|
57
|
+
// MC icon styles are constant, but we do allow the caller
|
|
58
|
+
// to specify the selected color, and thus must control styles
|
|
59
|
+
// related to the selected state dynamically.
|
|
60
|
+
function getDynamicStyles(
|
|
61
|
+
checked: boolean,
|
|
62
|
+
showCorrectness: boolean,
|
|
63
|
+
pressed: boolean,
|
|
64
|
+
multipleSelect: boolean,
|
|
65
|
+
correct: ?boolean,
|
|
66
|
+
): {
|
|
67
|
+
backgroundColor: ?string,
|
|
68
|
+
borderColor: string,
|
|
69
|
+
color: string,
|
|
70
|
+
borderRadius: number,
|
|
71
|
+
} {
|
|
72
|
+
let backgroundColor;
|
|
73
|
+
let borderColor;
|
|
74
|
+
let color;
|
|
75
|
+
if (!showCorrectness && pressed) {
|
|
76
|
+
borderColor = Color.blue;
|
|
77
|
+
color = Color.blue;
|
|
78
|
+
backgroundColor = "transparent";
|
|
79
|
+
} else if (checked) {
|
|
80
|
+
const bg = showCorrectness && correct ? Color.green : Color.blue;
|
|
81
|
+
color = styleConstants.white;
|
|
82
|
+
backgroundColor = bg;
|
|
83
|
+
borderColor = bg;
|
|
84
|
+
} else {
|
|
85
|
+
borderColor = Color.offBlack64;
|
|
86
|
+
color = Color.offBlack64;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// define shape
|
|
90
|
+
let borderRadius;
|
|
91
|
+
if (multipleSelect) {
|
|
92
|
+
borderRadius = 3;
|
|
93
|
+
} else {
|
|
94
|
+
borderRadius = CHOICE_ICON_SIZE;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return {backgroundColor, borderColor, color, borderRadius};
|
|
98
|
+
}
|
|
35
99
|
|
|
36
100
|
function ChoiceIcon(props: ChoiceIconProps): React.Node {
|
|
37
101
|
const {
|
|
38
|
-
pos,
|
|
39
|
-
reviewMode,
|
|
40
102
|
checked,
|
|
41
103
|
crossedOut,
|
|
42
|
-
correct,
|
|
43
|
-
product,
|
|
44
|
-
multipleSelect,
|
|
45
104
|
showCorrectness,
|
|
105
|
+
correct,
|
|
46
106
|
focused,
|
|
47
107
|
hovered,
|
|
48
|
-
|
|
49
|
-
|
|
108
|
+
multipleSelect,
|
|
109
|
+
pos,
|
|
50
110
|
previouslyAnswered,
|
|
51
|
-
|
|
111
|
+
pressed,
|
|
52
112
|
} = props;
|
|
53
113
|
|
|
54
|
-
const
|
|
114
|
+
const dynamicStyles = getDynamicStyles(
|
|
115
|
+
checked,
|
|
116
|
+
showCorrectness,
|
|
117
|
+
pressed,
|
|
118
|
+
multipleSelect,
|
|
119
|
+
correct,
|
|
120
|
+
);
|
|
55
121
|
|
|
56
|
-
if (product === "sat") {
|
|
57
|
-
return (
|
|
58
|
-
<SATChoiceIcon
|
|
59
|
-
letter={letter}
|
|
60
|
-
reviewMode={reviewMode}
|
|
61
|
-
checked={checked}
|
|
62
|
-
correct={correct}
|
|
63
|
-
crossedOut={crossedOut}
|
|
64
|
-
multipleSelect={multipleSelect}
|
|
65
|
-
/>
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
122
|
return (
|
|
69
|
-
<
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
123
|
+
<div className={css(sharedStyles.iconWrapper)}>
|
|
124
|
+
<FocusRing
|
|
125
|
+
color={Color.blue}
|
|
126
|
+
visible={focused || hovered}
|
|
127
|
+
multipleSelect={multipleSelect}
|
|
128
|
+
>
|
|
129
|
+
<div
|
|
130
|
+
style={dynamicStyles}
|
|
131
|
+
data-test-id="choice-icon__library-choice-icon"
|
|
132
|
+
className={css(
|
|
133
|
+
styles.circle,
|
|
134
|
+
showCorrectness && correct && styles.circleCorrect,
|
|
135
|
+
showCorrectness && !correct && styles.circleIncorrect,
|
|
136
|
+
showCorrectness &&
|
|
137
|
+
!correct &&
|
|
138
|
+
(checked || previouslyAnswered) &&
|
|
139
|
+
styles.circleIncorrectAnswered,
|
|
140
|
+
)}
|
|
141
|
+
// used in BaseRadio editMode to check
|
|
142
|
+
// if we actually clicked on the radio icon
|
|
143
|
+
data-is-radio-icon={true}
|
|
144
|
+
>
|
|
145
|
+
<div className={css(styles.innerWrapper)}>
|
|
146
|
+
<ChoiceInner
|
|
147
|
+
pos={pos}
|
|
148
|
+
showCorrectness={showCorrectness}
|
|
149
|
+
correct={correct}
|
|
150
|
+
/>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
</FocusRing>
|
|
154
|
+
{crossedOut && <CrossOutLine color={dynamicStyles.borderColor} />}
|
|
155
|
+
</div>
|
|
84
156
|
);
|
|
85
157
|
}
|
|
86
158
|
|
|
159
|
+
const styles = StyleSheet.create({
|
|
160
|
+
innerWrapper: {
|
|
161
|
+
width: CHOICE_ICON_SIZE,
|
|
162
|
+
height: CHOICE_ICON_SIZE,
|
|
163
|
+
|
|
164
|
+
// Center the contents of the icon.
|
|
165
|
+
display: "flex",
|
|
166
|
+
alignItems: "center",
|
|
167
|
+
justifyContent: "center",
|
|
168
|
+
},
|
|
169
|
+
circle: {
|
|
170
|
+
// Make the circle
|
|
171
|
+
width: CHOICE_ICON_SIZE,
|
|
172
|
+
height: CHOICE_ICON_SIZE,
|
|
173
|
+
boxSizing: "border-box",
|
|
174
|
+
borderStyle: "solid",
|
|
175
|
+
borderWidth: 2,
|
|
176
|
+
|
|
177
|
+
// The default icons have letters in them. Style those letters.
|
|
178
|
+
fontFamily: styleConstants.baseFontFamily,
|
|
179
|
+
// NOTE(emily): We explicitly set the font weight instead of using the
|
|
180
|
+
// "bold font family" so that characters which fall back to the default
|
|
181
|
+
// font get bolded too.
|
|
182
|
+
fontWeight: "bold",
|
|
183
|
+
fontSize: 12,
|
|
184
|
+
|
|
185
|
+
// Center the icon wrapper.
|
|
186
|
+
display: "flex",
|
|
187
|
+
alignItems: "center",
|
|
188
|
+
justifyContent: "center",
|
|
189
|
+
// HACK(emily): I don't know why adding this line height makes the text
|
|
190
|
+
// appear centered better than any other value, but it does. In
|
|
191
|
+
// particular, at large zoom levels this line height does almost
|
|
192
|
+
// nothing, but at the default size this shifts the letter down one
|
|
193
|
+
// pixel so it is much better centered.
|
|
194
|
+
lineHeight: "1px",
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
circleCorrect: {
|
|
198
|
+
fontSize: CHOICE_ICON_SIZE,
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
circleIncorrect: {
|
|
202
|
+
fontSize: CHOICE_ICON_SIZE,
|
|
203
|
+
borderColor: styleConstants.gray68,
|
|
204
|
+
color: styleConstants.gray68,
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
circleIncorrectAnswered: {
|
|
208
|
+
backgroundColor: Color.red,
|
|
209
|
+
borderColor: Color.red,
|
|
210
|
+
color: Color.white,
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
|
|
87
214
|
export default ChoiceIcon;
|
|
@@ -4,27 +4,23 @@
|
|
|
4
4
|
import {StyleSheet, css} from "aphrodite";
|
|
5
5
|
import * as React from "react";
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import {CHOICE_ICON_SIZE} from "./shared-styles.js";
|
|
8
8
|
|
|
9
|
-
// The "cross-out line" SVG is a bit bigger than the
|
|
9
|
+
// The "cross-out line" SVG is a bit bigger than the choice icon, to provide
|
|
10
10
|
// extra space for the cute rounded line cap.
|
|
11
11
|
const CROSS_OUT_LINE_PADDING = 2;
|
|
12
|
-
const CROSS_OUT_LINE_SIZE =
|
|
13
|
-
const SAT_CROSS_OUT_LINE_SIZE = SAT_ICON_SIZE + CROSS_OUT_LINE_PADDING * 2;
|
|
12
|
+
const CROSS_OUT_LINE_SIZE = CHOICE_ICON_SIZE + CROSS_OUT_LINE_PADDING * 2;
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
15
|
* The "cross-out line" that appears over the icon when the choice has been
|
|
17
16
|
* `crossedOut`.
|
|
18
17
|
*/
|
|
19
|
-
function CrossOutLine(props: {color: string,
|
|
20
|
-
const crossOutLineSize = props.sat
|
|
21
|
-
? SAT_CROSS_OUT_LINE_SIZE
|
|
22
|
-
: CROSS_OUT_LINE_SIZE;
|
|
18
|
+
function CrossOutLine(props: {color: string, ...}): React.Node {
|
|
23
19
|
return (
|
|
24
20
|
<svg
|
|
25
|
-
width={
|
|
26
|
-
height={
|
|
27
|
-
viewBox={`0 0 ${
|
|
21
|
+
width={CROSS_OUT_LINE_SIZE}
|
|
22
|
+
height={CROSS_OUT_LINE_SIZE}
|
|
23
|
+
viewBox={`0 0 ${CROSS_OUT_LINE_SIZE} ${CROSS_OUT_LINE_SIZE}`}
|
|
28
24
|
className={css(styles.crossOutLine)}
|
|
29
25
|
data-test-id="choice-icon__cross-out-line"
|
|
30
26
|
>
|
|
@@ -33,8 +29,8 @@ function CrossOutLine(props: {color: string, sat?: boolean, ...}): React.Node {
|
|
|
33
29
|
// We don't quite go to the _very_ corner, because the cute
|
|
34
30
|
// rounded line cap needs to bleed into our padding.
|
|
35
31
|
x1={CROSS_OUT_LINE_PADDING}
|
|
36
|
-
x2={
|
|
37
|
-
y1={
|
|
32
|
+
x2={CROSS_OUT_LINE_SIZE - CROSS_OUT_LINE_PADDING}
|
|
33
|
+
y1={CROSS_OUT_LINE_SIZE - CROSS_OUT_LINE_PADDING}
|
|
38
34
|
y2={CROSS_OUT_LINE_PADDING}
|
|
39
35
|
stroke={props.color}
|
|
40
36
|
strokeWidth="2"
|
|
@@ -3,8 +3,7 @@ import {StyleSheet} from "aphrodite";
|
|
|
3
3
|
|
|
4
4
|
import type {StyleDeclaration} from "aphrodite";
|
|
5
5
|
|
|
6
|
-
const
|
|
7
|
-
const LIBRARY_ICON_SIZE = 24;
|
|
6
|
+
const CHOICE_ICON_SIZE = 24;
|
|
8
7
|
|
|
9
8
|
const styles: StyleDeclaration = StyleSheet.create({
|
|
10
9
|
iconWrapper: {
|
|
@@ -13,5 +12,5 @@ const styles: StyleDeclaration = StyleSheet.create({
|
|
|
13
12
|
},
|
|
14
13
|
});
|
|
15
14
|
|
|
16
|
-
export {
|
|
15
|
+
export {CHOICE_ICON_SIZE};
|
|
17
16
|
export default styles;
|
|
@@ -15,7 +15,6 @@ import _ from "underscore";
|
|
|
15
15
|
|
|
16
16
|
import Icon from "../../components/icon.jsx";
|
|
17
17
|
import {ApiOptions, ClassNames} from "../../perseus-api.jsx";
|
|
18
|
-
import * as styleConstants from "../../styles/constants.js";
|
|
19
18
|
import mediaQueries from "../../styles/media-queries.js";
|
|
20
19
|
|
|
21
20
|
import getA11yText from "./choice-a11y-text.js";
|
|
@@ -126,41 +125,20 @@ function Choice(props: ChoicePropsWithForwardRef): React.Node {
|
|
|
126
125
|
onChange({checked: updatedChecked, crossedOut: updatedCrossedOut});
|
|
127
126
|
}
|
|
128
127
|
|
|
129
|
-
// HACK: while most of the styling for rendering SAT items is handled
|
|
130
|
-
// via aphrodite, we also need to assign normal CSS classnames here to
|
|
131
|
-
// special-case the coloring of MathJax formulas (see .MathJax .math in
|
|
132
|
-
// stylesheets/task-package/tasks.less)
|
|
133
|
-
const satCorrectChoice = apiOptions.satStyling && reviewMode && correct;
|
|
134
|
-
const satIncorrectChecked =
|
|
135
|
-
apiOptions.satStyling && reviewMode && !correct && checked;
|
|
136
128
|
const descriptionClassName = classNames(
|
|
137
129
|
"description",
|
|
138
|
-
|
|
139
|
-
satIncorrectChecked && "sat-incorrect",
|
|
140
|
-
css(
|
|
141
|
-
!apiOptions.satStyling && styles.description,
|
|
142
|
-
apiOptions.satStyling && styles.satDescription,
|
|
143
|
-
satCorrectChoice && checked && styles.satDescriptionCorrectChecked,
|
|
144
|
-
satIncorrectChecked && styles.satDescriptionIncorrectChecked,
|
|
145
|
-
),
|
|
130
|
+
css(styles.description),
|
|
146
131
|
);
|
|
147
132
|
|
|
148
133
|
const rationaleClassName = classNames(
|
|
149
134
|
"perseus-radio-rationale-content",
|
|
150
|
-
css(
|
|
151
|
-
styles.rationale,
|
|
152
|
-
!apiOptions.satStyling && styles.nonSatRationale,
|
|
153
|
-
apiOptions.satStyling && styles.satReviewRationale,
|
|
154
|
-
),
|
|
135
|
+
css(styles.rationale),
|
|
155
136
|
);
|
|
156
137
|
|
|
157
|
-
// We want to show the choices as dimmed out when the choices are
|
|
158
|
-
//
|
|
159
|
-
// we also don't want to do this when we're in review mode in the
|
|
138
|
+
// We want to show the choices as dimmed out when the choices are disabled.
|
|
139
|
+
// However, we don't want to do this when we're in review mode in the
|
|
160
140
|
// content library.
|
|
161
|
-
const showDimmed =
|
|
162
|
-
(!apiOptions.satStyling && !reviewMode && apiOptions.readOnly) ||
|
|
163
|
-
crossedOut;
|
|
141
|
+
const showDimmed = (!reviewMode && apiOptions.readOnly) || crossedOut;
|
|
164
142
|
|
|
165
143
|
const letter = getChoiceLetter(pos);
|
|
166
144
|
const a11yText = getA11yText(
|
|
@@ -255,12 +233,6 @@ function Choice(props: ChoicePropsWithForwardRef): React.Node {
|
|
|
255
233
|
showCorrectness={showCorrectness}
|
|
256
234
|
multipleSelect={multipleSelect}
|
|
257
235
|
reviewMode={reviewMode}
|
|
258
|
-
product={
|
|
259
|
-
apiOptions.satStyling ? "sat" : "library"
|
|
260
|
-
}
|
|
261
|
-
primaryProductColor={
|
|
262
|
-
apiOptions.styling?.primaryProductColor
|
|
263
|
-
}
|
|
264
236
|
previouslyAnswered={previouslyAnswered}
|
|
265
237
|
/>
|
|
266
238
|
<span
|
|
@@ -278,7 +250,6 @@ function Choice(props: ChoicePropsWithForwardRef): React.Node {
|
|
|
278
250
|
crossedOut={crossedOut}
|
|
279
251
|
previouslyAnswered={previouslyAnswered}
|
|
280
252
|
reviewMode={reviewMode}
|
|
281
|
-
satStyling={apiOptions.satStyling}
|
|
282
253
|
/>
|
|
283
254
|
</div>
|
|
284
255
|
<div>{content}</div>
|
|
@@ -392,28 +363,8 @@ const styles = StyleSheet.create({
|
|
|
392
363
|
width: "100%",
|
|
393
364
|
},
|
|
394
365
|
|
|
395
|
-
satDescription: {
|
|
396
|
-
display: "block",
|
|
397
|
-
position: "relative",
|
|
398
|
-
boxSizing: "border-box",
|
|
399
|
-
cursor: "pointer",
|
|
400
|
-
marginLeft: 0,
|
|
401
|
-
padding: "17px 14px 17px 0",
|
|
402
|
-
},
|
|
403
|
-
|
|
404
|
-
satDescriptionCorrectChecked: {
|
|
405
|
-
backgroundColor: styleConstants.satCorrectBackgroundColor,
|
|
406
|
-
},
|
|
407
|
-
|
|
408
|
-
satDescriptionIncorrectChecked: {
|
|
409
|
-
backgroundColor: styleConstants.satIncorrectBackgroundColor,
|
|
410
|
-
},
|
|
411
|
-
|
|
412
366
|
rationale: {
|
|
413
367
|
display: "block",
|
|
414
|
-
},
|
|
415
|
-
|
|
416
|
-
nonSatRationale: {
|
|
417
368
|
padding: intermediateCheckboxPadding,
|
|
418
369
|
paddingTop: 0,
|
|
419
370
|
marginLeft: 54,
|
|
@@ -422,11 +373,6 @@ const styles = StyleSheet.create({
|
|
|
422
373
|
paddingTop: 0,
|
|
423
374
|
},
|
|
424
375
|
},
|
|
425
|
-
|
|
426
|
-
satReviewRationale: {
|
|
427
|
-
marginTop: 13,
|
|
428
|
-
marginLeft: 45,
|
|
429
|
-
},
|
|
430
376
|
});
|
|
431
377
|
|
|
432
378
|
type ExportProps = $Diff<React.ElementConfig<typeof Choice>, WithForwardRef>;
|
|
@@ -22,7 +22,6 @@ type OptionStatusProps = {|
|
|
|
22
22
|
// Did the user select this option as the answer earlier?
|
|
23
23
|
previouslyAnswered: boolean,
|
|
24
24
|
reviewMode: boolean,
|
|
25
|
-
satStyling?: boolean,
|
|
26
25
|
|};
|
|
27
26
|
|
|
28
27
|
function renderText(
|
|
@@ -50,19 +49,8 @@ function renderText(
|
|
|
50
49
|
}
|
|
51
50
|
|
|
52
51
|
function OptionStatus(props: OptionStatusProps): React.Node {
|
|
53
|
-
const {
|
|
54
|
-
|
|
55
|
-
correct,
|
|
56
|
-
crossedOut,
|
|
57
|
-
previouslyAnswered,
|
|
58
|
-
reviewMode,
|
|
59
|
-
satStyling,
|
|
60
|
-
} = props;
|
|
61
|
-
|
|
62
|
-
// Option status is exclued for SAT
|
|
63
|
-
if (satStyling) {
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
52
|
+
const {checked, correct, crossedOut, previouslyAnswered, reviewMode} =
|
|
53
|
+
props;
|
|
66
54
|
|
|
67
55
|
// Option status is shown only in review mode, or for incorrectly
|
|
68
56
|
// answered items.
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
// @flow
|
|
2
|
-
import * as React from "react";
|
|
3
|
-
|
|
4
|
-
import PossibleAnswers from "../possible-answers.jsx";
|
|
5
|
-
|
|
6
|
-
type StoryArgs = {||};
|
|
7
|
-
|
|
8
|
-
type Story = {|
|
|
9
|
-
title: string,
|
|
10
|
-
|};
|
|
11
|
-
|
|
12
|
-
export default ({
|
|
13
|
-
title: "Perseus/Components/Possible Answers",
|
|
14
|
-
}: Story);
|
|
15
|
-
|
|
16
|
-
export const EmptyPropsObject = (args: StoryArgs): React.Node => {
|
|
17
|
-
return <PossibleAnswers />;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export const ThreeSampleAnswers = (args: StoryArgs): React.Node => {
|
|
21
|
-
return <PossibleAnswers answers={["Answer 1", "Answer 2", "Answer 3"]} />;
|
|
22
|
-
};
|