@khanacademy/perseus 0.4.2 → 1.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/.eslintrc.js +11 -0
- package/CHANGELOG.md +47 -0
- package/dist/es/index.js +1078 -1299
- package/dist/es/index.js.map +1 -1
- package/dist/index.js +1077 -1304
- package/dist/index.js.map +1 -1
- package/dist/strings.js +0 -4
- package/package.json +9 -8
- package/src/multi-items/__tests__/__snapshots__/multi-renderer_test.js.snap +110 -110
- package/src/perseus-api.jsx +10 -36
- package/src/perseus-types.js +0 -138
- package/src/styles/constants.js +0 -2
- package/src/styles/variables.less +0 -2
- package/src/types.js +18 -35
- package/src/util.js +0 -6
- package/src/version.js +1 -1
- package/src/widgets/__testdata__/base-radio_testdata.js +22 -0
- package/src/widgets/__tests__/__snapshots__/group_test.jsx.snap +110 -110
- package/src/widgets/__tests__/__snapshots__/radio_test.jsx.snap +1136 -1160
- package/src/widgets/__tests__/radio/base-radio_test.jsx +243 -41
- package/src/widgets/__tests__/radio/choice-icon_test.jsx +67 -0
- package/src/widgets/__tests__/radio/choice_test.jsx +201 -0
- package/src/widgets/__tests__/radio/focus-ring_test.jsx +23 -0
- package/src/widgets/__tests__/radio/option-status_test.jsx +65 -0
- package/src/widgets/passage/__tests__/passage-markdown_test.jsx +0 -9
- package/src/widgets/passage/get-line-height-for-node.js +4 -5
- package/src/widgets/passage/passage-markdown.jsx +176 -120
- package/src/widgets/passage.jsx +48 -46
- package/src/widgets/radio/__stories__/base-radio.stories.jsx +164 -17
- package/src/widgets/radio/__stories__/choice-icon.stories.jsx +202 -111
- package/src/widgets/radio/__stories__/choice-none-above.stories.jsx +21 -6
- package/src/widgets/radio/__stories__/choice.stories.jsx +138 -0
- package/src/widgets/radio/__stories__/focus-ring.stories.jsx +29 -23
- package/src/widgets/radio/__stories__/option-status.stories.jsx +93 -99
- package/src/widgets/radio/base-radio.jsx +483 -556
- package/src/widgets/radio/choice-icon/choice-icon.jsx +155 -0
- package/src/widgets/radio/choice-icon/cross-out-line.jsx +55 -0
- package/src/widgets/radio/choice-icon/library-choice-icon.jsx +219 -0
- package/src/widgets/radio/choice-icon/sat-choice-icon.jsx +129 -0
- package/src/widgets/radio/choice-icon/shared-styles.js +17 -0
- package/src/widgets/radio/choice-none-above.jsx +49 -40
- package/src/widgets/radio/choice.jsx +282 -353
- package/src/widgets/radio/choice.jsx.fixture.js +0 -18
- package/src/widgets/radio/focus-ring.jsx +29 -22
- package/src/widgets/radio/option-status.jsx +58 -36
- package/src/widgets/radio/widget.jsx +181 -136
- package/src/widgets/radio.jsx +16 -17
- package/src/widgets/radio/__stories__/cross-out-button.stories.jsx +0 -64
- package/src/widgets/radio/choice-icon.jsx +0 -469
- package/src/widgets/radio/cross-out-button.jsx +0 -134
package/src/widgets/passage.jsx
CHANGED
|
@@ -18,9 +18,14 @@ import PassageMarkdown from "./passage/passage-markdown.jsx";
|
|
|
18
18
|
import type {SerializedHighlightSet} from "../components/highlighting/types.js";
|
|
19
19
|
import type {ChangeableProps} from "../mixins/changeable.jsx";
|
|
20
20
|
import type {PerseusPassageWidgetOptions} from "../perseus-types.js";
|
|
21
|
-
import type {
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
import type {
|
|
22
|
+
PerseusScore,
|
|
23
|
+
WidgetExports,
|
|
24
|
+
WidgetInfo,
|
|
25
|
+
WidgetProps,
|
|
26
|
+
} from "../types";
|
|
27
|
+
import type {ParseState} from "./passage/passage-markdown.jsx";
|
|
28
|
+
import type {SingleASTNode} from "@khanacademy/simple-markdown";
|
|
24
29
|
|
|
25
30
|
// A fake paragraph to measure the line height of the passage. In CSS we always
|
|
26
31
|
// set the line height to 22 pixels, but when using the browser zoom feature,
|
|
@@ -28,8 +33,8 @@ type JQueryCollection = $FlowFixMe;
|
|
|
28
33
|
// 22 pixels.
|
|
29
34
|
class LineHeightMeasurer extends React.Component<{...}> {
|
|
30
35
|
_cachedLineHeight: number;
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
body: ?HTMLDivElement;
|
|
37
|
+
end: ?HTMLDivElement;
|
|
33
38
|
|
|
34
39
|
measureLineHeight(): number {
|
|
35
40
|
if (typeof this._cachedLineHeight !== "number") {
|
|
@@ -40,15 +45,22 @@ class LineHeightMeasurer extends React.Component<{...}> {
|
|
|
40
45
|
}
|
|
41
46
|
|
|
42
47
|
forceMeasureLineHeight() {
|
|
48
|
+
const body = this.body;
|
|
49
|
+
const end = this.end;
|
|
50
|
+
|
|
51
|
+
if (!body || !end) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
43
55
|
// Add some text which magically fills an entire line.
|
|
44
|
-
|
|
56
|
+
body.textContent = " \u0080";
|
|
45
57
|
|
|
46
58
|
// Now, the line height is the difference between the top of the
|
|
47
59
|
// second line and the top of the first line.
|
|
48
|
-
this._cachedLineHeight = getLineHeightForNode(
|
|
60
|
+
this._cachedLineHeight = getLineHeightForNode(body, end);
|
|
49
61
|
|
|
50
62
|
// Clear out the first line so it doesn't overlap the passage.
|
|
51
|
-
|
|
63
|
+
body.textContent = "";
|
|
52
64
|
}
|
|
53
65
|
|
|
54
66
|
render(): React.Node {
|
|
@@ -56,10 +68,10 @@ class LineHeightMeasurer extends React.Component<{...}> {
|
|
|
56
68
|
<div className={css(styles.measurer)}>
|
|
57
69
|
<div>
|
|
58
70
|
<div
|
|
59
|
-
ref={(
|
|
71
|
+
ref={(ref) => (this.body = ref)}
|
|
60
72
|
className="paragraph"
|
|
61
73
|
/>
|
|
62
|
-
<div ref={(
|
|
74
|
+
<div ref={(ref) => (this.end = ref)} />
|
|
63
75
|
</div>
|
|
64
76
|
</div>
|
|
65
77
|
);
|
|
@@ -87,9 +99,12 @@ type RenderProps = {|
|
|
|
87
99
|
showLineNumbers: PerseusPassageWidgetOptions["showLineNumbers"],
|
|
88
100
|
|};
|
|
89
101
|
|
|
102
|
+
type FindWidgetsCallback = (id: string, widgetInfo: WidgetInfo) => boolean;
|
|
103
|
+
|
|
90
104
|
type PassageProps = {|
|
|
91
105
|
...ChangeableProps,
|
|
92
106
|
...WidgetProps<RenderProps, Rubric>,
|
|
107
|
+
findWidgets: (FindWidgetsCallback) => $ReadOnlyArray<Passage>,
|
|
93
108
|
highlights: SerializedHighlightSet,
|
|
94
109
|
|};
|
|
95
110
|
|
|
@@ -108,13 +123,6 @@ type PassageState = {|
|
|
|
108
123
|
stylesAreApplied: boolean,
|
|
109
124
|
|};
|
|
110
125
|
|
|
111
|
-
// State kept track of by the PassageMarkdown parser.
|
|
112
|
-
type PassageParseState = {
|
|
113
|
-
firstQuestionRef: ?any,
|
|
114
|
-
firstSentenceRef: ?any,
|
|
115
|
-
...
|
|
116
|
-
};
|
|
117
|
-
|
|
118
126
|
// Information about a passage reference, used in inter-widgets.
|
|
119
127
|
type Reference = {
|
|
120
128
|
startLine: number,
|
|
@@ -124,8 +132,9 @@ type Reference = {
|
|
|
124
132
|
};
|
|
125
133
|
|
|
126
134
|
class Passage extends React.Component<PassageProps, PassageState> {
|
|
135
|
+
_contentRef: ?HTMLDivElement;
|
|
136
|
+
_lineHeightMeasurerRef: ?LineHeightMeasurer;
|
|
127
137
|
_onResize: () => {...};
|
|
128
|
-
_lineHeightMeasurer: LineHeightMeasurer;
|
|
129
138
|
_stylesAppiedTimer: TimeoutID;
|
|
130
139
|
|
|
131
140
|
static defaultProps: DefaultPassageProps = {
|
|
@@ -157,7 +166,7 @@ class Passage extends React.Component<PassageProps, PassageState> {
|
|
|
157
166
|
// height changes we expect are subpixel changes when the user
|
|
158
167
|
// zooms in/out, and the only way to listen for zoom events is to
|
|
159
168
|
// listen for resize events.
|
|
160
|
-
this.
|
|
169
|
+
this._lineHeightMeasurerRef?.forceMeasureLineHeight();
|
|
161
170
|
this._updateState();
|
|
162
171
|
}, 500);
|
|
163
172
|
window.addEventListener("resize", this._onResize);
|
|
@@ -231,9 +240,8 @@ class Passage extends React.Component<PassageProps, PassageState> {
|
|
|
231
240
|
}
|
|
232
241
|
|
|
233
242
|
_measureLines(): number {
|
|
234
|
-
|
|
235
|
-
const
|
|
236
|
-
const contentsHeight = $renderer.height();
|
|
243
|
+
const renderer = ReactDOM.findDOMNode(this._contentRef);
|
|
244
|
+
const contentsHeight: number = $(renderer).height();
|
|
237
245
|
const lineHeight = this._getLineHeight();
|
|
238
246
|
const nLines = Math.round(contentsHeight / lineHeight);
|
|
239
247
|
return nLines;
|
|
@@ -259,7 +267,7 @@ class Passage extends React.Component<PassageProps, PassageState> {
|
|
|
259
267
|
}
|
|
260
268
|
|
|
261
269
|
_getLineHeight(): number {
|
|
262
|
-
return this.
|
|
270
|
+
return this._lineHeightMeasurerRef?.measureLineHeight() || 0;
|
|
263
271
|
}
|
|
264
272
|
|
|
265
273
|
getLineCount(): number {
|
|
@@ -275,11 +283,15 @@ class Passage extends React.Component<PassageProps, PassageState> {
|
|
|
275
283
|
* These are functions to support the passage refs inter-widgets feature
|
|
276
284
|
* where other widgets can fetch the line numbers of a reference inside of
|
|
277
285
|
* a passage.
|
|
286
|
+
*
|
|
287
|
+
* todo(matthewc): The refs are created by PassageMarkdown's refStart and refEnd,
|
|
288
|
+
* somehow bubbling up to Passage's `this.refs`. This runs against
|
|
289
|
+
* current best practices for refs by using string refs, but also
|
|
290
|
+
* by breaking our expectation of explicit data flow.
|
|
278
291
|
*/
|
|
279
292
|
|
|
280
293
|
_getStartRefLineNumber(referenceNumber: number): ?number {
|
|
281
294
|
const refRef = PassageMarkdown.START_REF_PREFIX + referenceNumber;
|
|
282
|
-
// eslint-disable-next-line react/no-string-refs
|
|
283
295
|
const ref = this.refs[refRef];
|
|
284
296
|
if (!ref) {
|
|
285
297
|
return null;
|
|
@@ -305,7 +317,6 @@ class Passage extends React.Component<PassageProps, PassageState> {
|
|
|
305
317
|
|
|
306
318
|
_getEndRefLineNumber(referenceNumber: number): ?number {
|
|
307
319
|
const refRef = PassageMarkdown.END_REF_PREFIX + referenceNumber;
|
|
308
|
-
// eslint-disable-next-line react/no-string-refs
|
|
309
320
|
const ref = this.refs[refRef];
|
|
310
321
|
if (!ref) {
|
|
311
322
|
return null;
|
|
@@ -320,8 +331,8 @@ class Passage extends React.Component<PassageProps, PassageState> {
|
|
|
320
331
|
// use the ref itself.
|
|
321
332
|
$refText = $ref;
|
|
322
333
|
}
|
|
323
|
-
const height = $refText.height();
|
|
324
|
-
const vPos = $refText.offset().top;
|
|
334
|
+
const height: number = $refText.height();
|
|
335
|
+
const vPos: number = $refText.offset().top;
|
|
325
336
|
|
|
326
337
|
let line = this._convertPosToLineNumber(vPos + height);
|
|
327
338
|
if (height === 0) {
|
|
@@ -338,9 +349,8 @@ class Passage extends React.Component<PassageProps, PassageState> {
|
|
|
338
349
|
}
|
|
339
350
|
|
|
340
351
|
_convertPosToLineNumber(absoluteVPos: number): number {
|
|
341
|
-
|
|
342
|
-
const
|
|
343
|
-
const relativeVPos = absoluteVPos - $content.offset().top;
|
|
352
|
+
const content = ReactDOM.findDOMNode(this._contentRef);
|
|
353
|
+
const relativeVPos = absoluteVPos - $(content).offset().top;
|
|
344
354
|
const lineHeight = this._getLineHeight();
|
|
345
355
|
|
|
346
356
|
const line = Math.round(relativeVPos / lineHeight);
|
|
@@ -349,7 +359,6 @@ class Passage extends React.Component<PassageProps, PassageState> {
|
|
|
349
359
|
|
|
350
360
|
_getRefContent(referenceNumber: number): ?string {
|
|
351
361
|
const refRef = PassageMarkdown.START_REF_PREFIX + referenceNumber;
|
|
352
|
-
// eslint-disable-next-line react/no-string-refs
|
|
353
362
|
const ref = this.refs[refRef];
|
|
354
363
|
if (!ref) {
|
|
355
364
|
return null;
|
|
@@ -403,7 +412,7 @@ class Passage extends React.Component<PassageProps, PassageState> {
|
|
|
403
412
|
* Functions to render the passage widget.
|
|
404
413
|
*/
|
|
405
414
|
|
|
406
|
-
_renderInstructions(parseState:
|
|
415
|
+
_renderInstructions(parseState: ParseState): React.Element<any> {
|
|
407
416
|
const firstQuestionNumber = parseState.firstQuestionRef;
|
|
408
417
|
const firstSentenceRef = parseState.firstSentenceRef;
|
|
409
418
|
|
|
@@ -443,7 +452,7 @@ class Passage extends React.Component<PassageProps, PassageState> {
|
|
|
443
452
|
return JIPT.useJIPT && this.props.passageText.indexOf("crwdns") !== -1;
|
|
444
453
|
}
|
|
445
454
|
|
|
446
|
-
_renderContent(parsed:
|
|
455
|
+
_renderContent(parsed: Array<SingleASTNode>): React.Element<any> {
|
|
447
456
|
// Wait until Aphrodite styles are applied before enabling highlights,
|
|
448
457
|
// so that we measure the correct positions.
|
|
449
458
|
const enabled = this.state.stylesAreApplied;
|
|
@@ -459,13 +468,9 @@ class Passage extends React.Component<PassageProps, PassageState> {
|
|
|
459
468
|
}
|
|
460
469
|
serializedHighlights={this.props.highlights}
|
|
461
470
|
>
|
|
462
|
-
{
|
|
463
|
-
<div ref="content">
|
|
471
|
+
<div ref={(ref) => (this._contentRef = ref)}>
|
|
464
472
|
<LineHeightMeasurer
|
|
465
|
-
|
|
466
|
-
// "e (null) This type is incompatible with this._lineHeightMeasurer (LineHeightMeasurer)"
|
|
467
|
-
// $FlowFixMe[incompatible-type](0.52.0->0.53.0)
|
|
468
|
-
ref={(e) => (this._lineHeightMeasurer = e)}
|
|
473
|
+
ref={(ref) => (this._lineHeightMeasurerRef = ref)}
|
|
469
474
|
/>
|
|
470
475
|
{PassageMarkdown.output(parsed)}
|
|
471
476
|
</div>
|
|
@@ -479,14 +484,14 @@ class Passage extends React.Component<PassageProps, PassageState> {
|
|
|
479
484
|
return !isEmpty;
|
|
480
485
|
}
|
|
481
486
|
|
|
482
|
-
_renderFootnotes(): React.
|
|
487
|
+
_renderFootnotes(): React.Node {
|
|
483
488
|
const rawContent = this.props.footnotes;
|
|
484
489
|
const parsed = PassageMarkdown.parse(rawContent);
|
|
485
490
|
return PassageMarkdown.output(parsed);
|
|
486
491
|
}
|
|
487
492
|
|
|
488
493
|
render(): React.Element<"div"> {
|
|
489
|
-
let lineNumbers
|
|
494
|
+
let lineNumbers: $ReadOnlyArray<React.Node>;
|
|
490
495
|
const nLines = this.state.nLines;
|
|
491
496
|
if (this.props.showLineNumbers && nLines) {
|
|
492
497
|
// lineN is the line number in the current passage
|
|
@@ -507,10 +512,7 @@ class Passage extends React.Component<PassageProps, PassageState> {
|
|
|
507
512
|
});
|
|
508
513
|
}
|
|
509
514
|
|
|
510
|
-
const parseState:
|
|
511
|
-
firstSentenceRef: null,
|
|
512
|
-
firstQuestionRef: null,
|
|
513
|
-
};
|
|
515
|
+
const parseState: ParseState = PassageMarkdown.getInitialParseState();
|
|
514
516
|
|
|
515
517
|
// Replace the vertical double quote characters quoting text with
|
|
516
518
|
// an unicode left and right double quote characters. This would
|
|
@@ -521,7 +523,7 @@ class Passage extends React.Component<PassageProps, PassageState> {
|
|
|
521
523
|
re,
|
|
522
524
|
"\u201c$2\u201d",
|
|
523
525
|
);
|
|
524
|
-
const parsedContent
|
|
526
|
+
const parsedContent = PassageMarkdown.parse(
|
|
525
527
|
doubleQuoteParsedContent,
|
|
526
528
|
parseState,
|
|
527
529
|
);
|
|
@@ -1,40 +1,187 @@
|
|
|
1
1
|
// @flow
|
|
2
|
+
import {action} from "@storybook/addon-actions";
|
|
2
3
|
import * as React from "react";
|
|
3
4
|
|
|
4
|
-
import
|
|
5
|
+
import {generateChoice} from "../../__testdata__/base-radio_testdata.js";
|
|
6
|
+
import BaseRadio from "../base-radio.jsx";
|
|
5
7
|
|
|
6
|
-
type StoryArgs = {
|
|
8
|
+
type StoryArgs = {|
|
|
9
|
+
multipleSelect: boolean,
|
|
10
|
+
editMode: boolean,
|
|
11
|
+
countChoices: boolean,
|
|
12
|
+
|};
|
|
7
13
|
|
|
8
14
|
type Story = {|
|
|
9
15
|
title: string,
|
|
16
|
+
args: StoryArgs,
|
|
10
17
|
|};
|
|
11
18
|
|
|
12
19
|
export default ({
|
|
13
20
|
title: "Perseus/Widgets/Radio/Base Radio",
|
|
21
|
+
args: {
|
|
22
|
+
multipleSelect: false,
|
|
23
|
+
editMode: false,
|
|
24
|
+
countChoices: false,
|
|
25
|
+
},
|
|
14
26
|
}: Story);
|
|
15
27
|
|
|
16
|
-
const
|
|
28
|
+
const defaultProps = {
|
|
17
29
|
apiOptions: {
|
|
18
30
|
satStyling: false,
|
|
19
|
-
styling: {
|
|
20
|
-
radioStyleVersion: "final",
|
|
21
|
-
},
|
|
22
31
|
},
|
|
23
32
|
choices: [
|
|
24
|
-
{
|
|
33
|
+
generateChoice({
|
|
25
34
|
content: "Content 1",
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
{
|
|
35
|
+
}),
|
|
36
|
+
generateChoice({
|
|
29
37
|
content: "Content 2",
|
|
30
|
-
|
|
31
|
-
|
|
38
|
+
}),
|
|
39
|
+
generateChoice({
|
|
40
|
+
content: "Content 3",
|
|
41
|
+
correct: true,
|
|
42
|
+
}),
|
|
43
|
+
generateChoice({
|
|
44
|
+
isNoneOfTheAbove: true,
|
|
45
|
+
}),
|
|
32
46
|
],
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
47
|
+
deselectEnabled: false,
|
|
48
|
+
editMode: false,
|
|
49
|
+
labelWrap: false,
|
|
50
|
+
countChoices: false,
|
|
51
|
+
numCorrect: 1,
|
|
52
|
+
multipleSelect: false,
|
|
53
|
+
|
|
54
|
+
// A callback indicating that this choice has changed. Its argument is
|
|
55
|
+
// an object with two keys: `checked` and `crossedOut`. Each contains
|
|
56
|
+
// an array of boolean values, specifying the new checked and
|
|
57
|
+
// crossed-out value of each choice.
|
|
58
|
+
onChange: action("changed"),
|
|
59
|
+
|
|
60
|
+
// Whether this widget was the most recently used widget in this
|
|
61
|
+
// Renderer. Determines whether we'll auto-scroll the page upon
|
|
62
|
+
// entering review mode.
|
|
63
|
+
isLastUsedWidget: false,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const Interactive = (args: StoryArgs): React.Node => {
|
|
67
|
+
const overwrittenProps = {...defaultProps, ...args};
|
|
68
|
+
return <BaseRadio {...overwrittenProps} />;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const DefaultSingleSelect = (args: StoryArgs): React.Node => {
|
|
72
|
+
const overwrittenProps = {...defaultProps, multipleSelect: false};
|
|
73
|
+
return <BaseRadio {...overwrittenProps} />;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export const DefaultMultipleSelect = (args: StoryArgs): React.Node => {
|
|
77
|
+
const overwrittenProps = {...defaultProps, multipleSelect: true};
|
|
78
|
+
return <BaseRadio {...overwrittenProps} />;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const DefaultMultipleSelectCountChoices = (
|
|
82
|
+
args: StoryArgs,
|
|
83
|
+
): React.Node => {
|
|
84
|
+
const overwrittenProps = {
|
|
85
|
+
...defaultProps,
|
|
86
|
+
multipleSelect: true,
|
|
87
|
+
numCorrect: 2,
|
|
88
|
+
countChoices: true,
|
|
89
|
+
};
|
|
90
|
+
return <BaseRadio {...overwrittenProps} />;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export const SingleSelected = (args: StoryArgs): React.Node => {
|
|
94
|
+
const choices = Array(4)
|
|
95
|
+
.fill(null)
|
|
96
|
+
.map((_, i) => generateChoice({content: `Choice ${i + 1}`}));
|
|
97
|
+
choices[1].checked = true;
|
|
98
|
+
|
|
99
|
+
const overwrittenProps = {...defaultProps, multipleSelect: false, choices};
|
|
100
|
+
return <BaseRadio {...overwrittenProps} />;
|
|
36
101
|
};
|
|
37
102
|
|
|
38
|
-
export const
|
|
39
|
-
|
|
103
|
+
export const MultipleSelected = (args: StoryArgs): React.Node => {
|
|
104
|
+
const choices = Array(4)
|
|
105
|
+
.fill(null)
|
|
106
|
+
.map((_, i) => generateChoice({content: `Choice ${i + 1}`}));
|
|
107
|
+
choices[1].checked = true;
|
|
108
|
+
choices[2].checked = true;
|
|
109
|
+
|
|
110
|
+
const overwrittenProps = {...defaultProps, multipleSelect: true, choices};
|
|
111
|
+
return <BaseRadio {...overwrittenProps} />;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export const SingleKitchenSink = (args: StoryArgs): React.Node => {
|
|
115
|
+
const choices = Array(4)
|
|
116
|
+
.fill(null)
|
|
117
|
+
.map((_, i) => {
|
|
118
|
+
const choice = generateChoice({
|
|
119
|
+
content: `Choice ${i + 1}`,
|
|
120
|
+
rationale: "This is a neat rationale",
|
|
121
|
+
hasRationale: true,
|
|
122
|
+
showRationale: true,
|
|
123
|
+
correct: false,
|
|
124
|
+
showCorrectness: true,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
return choice;
|
|
128
|
+
});
|
|
129
|
+
choices[1].checked = true;
|
|
130
|
+
choices[2].correct = true;
|
|
131
|
+
|
|
132
|
+
const rubricChoices = choices.map(({correct}) => ({
|
|
133
|
+
// note(matthew): reviewModeRubric.choices requires content,
|
|
134
|
+
// but I don't see how it's getting used and Flow gets mad
|
|
135
|
+
// when I use choice.content because it's not a string.
|
|
136
|
+
// reviewModeRubric could probably use a look over.
|
|
137
|
+
content: "",
|
|
138
|
+
correct,
|
|
139
|
+
}));
|
|
140
|
+
|
|
141
|
+
const overwrittenProps = {
|
|
142
|
+
...defaultProps,
|
|
143
|
+
multipleSelect: false,
|
|
144
|
+
reviewModeRubric: {choices: rubricChoices},
|
|
145
|
+
choices,
|
|
146
|
+
};
|
|
147
|
+
return <BaseRadio {...overwrittenProps} />;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
export const MultipleKitchenSink = (args: StoryArgs): React.Node => {
|
|
151
|
+
const choices = Array(4)
|
|
152
|
+
.fill(null)
|
|
153
|
+
.map((_, i) => {
|
|
154
|
+
const choice = generateChoice({
|
|
155
|
+
content: `Choice ${i + 1}`,
|
|
156
|
+
rationale: "This is a neat rationale",
|
|
157
|
+
hasRationale: true,
|
|
158
|
+
showRationale: true,
|
|
159
|
+
correct: false,
|
|
160
|
+
showCorrectness: true,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
return choice;
|
|
164
|
+
});
|
|
165
|
+
choices[1].checked = true;
|
|
166
|
+
choices[2].checked = true;
|
|
167
|
+
choices[2].correct = true;
|
|
168
|
+
choices[3].correct = true;
|
|
169
|
+
|
|
170
|
+
const rubricChoices = choices.map((c) => ({
|
|
171
|
+
// note(matthew): reviewModeRubric.choices requires content,
|
|
172
|
+
// but I don't see how it's getting used and Flow gets mad
|
|
173
|
+
// when I use choice.content because it's not a string.
|
|
174
|
+
// reviewModeRubric could probably use a look over.
|
|
175
|
+
content: "",
|
|
176
|
+
correct: c.correct,
|
|
177
|
+
}));
|
|
178
|
+
|
|
179
|
+
const overwrittenProps = {
|
|
180
|
+
...defaultProps,
|
|
181
|
+
multipleSelect: true,
|
|
182
|
+
numCorrect: 2,
|
|
183
|
+
reviewModeRubric: {choices: rubricChoices},
|
|
184
|
+
choices,
|
|
185
|
+
};
|
|
186
|
+
return <BaseRadio {...overwrittenProps} />;
|
|
40
187
|
};
|