@khanacademy/perseus 0.4.3 → 1.1.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.
Files changed (71) hide show
  1. package/.eslintrc.js +6 -1
  2. package/CHANGELOG.md +45 -0
  3. package/dist/es/index.css +15 -8
  4. package/dist/es/index.js +1214 -1404
  5. package/dist/es/index.js.map +1 -1
  6. package/dist/index.css +15 -8
  7. package/dist/index.js +1217 -1411
  8. package/dist/index.js.map +1 -1
  9. package/dist/strings.js +0 -4
  10. package/package.json +9 -8
  11. package/src/components/number-input.jsx +2 -2
  12. package/src/components/simple-keypad-input.jsx +18 -21
  13. package/src/index.js +1 -1
  14. package/src/multi-items/__tests__/__snapshots__/multi-renderer_test.js.snap +426 -135
  15. package/src/multi-items/__tests__/multi-renderer_test.js +9 -9
  16. package/src/perseus-api.jsx +10 -36
  17. package/src/perseus-types.js +18 -145
  18. package/src/styles/constants.js +0 -2
  19. package/src/styles/perseus-renderer.less +8 -2
  20. package/src/styles/variables.less +0 -2
  21. package/src/styles/widgets/passage.less +9 -5
  22. package/src/types.js +18 -35
  23. package/src/util/answer-types.js +2 -3
  24. package/src/util/math.js +3 -10
  25. package/src/util.js +0 -6
  26. package/src/version.js +1 -1
  27. package/src/widgets/__stories__/numeric-input.stories.jsx +43 -2
  28. package/src/widgets/__testdata__/base-radio_testdata.js +22 -0
  29. package/src/widgets/__tests__/__snapshots__/definition_test.js.snap +4 -4
  30. package/src/widgets/__tests__/__snapshots__/group_test.jsx.snap +291 -130
  31. package/src/widgets/__tests__/__snapshots__/radio_test.jsx.snap +3293 -1415
  32. package/src/widgets/__tests__/group_test.jsx +4 -4
  33. package/src/widgets/__tests__/numeric-input_test.jsx +12 -8
  34. package/src/widgets/__tests__/passage_test.jsx +81 -0
  35. package/src/widgets/__tests__/radio/base-radio_test.jsx +291 -41
  36. package/src/widgets/__tests__/radio/choice-icon_test.jsx +67 -0
  37. package/src/widgets/__tests__/radio/choice_test.jsx +302 -0
  38. package/src/widgets/__tests__/radio/focus-ring_test.jsx +23 -0
  39. package/src/widgets/__tests__/radio/option-status_test.jsx +65 -0
  40. package/src/widgets/__tests__/radio_test.jsx +39 -15
  41. package/src/widgets/definition.jsx +1 -1
  42. package/src/widgets/group.jsx +84 -102
  43. package/src/widgets/input-number.jsx +6 -1
  44. package/src/widgets/numeric-input.jsx +87 -80
  45. package/src/widgets/passage/__tests__/passage-markdown_test.jsx +0 -9
  46. package/src/widgets/passage/get-line-height-for-node.js +4 -5
  47. package/src/widgets/passage/passage-markdown.jsx +176 -120
  48. package/src/widgets/passage.jsx +48 -46
  49. package/src/widgets/radio/__stories__/base-radio.stories.jsx +164 -17
  50. package/src/widgets/radio/__stories__/choice-icon.stories.jsx +202 -111
  51. package/src/widgets/radio/__stories__/choice-none-above.stories.jsx +21 -6
  52. package/src/widgets/radio/__stories__/choice.stories.jsx +138 -0
  53. package/src/widgets/radio/__stories__/focus-ring.stories.jsx +29 -23
  54. package/src/widgets/radio/__stories__/option-status.stories.jsx +93 -99
  55. package/src/widgets/radio/base-radio.jsx +485 -556
  56. package/src/widgets/radio/choice-a11y-text.js +62 -0
  57. package/src/widgets/radio/choice-icon/choice-icon.jsx +87 -0
  58. package/src/widgets/radio/choice-icon/cross-out-line.jsx +55 -0
  59. package/src/widgets/radio/choice-icon/library-choice-icon.jsx +214 -0
  60. package/src/widgets/radio/choice-icon/sat-choice-icon.jsx +120 -0
  61. package/src/widgets/radio/choice-icon/shared-styles.js +17 -0
  62. package/src/widgets/radio/choice-none-above.jsx +49 -40
  63. package/src/widgets/radio/choice.jsx +319 -347
  64. package/src/widgets/radio/choice.jsx.fixture.js +0 -18
  65. package/src/widgets/radio/focus-ring.jsx +29 -22
  66. package/src/widgets/radio/option-status.jsx +58 -36
  67. package/src/widgets/radio/widget.jsx +181 -136
  68. package/src/widgets/radio.jsx +16 -17
  69. package/src/widgets/radio/__stories__/cross-out-button.stories.jsx +0 -64
  70. package/src/widgets/radio/choice-icon.jsx +0 -469
  71. package/src/widgets/radio/cross-out-button.jsx +0 -134
@@ -1,157 +1,248 @@
1
1
  // @flow
2
2
  import * as React from "react";
3
3
 
4
- import ChoiceIcon from "../choice-icon.jsx";
5
-
6
- type StoryArgs = {||};
7
-
8
- type Story = {|
9
- title: string,
4
+ import ChoiceIcon from "../choice-icon/choice-icon.jsx";
5
+
6
+ type StoryArgs = {|
7
+ pos: number,
8
+ checked: boolean,
9
+ crossedOut: boolean,
10
+ focused: boolean,
11
+ hovered: boolean,
12
+ pressed: boolean,
13
+ correct: boolean,
14
+ showCorrectness: boolean,
15
+ product: "sat" | "library",
16
+ multipleSelect: boolean,
17
+ reviewMode: boolean,
18
+ previouslyAnswered: boolean,
19
+ transparentBackground: boolean,
20
+ primaryProductColor?: string,
10
21
  |};
11
22
 
12
- export default ({
13
- title: "Perseus/Widgets/Radio/Choice Icon",
14
- }: Story);
15
-
16
- const defaultObject = {
17
- crossedOut: false,
23
+ const defaultProps = {
24
+ pos: 0,
18
25
  checked: false,
19
- correct: true,
26
+ crossedOut: false,
27
+ focused: false,
28
+ hovered: false,
20
29
  pressed: false,
21
- previouslyAnswered: false,
30
+ correct: false,
31
+ showCorrectness: false,
22
32
  product: "library",
23
- focused: false,
33
+ multipleSelect: false,
24
34
  reviewMode: false,
25
- showCorrectness: false,
26
- };
27
-
28
- // Note: the SAT icon is different from the general library
29
- // icon displayed on KA. Therefore props like pressed,
30
- // previouslyAnswered and focused do not apply here.
31
- // That said, the props of the component still want them as they are
32
- // currently defined, so we give them values anyway.
33
- const satObject = {
34
- crossedOut: false,
35
- pressed: false,
36
- checked: false,
37
- focused: false,
38
- correct: true,
39
35
  previouslyAnswered: false,
40
- product: "sat",
41
- reviewMode: false,
42
- showCorrectness: false,
36
+ transparentBackground: false,
43
37
  };
44
38
 
45
- export const Position0Sat = (args: StoryArgs): React.Node => {
46
- return <ChoiceIcon {...satObject} pos={0} />;
39
+ const defaultSATProps = {
40
+ ...defaultProps,
41
+ product: "sat",
47
42
  };
48
43
 
49
- export const Position1CrossedOutSat = (args: StoryArgs): React.Node => {
50
- return <ChoiceIcon {...satObject} pos={1} crossedOut={true} />;
44
+ export default {
45
+ title: "Perseus/Widgets/Radio/Choice Icon",
46
+ argTypes: {
47
+ pos: {
48
+ control: {type: "number", min: 0, max: 25, step: 1},
49
+ },
50
+ product: {
51
+ options: ["library", "sat"],
52
+ control: {type: "radio"},
53
+ },
54
+ },
55
+ args: defaultProps,
51
56
  };
52
57
 
53
- export const Position2SelectedSat = (args: StoryArgs): React.Node => {
54
- return (
55
- <ChoiceIcon {...satObject} pos={2} checked={true} crossedOut={false} />
56
- );
58
+ export const Interactive = (args: StoryArgs): React.Node => {
59
+ return <ChoiceIcon {...args} />;
57
60
  };
58
61
 
59
- export const Position2SelectedReviewModeSat = (args: StoryArgs): React.Node => {
62
+ export const Default = (args: StoryArgs): React.Node => {
60
63
  return (
61
- <ChoiceIcon
62
- {...satObject}
63
- pos={2}
64
- checked={true}
65
- crossedOut={false}
66
- reviewMode={true}
67
- />
64
+ <>
65
+ <p>Library</p>
66
+ <ChoiceIcon {...defaultProps} />
67
+ <ChoiceIcon {...defaultProps} multipleSelect={true} />
68
+ <p>SAT</p>
69
+ <ChoiceIcon {...defaultSATProps} />
70
+ <ChoiceIcon {...defaultSATProps} multipleSelect={true} />
71
+ </>
68
72
  );
69
73
  };
70
74
 
71
- export const Position2SelectedReviewModeCrossedOutSat = (
72
- args: StoryArgs,
73
- ): React.Node => {
75
+ export const Focused = (args: StoryArgs): React.Node => {
74
76
  return (
75
- <ChoiceIcon
76
- {...satObject}
77
- pos={2}
78
- checked={false}
79
- crossedOut={true}
80
- reviewMode={true}
81
- />
77
+ <>
78
+ <p>Library</p>
79
+ <ChoiceIcon {...defaultProps} focused={true} />
80
+ <ChoiceIcon
81
+ {...defaultProps}
82
+ focused={true}
83
+ multipleSelect={true}
84
+ />
85
+ <p>SAT</p>
86
+ <ChoiceIcon {...defaultSATProps} focused={true} />
87
+ <ChoiceIcon
88
+ {...defaultSATProps}
89
+ focused={true}
90
+ multipleSelect={true}
91
+ />
92
+ </>
82
93
  );
83
94
  };
84
95
 
85
- export const Position0 = (args: StoryArgs): React.Node => {
86
- return <ChoiceIcon {...defaultObject} pos={0} reviewMode={false} />;
87
- };
88
-
89
- export const Position1 = (args: StoryArgs): React.Node => {
90
- return <ChoiceIcon {...defaultObject} pos={1} reviewMode={false} />;
91
- };
92
-
93
- export const Position2 = (args: StoryArgs): React.Node => {
94
- return <ChoiceIcon {...defaultObject} pos={2} reviewMode={false} />;
95
- };
96
-
97
96
  export const Checked = (args: StoryArgs): React.Node => {
98
97
  return (
99
- <ChoiceIcon
100
- {...defaultObject}
101
- pos={0}
102
- checked={true}
103
- reviewMode={false}
104
- />
98
+ <>
99
+ <p>Library</p>
100
+ <ChoiceIcon {...defaultProps} checked={true} />
101
+ <ChoiceIcon
102
+ {...defaultProps}
103
+ checked={true}
104
+ multipleSelect={true}
105
+ />
106
+ <p>SAT</p>
107
+ <ChoiceIcon {...defaultSATProps} checked={true} />
108
+ <ChoiceIcon
109
+ {...defaultSATProps}
110
+ checked={true}
111
+ multipleSelect={true}
112
+ />
113
+ </>
105
114
  );
106
115
  };
107
116
 
108
- export const Focused = (args: StoryArgs): React.Node => {
117
+ export const CrossedOut = (args: StoryArgs): React.Node => {
109
118
  return (
110
- <ChoiceIcon
111
- {...defaultObject}
112
- focused={true}
113
- pos={0}
114
- checked={true}
115
- reviewMode={false}
116
- />
119
+ <>
120
+ <p>Library</p>
121
+ <ChoiceIcon {...defaultProps} crossedOut={true} />
122
+ <ChoiceIcon
123
+ {...defaultProps}
124
+ crossedOut={true}
125
+ multipleSelect={true}
126
+ />
127
+ <p>SAT</p>
128
+ <ChoiceIcon {...defaultSATProps} crossedOut={true} />
129
+ <ChoiceIcon
130
+ {...defaultSATProps}
131
+ crossedOut={true}
132
+ multipleSelect={true}
133
+ />
134
+ </>
117
135
  );
118
136
  };
119
137
 
120
- export const ShowCorrectnessCorrect = (args: StoryArgs): React.Node => {
138
+ export const Correct = (args: StoryArgs): React.Node => {
121
139
  return (
122
- <ChoiceIcon
123
- {...defaultObject}
124
- pos={0}
125
- showCorrectness={true}
126
- checked={true}
127
- reviewMode={false}
128
- />
140
+ <>
141
+ <p>Library</p>
142
+ <ChoiceIcon
143
+ {...defaultProps}
144
+ checked={true}
145
+ correct={true}
146
+ showCorrectness={true}
147
+ reviewMode={true}
148
+ />
149
+ <ChoiceIcon
150
+ {...defaultProps}
151
+ checked={true}
152
+ correct={true}
153
+ showCorrectness={true}
154
+ reviewMode={true}
155
+ multipleSelect={true}
156
+ />
157
+ <p>SAT</p>
158
+ <ChoiceIcon
159
+ {...defaultSATProps}
160
+ checked={true}
161
+ correct={true}
162
+ showCorrectness={true}
163
+ reviewMode={true}
164
+ />
165
+ <ChoiceIcon
166
+ {...defaultSATProps}
167
+ checked={true}
168
+ correct={true}
169
+ showCorrectness={true}
170
+ reviewMode={true}
171
+ multipleSelect={true}
172
+ />
173
+ </>
129
174
  );
130
175
  };
131
176
 
132
- export const ShowCorrectnessIncorrect = (args: StoryArgs): React.Node => {
177
+ export const Incorrect = (args: StoryArgs): React.Node => {
133
178
  return (
134
- <ChoiceIcon
135
- {...defaultObject}
136
- pos={0}
137
- correct={false}
138
- showCorrectness={true}
139
- reviewMode={false}
140
- />
179
+ <>
180
+ <p>Library</p>
181
+ <ChoiceIcon
182
+ {...defaultProps}
183
+ checked={true}
184
+ correct={false}
185
+ showCorrectness={true}
186
+ reviewMode={true}
187
+ />
188
+ <ChoiceIcon
189
+ {...defaultProps}
190
+ checked={true}
191
+ correct={false}
192
+ showCorrectness={true}
193
+ reviewMode={true}
194
+ multipleSelect={true}
195
+ />
196
+ <p>SAT</p>
197
+ <ChoiceIcon
198
+ {...defaultSATProps}
199
+ checked={true}
200
+ correct={false}
201
+ showCorrectness={true}
202
+ reviewMode={true}
203
+ />
204
+ <ChoiceIcon
205
+ {...defaultSATProps}
206
+ checked={true}
207
+ correct={false}
208
+ showCorrectness={true}
209
+ reviewMode={true}
210
+ multipleSelect={true}
211
+ />
212
+ </>
141
213
  );
142
214
  };
143
215
 
144
- export const CrossedOut = (args: StoryArgs): React.Node => {
145
- return <ChoiceIcon {...defaultObject} pos={0} crossedOut={true} />;
146
- };
147
-
148
- export const CrossedOutPressed = (args: StoryArgs): React.Node => {
216
+ export const AllPositions = (args: StoryArgs): React.Node => {
217
+ const allLetters = Array(26).fill();
149
218
  return (
150
- <ChoiceIcon
151
- {...defaultObject}
152
- pos={0}
153
- crossedOut={true}
154
- pressed={true}
155
- />
219
+ <>
220
+ <p>Library</p>
221
+ {allLetters.map((_, i: number) => (
222
+ <ChoiceIcon {...defaultProps} pos={i} key={"choice" + i} />
223
+ ))}
224
+ <br />
225
+ {allLetters.map((_, i: number) => (
226
+ <ChoiceIcon
227
+ {...defaultProps}
228
+ pos={i}
229
+ multipleSelect={true}
230
+ key={"choice" + i}
231
+ />
232
+ ))}
233
+ <p>SAT</p>
234
+ {allLetters.map((_, i: number) => (
235
+ <ChoiceIcon {...defaultSATProps} pos={i} key={"choice" + i} />
236
+ ))}
237
+ <br />
238
+ {allLetters.map((_, i: number) => (
239
+ <ChoiceIcon
240
+ {...defaultSATProps}
241
+ pos={i}
242
+ multipleSelect={true}
243
+ key={"choice" + i}
244
+ />
245
+ ))}
246
+ </>
156
247
  );
157
248
  };
@@ -1,12 +1,10 @@
1
1
  // @flow
2
+ import {action} from "@storybook/addon-actions";
2
3
  import * as React from "react";
3
4
 
4
5
  import ChoiceNoneAbove from "../choice-none-above.jsx";
5
6
 
6
7
  type StoryArgs = {|
7
- // Eventually this?
8
- // ...React.ElementConfig<ChoiceNoneAbove>,
9
- className: string,
10
8
  content: React.Node,
11
9
  showContent?: boolean,
12
10
  |};
@@ -17,10 +15,27 @@ type ChoiceNoneAboveStory = {|
17
15
  |};
18
16
 
19
17
  export default ({
20
- title: "Perseus/Widgets/Radio/None of the Above",
21
- args: {className: "", showContent: false, content: ""},
18
+ title: "Perseus/Widgets/Radio/Choice None of the Above",
19
+ args: {showContent: false, content: "This is a possible choice"},
22
20
  }: ChoiceNoneAboveStory);
23
21
 
22
+ const ChoiceDefaults = {
23
+ checked: false,
24
+ rationale: "This is a good rational",
25
+ correct: true,
26
+ disabled: false,
27
+ pos: 0,
28
+ reviewMode: false,
29
+ showRationale: false,
30
+ showCorrectness: false,
31
+ multipleSelect: false,
32
+ crossedOut: false,
33
+ previouslyAnswered: false,
34
+ apiOptions: {},
35
+ onChange: action("changed"),
36
+ };
37
+
24
38
  export const Example = (args: StoryArgs): React.Node => {
25
- return <ChoiceNoneAbove {...args} />;
39
+ const combineProps = {...ChoiceDefaults, ...args};
40
+ return <ChoiceNoneAbove {...combineProps} />;
26
41
  };
@@ -0,0 +1,138 @@
1
+ // @flow
2
+ import {action} from "@storybook/addon-actions";
3
+ import * as React from "react";
4
+
5
+ import Choice from "../choice.jsx";
6
+
7
+ type StoryArgs = {|
8
+ checked: boolean,
9
+ rationale: React.Node,
10
+ content: React.Node,
11
+ correct: boolean,
12
+ disabled: boolean,
13
+ pos: number,
14
+ reviewMode: boolean,
15
+ showRationale: boolean,
16
+ showCorrectness: boolean,
17
+ multipleSelect: boolean,
18
+ crossedOut: boolean,
19
+ previouslyAnswered: boolean,
20
+ onChange: (newValues: {checked: boolean, crossedOut: boolean}) => void,
21
+ |};
22
+
23
+ type Story = {|
24
+ title: string,
25
+ args: StoryArgs,
26
+ |};
27
+
28
+ const defaultProps = {
29
+ checked: false,
30
+ rationale: "This is a good rationale",
31
+ content: "This is a possible choice",
32
+ correct: true,
33
+ disabled: false,
34
+ pos: 0,
35
+ reviewMode: false,
36
+ showRationale: false,
37
+ showCorrectness: false,
38
+ multipleSelect: false,
39
+ crossedOut: false,
40
+ previouslyAnswered: false,
41
+ onChange: action("changed"),
42
+ };
43
+
44
+ export default ({
45
+ title: "Perseus/Widgets/Radio/Choice",
46
+ args: defaultProps,
47
+ }: Story);
48
+
49
+ export const Interactive = (args: StoryArgs): React.Node => {
50
+ return <Choice {...args} />;
51
+ };
52
+
53
+ export const Checked = (args: StoryArgs): React.Node => {
54
+ const sharedProps = {
55
+ ...defaultProps,
56
+ checked: true,
57
+ showCorrectness: true,
58
+ };
59
+ const correctProps = {
60
+ ...sharedProps,
61
+ correct: true,
62
+ content: "This choice is correct",
63
+ };
64
+ const incorrectProps = {
65
+ ...sharedProps,
66
+ correct: false,
67
+ content: "This choice is incorrect",
68
+ };
69
+ return (
70
+ <>
71
+ <Choice {...correctProps} />
72
+ <Choice {...incorrectProps} />
73
+ </>
74
+ );
75
+ };
76
+
77
+ export const ReviewMode = (args: StoryArgs): React.Node => {
78
+ const sharedProps = {
79
+ ...defaultProps,
80
+ showCorrectness: true,
81
+ reviewMode: true,
82
+ multipleSelect: true,
83
+ };
84
+ return (
85
+ <>
86
+ <Choice
87
+ {...sharedProps}
88
+ correct={true}
89
+ checked={true}
90
+ content="This choice was correct and checked"
91
+ />
92
+ <Choice
93
+ {...sharedProps}
94
+ correct={true}
95
+ content="This choice was also correct and not checked"
96
+ />
97
+ <Choice
98
+ {...sharedProps}
99
+ correct={false}
100
+ checked={true}
101
+ content="This choice was incorrect and checked"
102
+ />
103
+ <Choice
104
+ {...sharedProps}
105
+ correct={false}
106
+ content="This choice was also incorrect and not checked"
107
+ />
108
+ </>
109
+ );
110
+ };
111
+
112
+ export const Rationale = (args: StoryArgs): React.Node => {
113
+ const sharedProps = {
114
+ ...defaultProps,
115
+ checked: true,
116
+ showCorrectness: true,
117
+ showRationale: true,
118
+ reviewMode: true,
119
+ };
120
+ const correctProps = {
121
+ ...sharedProps,
122
+ correct: true,
123
+ content: "This choice is correct",
124
+ rationale: "It was correct because of the way it is",
125
+ };
126
+ const incorrectProps = {
127
+ ...sharedProps,
128
+ correct: false,
129
+ content: "This choice is incorrect",
130
+ rationale: "It was incorrect because of the way it is",
131
+ };
132
+ return (
133
+ <>
134
+ <Choice {...correctProps} />
135
+ <Choice {...incorrectProps} />
136
+ </>
137
+ );
138
+ };
@@ -1,38 +1,44 @@
1
1
  // @flow
2
2
  import * as React from "react";
3
3
 
4
+ import * as styleConstants from "../../../styles/constants.js";
4
5
  import FocusRing from "../focus-ring.jsx";
5
6
 
6
- type StoryArgs = {||};
7
+ type StoryArgs = {|
8
+ children: React.Node,
9
+ color: string,
10
+ visible: boolean,
11
+ multipleSelect: 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/Focus Ring",
21
+ args: {
22
+ children: "",
23
+ color: styleConstants.kaGreen,
24
+ visible: true,
25
+ multipleSelect: false,
26
+ },
14
27
  }: Story);
15
28
 
16
- export const EmptyPropsObject = (args: StoryArgs): React.Node => {
17
- return <FocusRing />;
18
- };
19
-
20
- export const VisibleSetToTrue = (args: StoryArgs): React.Node => {
21
- return <FocusRing visible={true} />;
22
- };
23
-
24
- export const VisibleSetToFalse = (args: StoryArgs): React.Node => {
25
- return <FocusRing visible={false} />;
26
- };
27
-
28
- export const ColorRed = (args: StoryArgs): React.Node => {
29
- return <FocusRing color="red" />;
30
- };
31
-
32
- export const SingleSpanChild = (args: StoryArgs): React.Node => {
33
- return (
34
- <FocusRing>
35
- <span>test</span>
36
- </FocusRing>
37
- );
29
+ export const Interactive = (args: StoryArgs): React.Node => {
30
+ // faux choice is just for demonstration
31
+ const fauxChoiceStyles = {
32
+ height: "20px",
33
+ width: "20px",
34
+ background: args.color,
35
+ borderRadius: args.multipleSelect ? "3px" : "50%",
36
+ };
37
+
38
+ const customArgs = {
39
+ ...args,
40
+ children: args.children || <div style={fauxChoiceStyles} />,
41
+ };
42
+
43
+ return <FocusRing {...customArgs} />;
38
44
  };