@khanacademy/perseus 1.4.1 → 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 +12 -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 +1 -1
- 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
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
// @flow
|
|
2
|
-
/**
|
|
3
|
-
* In review mode (currently only visible in the sat-mission), NumericInput and
|
|
4
|
-
* InputNumber use this component to display the set of correct answers.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import * as i18n from "@khanacademy/wonder-blocks-i18n";
|
|
8
|
-
import PropTypes from "prop-types";
|
|
9
|
-
import * as React from "react";
|
|
10
|
-
import _ from "underscore";
|
|
11
|
-
|
|
12
|
-
class PossibleAnswers extends React.Component<$FlowFixMe> {
|
|
13
|
-
static propTypes = {
|
|
14
|
-
answers: PropTypes.arrayOf(PropTypes.string),
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
render(): React.Node {
|
|
18
|
-
// It's redundant to show duplicate answers.
|
|
19
|
-
// So, remove duplicates from the given list of answer strings.
|
|
20
|
-
const answers = _.uniq(this.props.answers);
|
|
21
|
-
|
|
22
|
-
const answerComponents = _.map(answers, (answer) => {
|
|
23
|
-
// Plus, now that our answers are distinct, we can safely use the
|
|
24
|
-
// answer string as a key.
|
|
25
|
-
return <dd key={answer}>{answer}</dd>;
|
|
26
|
-
});
|
|
27
|
-
return (
|
|
28
|
-
<dl className="perseus-possible-answers">
|
|
29
|
-
<dt>{i18n.doNotTranslate("Correct Answer")}</dt>
|
|
30
|
-
{answerComponents}
|
|
31
|
-
</dl>
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export default PossibleAnswers;
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
@import "../variables.less";
|
|
2
|
-
@import "../util.less";
|
|
3
|
-
|
|
4
|
-
// HACK(michaelpolyak): As the Definition widget may be rendered within an
|
|
5
|
-
// Article or an Exercise, we need to support the "legacy" way of styling for
|
|
6
|
-
// the specific responsive rendering context. In order to match the surrounding
|
|
7
|
-
// Perseus content styling, we copy and apply styles defined in
|
|
8
|
-
// `perseus-renderer.less`, `articles.less` (both of which are now moved into
|
|
9
|
-
// Perseus).
|
|
10
|
-
|
|
11
|
-
// Desktop exercise styles
|
|
12
|
-
.framework-perseus:not(.perseus-article):not(.perseus-mobile) {
|
|
13
|
-
.perseus-widget-definition {
|
|
14
|
-
.legacy-typography;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// Mobile exercise styles
|
|
19
|
-
.framework-perseus:not(.perseus-article).perseus-mobile {
|
|
20
|
-
.perseus-widget-definition {
|
|
21
|
-
@desktop-width-min: 1200px;
|
|
22
|
-
@tablet-width-max: (@desktop-width-min - 1);
|
|
23
|
-
|
|
24
|
-
// Phone styles
|
|
25
|
-
@media (max-width: @pure-sm-max) {
|
|
26
|
-
font-size: 18px;
|
|
27
|
-
line-height: 1.4;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Tablet styles
|
|
31
|
-
@media (min-width: @pure-md-min) and (max-width: @tablet-width-max) {
|
|
32
|
-
font-size: 20px;
|
|
33
|
-
line-height: 1.5;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// "Desktop" (resolution) styles (while rendering on a mobile device,
|
|
37
|
-
// ie. `this.props.apiOptions.isMobile` is `true`)
|
|
38
|
-
@media (min-width: @desktop-width-min) {
|
|
39
|
-
font-size: 22px;
|
|
40
|
-
line-height: 1.4;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Default article styles
|
|
46
|
-
.bibliotron-article.framework-perseus.perseus-article {
|
|
47
|
-
.perseus-widget-definition {
|
|
48
|
-
font-size: 18px;
|
|
49
|
-
line-height: 1.4;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Desktop article styles
|
|
54
|
-
.bibliotron-article.framework-perseus.perseus-article:not(.perseus-mobile) {
|
|
55
|
-
.perseus-widget-definition {
|
|
56
|
-
font-size: 20px;
|
|
57
|
-
line-height: 30px;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Mobile article styles
|
|
62
|
-
.bibliotron-article.framework-perseus.perseus-article.perseus-mobile {
|
|
63
|
-
.perseus-widget-definition {
|
|
64
|
-
@desktop-width-min: 1200px;
|
|
65
|
-
@tablet-width-max: (@desktop-width-min - 1);
|
|
66
|
-
|
|
67
|
-
// Phone styles
|
|
68
|
-
@media (max-width: @pure-sm-max) {
|
|
69
|
-
font-size: 18px;
|
|
70
|
-
line-height: 1.4;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Tablet styles
|
|
74
|
-
@media (min-width: @pure-md-min) and (max-width: @tablet-width-max) {
|
|
75
|
-
font-size: 20px;
|
|
76
|
-
line-height: 1.5;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// "Desktop" (resolution) styles (while rendering on a mobile device,
|
|
80
|
-
// ie. `this.props.apiOptions.isMobile` is `true`)
|
|
81
|
-
@media (min-width: @desktop-width-min) {
|
|
82
|
-
font-size: 22px;
|
|
83
|
-
line-height: 1.4;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
// @flow
|
|
2
|
-
import {action} from "@storybook/addon-actions";
|
|
3
|
-
import * as React from "react";
|
|
4
|
-
|
|
5
|
-
import {generateChoice} from "../../__testdata__/base-radio_testdata.js";
|
|
6
|
-
import BaseRadio from "../base-radio.jsx";
|
|
7
|
-
|
|
8
|
-
type StoryArgs = {|
|
|
9
|
-
multipleSelect: boolean,
|
|
10
|
-
editMode: boolean,
|
|
11
|
-
countChoices: boolean,
|
|
12
|
-
|};
|
|
13
|
-
|
|
14
|
-
type Story = {|
|
|
15
|
-
title: string,
|
|
16
|
-
args: StoryArgs,
|
|
17
|
-
|};
|
|
18
|
-
|
|
19
|
-
export default ({
|
|
20
|
-
title: "Perseus/Widgets/Radio/SAT Base Radio",
|
|
21
|
-
args: {
|
|
22
|
-
multipleSelect: false,
|
|
23
|
-
editMode: false,
|
|
24
|
-
countChoices: false,
|
|
25
|
-
},
|
|
26
|
-
}: Story);
|
|
27
|
-
|
|
28
|
-
const defaultProps = {
|
|
29
|
-
apiOptions: {
|
|
30
|
-
satStyling: true,
|
|
31
|
-
},
|
|
32
|
-
choices: [
|
|
33
|
-
generateChoice({
|
|
34
|
-
content: "Content 1",
|
|
35
|
-
}),
|
|
36
|
-
generateChoice({
|
|
37
|
-
content: "Content 2",
|
|
38
|
-
}),
|
|
39
|
-
generateChoice({
|
|
40
|
-
content: "Content 3",
|
|
41
|
-
correct: true,
|
|
42
|
-
}),
|
|
43
|
-
generateChoice({
|
|
44
|
-
isNoneOfTheAbove: true,
|
|
45
|
-
}),
|
|
46
|
-
],
|
|
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 SingleSelectWithNothingSelected = (
|
|
72
|
-
args: StoryArgs,
|
|
73
|
-
): React.Node => {
|
|
74
|
-
const overwrittenProps = {...defaultProps, multipleSelect: false};
|
|
75
|
-
return <BaseRadio {...overwrittenProps} />;
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
export const MultipleSelectWithNothingSelected = (
|
|
79
|
-
args: StoryArgs,
|
|
80
|
-
): React.Node => {
|
|
81
|
-
const overwrittenProps = {...defaultProps, multipleSelect: true};
|
|
82
|
-
return <BaseRadio {...overwrittenProps} />;
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
export const MultipleSelectWithCountChoicesLabel = (
|
|
86
|
-
args: StoryArgs,
|
|
87
|
-
): React.Node => {
|
|
88
|
-
const overwrittenProps = {
|
|
89
|
-
...defaultProps,
|
|
90
|
-
multipleSelect: true,
|
|
91
|
-
numCorrect: 2,
|
|
92
|
-
countChoices: true,
|
|
93
|
-
};
|
|
94
|
-
return <BaseRadio {...overwrittenProps} />;
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
export const SingleSelected = (args: StoryArgs): React.Node => {
|
|
98
|
-
const choices = Array(4)
|
|
99
|
-
.fill(null)
|
|
100
|
-
.map((_, i) => generateChoice({content: `Choice ${i + 1}`}));
|
|
101
|
-
choices[1].checked = true;
|
|
102
|
-
|
|
103
|
-
const overwrittenProps = {...defaultProps, multipleSelect: false, choices};
|
|
104
|
-
return <BaseRadio {...overwrittenProps} />;
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
export const MultipleSelected = (args: StoryArgs): React.Node => {
|
|
108
|
-
const choices = Array(4)
|
|
109
|
-
.fill(null)
|
|
110
|
-
.map((_, i) => generateChoice({content: `Choice ${i + 1}`}));
|
|
111
|
-
choices[1].checked = true;
|
|
112
|
-
choices[2].checked = true;
|
|
113
|
-
|
|
114
|
-
const overwrittenProps = {...defaultProps, multipleSelect: true, choices};
|
|
115
|
-
return <BaseRadio {...overwrittenProps} />;
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
export const SingleKitchenSink = (args: StoryArgs): React.Node => {
|
|
119
|
-
const choices = Array(4)
|
|
120
|
-
.fill(null)
|
|
121
|
-
.map((_, i) => {
|
|
122
|
-
const choice = generateChoice({
|
|
123
|
-
content: `Choice ${i + 1}`,
|
|
124
|
-
rationale: "This is a neat rationale",
|
|
125
|
-
hasRationale: true,
|
|
126
|
-
showRationale: true,
|
|
127
|
-
correct: false,
|
|
128
|
-
showCorrectness: true,
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
return choice;
|
|
132
|
-
});
|
|
133
|
-
choices[1].checked = true;
|
|
134
|
-
choices[2].correct = true;
|
|
135
|
-
|
|
136
|
-
const rubricChoices = choices.map(({correct}) => ({
|
|
137
|
-
// note(matthew): reviewModeRubric.choices requires content,
|
|
138
|
-
// but I don't see how it's getting used and Flow gets mad
|
|
139
|
-
// when I use choice.content because it's not a string.
|
|
140
|
-
// reviewModeRubric could probably use a look over.
|
|
141
|
-
content: "",
|
|
142
|
-
correct,
|
|
143
|
-
}));
|
|
144
|
-
|
|
145
|
-
const overwrittenProps = {
|
|
146
|
-
...defaultProps,
|
|
147
|
-
multipleSelect: false,
|
|
148
|
-
reviewModeRubric: {choices: rubricChoices},
|
|
149
|
-
choices,
|
|
150
|
-
};
|
|
151
|
-
return <BaseRadio {...overwrittenProps} />;
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
export const MultipleKitchenSink = (args: StoryArgs): React.Node => {
|
|
155
|
-
const choices = Array(4)
|
|
156
|
-
.fill(null)
|
|
157
|
-
.map((_, i) => {
|
|
158
|
-
const choice = generateChoice({
|
|
159
|
-
content: `Choice ${i + 1}`,
|
|
160
|
-
rationale: "This is a neat rationale",
|
|
161
|
-
hasRationale: true,
|
|
162
|
-
showRationale: true,
|
|
163
|
-
correct: false,
|
|
164
|
-
showCorrectness: true,
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
return choice;
|
|
168
|
-
});
|
|
169
|
-
choices[1].checked = true;
|
|
170
|
-
choices[2].checked = true;
|
|
171
|
-
choices[2].correct = true;
|
|
172
|
-
choices[3].correct = true;
|
|
173
|
-
|
|
174
|
-
const rubricChoices = choices.map((c) => ({
|
|
175
|
-
// note(matthew): reviewModeRubric.choices requires content,
|
|
176
|
-
// but I don't see how it's getting used and Flow gets mad
|
|
177
|
-
// when I use choice.content because it's not a string.
|
|
178
|
-
// reviewModeRubric could probably use a look over.
|
|
179
|
-
content: "",
|
|
180
|
-
correct: c.correct,
|
|
181
|
-
}));
|
|
182
|
-
|
|
183
|
-
const overwrittenProps = {
|
|
184
|
-
...defaultProps,
|
|
185
|
-
multipleSelect: true,
|
|
186
|
-
numCorrect: 2,
|
|
187
|
-
reviewModeRubric: {choices: rubricChoices},
|
|
188
|
-
choices,
|
|
189
|
-
};
|
|
190
|
-
return <BaseRadio {...overwrittenProps} />;
|
|
191
|
-
};
|
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
// @flow
|
|
2
|
-
/* Component for rendering a letter icon in a library radio choice */
|
|
3
|
-
|
|
4
|
-
import Color from "@khanacademy/wonder-blocks-color";
|
|
5
|
-
import {StyleSheet, css} from "aphrodite";
|
|
6
|
-
import * as React from "react";
|
|
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";
|
|
12
|
-
|
|
13
|
-
import CrossOutLine from "./cross-out-line.jsx";
|
|
14
|
-
import sharedStyles, {LIBRARY_ICON_SIZE} from "./shared-styles.js";
|
|
15
|
-
|
|
16
|
-
type LibraryChoiceIconProps = {
|
|
17
|
-
letter: string,
|
|
18
|
-
checked: boolean,
|
|
19
|
-
crossedOut: boolean,
|
|
20
|
-
focused: boolean,
|
|
21
|
-
hovered: boolean,
|
|
22
|
-
pressed: boolean,
|
|
23
|
-
multipleSelect: boolean,
|
|
24
|
-
correct: ?boolean,
|
|
25
|
-
reviewMode: boolean,
|
|
26
|
-
showCorrectness: boolean,
|
|
27
|
-
primaryProductColor: string,
|
|
28
|
-
previouslyAnswered: boolean,
|
|
29
|
-
transparentBackground?: boolean,
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
function ChoiceInner(props: {
|
|
33
|
-
letter: string,
|
|
34
|
-
showCorrectness: boolean,
|
|
35
|
-
correct: ?boolean,
|
|
36
|
-
}) {
|
|
37
|
-
const {letter, showCorrectness, correct} = props;
|
|
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
|
-
primaryProductColor: string,
|
|
66
|
-
correct: ?boolean,
|
|
67
|
-
transparentBackground?: boolean,
|
|
68
|
-
): {
|
|
69
|
-
backgroundColor: ?string,
|
|
70
|
-
borderColor: string,
|
|
71
|
-
color: string,
|
|
72
|
-
borderRadius: number,
|
|
73
|
-
} {
|
|
74
|
-
let backgroundColor;
|
|
75
|
-
let borderColor;
|
|
76
|
-
let color;
|
|
77
|
-
if (!showCorrectness && pressed) {
|
|
78
|
-
borderColor = primaryProductColor;
|
|
79
|
-
color = primaryProductColor;
|
|
80
|
-
backgroundColor = transparentBackground
|
|
81
|
-
? "transparent"
|
|
82
|
-
: styleConstants.white;
|
|
83
|
-
} else if (checked) {
|
|
84
|
-
// Note: kaGreen is not only the default product color,
|
|
85
|
-
// but also the "correctness" color
|
|
86
|
-
const bg =
|
|
87
|
-
showCorrectness && correct ? Color.green : primaryProductColor;
|
|
88
|
-
color = styleConstants.white;
|
|
89
|
-
backgroundColor = bg;
|
|
90
|
-
borderColor = bg;
|
|
91
|
-
} else {
|
|
92
|
-
borderColor = Color.offBlack64;
|
|
93
|
-
color = Color.offBlack64;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// define shape
|
|
97
|
-
let borderRadius;
|
|
98
|
-
if (multipleSelect) {
|
|
99
|
-
borderRadius = 3;
|
|
100
|
-
} else {
|
|
101
|
-
borderRadius = LIBRARY_ICON_SIZE;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return {backgroundColor, borderColor, color, borderRadius};
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function LibraryChoiceIcon(props: LibraryChoiceIconProps): React.Node {
|
|
108
|
-
const {
|
|
109
|
-
checked,
|
|
110
|
-
crossedOut,
|
|
111
|
-
showCorrectness,
|
|
112
|
-
correct,
|
|
113
|
-
focused,
|
|
114
|
-
hovered,
|
|
115
|
-
multipleSelect,
|
|
116
|
-
primaryProductColor,
|
|
117
|
-
previouslyAnswered,
|
|
118
|
-
letter,
|
|
119
|
-
pressed,
|
|
120
|
-
transparentBackground,
|
|
121
|
-
} = props;
|
|
122
|
-
|
|
123
|
-
const dynamicStyles = getDynamicStyles(
|
|
124
|
-
checked,
|
|
125
|
-
showCorrectness,
|
|
126
|
-
pressed,
|
|
127
|
-
multipleSelect,
|
|
128
|
-
primaryProductColor,
|
|
129
|
-
correct,
|
|
130
|
-
transparentBackground,
|
|
131
|
-
);
|
|
132
|
-
|
|
133
|
-
return (
|
|
134
|
-
<div className={css(sharedStyles.iconWrapper)}>
|
|
135
|
-
<FocusRing
|
|
136
|
-
color={primaryProductColor}
|
|
137
|
-
visible={focused || hovered}
|
|
138
|
-
multipleSelect={multipleSelect}
|
|
139
|
-
>
|
|
140
|
-
<div
|
|
141
|
-
style={dynamicStyles}
|
|
142
|
-
data-test-id="choice-icon__library-choice-icon"
|
|
143
|
-
className={css(
|
|
144
|
-
styles.circle,
|
|
145
|
-
showCorrectness && correct && styles.circleCorrect,
|
|
146
|
-
showCorrectness && !correct && styles.circleIncorrect,
|
|
147
|
-
showCorrectness &&
|
|
148
|
-
!correct &&
|
|
149
|
-
(checked || previouslyAnswered) &&
|
|
150
|
-
styles.circleIncorrectAnswered,
|
|
151
|
-
)}
|
|
152
|
-
// used in BaseRadio editMode to check
|
|
153
|
-
// if we actually clicked on the radio icon
|
|
154
|
-
data-is-radio-icon={true}
|
|
155
|
-
>
|
|
156
|
-
<div className={css(styles.innerWrapper)}>
|
|
157
|
-
<ChoiceInner
|
|
158
|
-
letter={letter}
|
|
159
|
-
showCorrectness={showCorrectness}
|
|
160
|
-
correct={correct}
|
|
161
|
-
/>
|
|
162
|
-
</div>
|
|
163
|
-
</div>
|
|
164
|
-
</FocusRing>
|
|
165
|
-
{crossedOut && <CrossOutLine color={dynamicStyles.borderColor} />}
|
|
166
|
-
</div>
|
|
167
|
-
);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const styles = StyleSheet.create({
|
|
171
|
-
innerWrapper: {
|
|
172
|
-
width: LIBRARY_ICON_SIZE,
|
|
173
|
-
height: LIBRARY_ICON_SIZE,
|
|
174
|
-
|
|
175
|
-
// Center the contents of the icon.
|
|
176
|
-
display: "flex",
|
|
177
|
-
alignItems: "center",
|
|
178
|
-
justifyContent: "center",
|
|
179
|
-
},
|
|
180
|
-
circle: {
|
|
181
|
-
// Make the circle
|
|
182
|
-
width: LIBRARY_ICON_SIZE,
|
|
183
|
-
height: LIBRARY_ICON_SIZE,
|
|
184
|
-
boxSizing: "border-box",
|
|
185
|
-
borderStyle: "solid",
|
|
186
|
-
borderWidth: 2,
|
|
187
|
-
|
|
188
|
-
// The default icons have letters in them. Style those letters.
|
|
189
|
-
fontFamily: styleConstants.baseFontFamily,
|
|
190
|
-
// NOTE(emily): We explicitly set the font weight instead of using the
|
|
191
|
-
// "bold font family" so that characters which fall back to the default
|
|
192
|
-
// font get bolded too.
|
|
193
|
-
fontWeight: "bold",
|
|
194
|
-
fontSize: 12,
|
|
195
|
-
|
|
196
|
-
// Center the icon wrapper.
|
|
197
|
-
display: "flex",
|
|
198
|
-
alignItems: "center",
|
|
199
|
-
justifyContent: "center",
|
|
200
|
-
// HACK(emily): I don't know why adding this line height makes the text
|
|
201
|
-
// appear centered better than any other value, but it does. In
|
|
202
|
-
// particular, at large zoom levels this line height does almost
|
|
203
|
-
// nothing, but at the default size this shifts the letter down one
|
|
204
|
-
// pixel so it is much better centered.
|
|
205
|
-
lineHeight: "1px",
|
|
206
|
-
},
|
|
207
|
-
|
|
208
|
-
circleCorrect: {
|
|
209
|
-
fontSize: LIBRARY_ICON_SIZE,
|
|
210
|
-
},
|
|
211
|
-
|
|
212
|
-
circleIncorrect: {
|
|
213
|
-
fontSize: LIBRARY_ICON_SIZE,
|
|
214
|
-
borderColor: styleConstants.gray68,
|
|
215
|
-
color: styleConstants.gray68,
|
|
216
|
-
},
|
|
217
|
-
|
|
218
|
-
circleIncorrectAnswered: {
|
|
219
|
-
backgroundColor: Color.red,
|
|
220
|
-
borderColor: Color.red,
|
|
221
|
-
color: Color.white,
|
|
222
|
-
},
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
export default LibraryChoiceIcon;
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
// @flow
|
|
2
|
-
/* Component for rendering a letter icon in a SAT radio choice */
|
|
3
|
-
|
|
4
|
-
import {StyleSheet, css} from "aphrodite";
|
|
5
|
-
import * as React from "react";
|
|
6
|
-
|
|
7
|
-
import * as styleConstants from "../../../styles/constants.js";
|
|
8
|
-
|
|
9
|
-
import CrossOutLine from "./cross-out-line.jsx";
|
|
10
|
-
import sharedStyles, {SAT_ICON_SIZE} from "./shared-styles.js";
|
|
11
|
-
|
|
12
|
-
type SATChoiceIconProps = {
|
|
13
|
-
letter: string,
|
|
14
|
-
checked: boolean,
|
|
15
|
-
multipleSelect: boolean,
|
|
16
|
-
correct: boolean,
|
|
17
|
-
reviewMode: boolean,
|
|
18
|
-
crossedOut: boolean,
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
// TODO(amy): figure out a better scheme for specifying these
|
|
22
|
-
// styles that isn't such a pain to grok. See some neat ideas
|
|
23
|
-
// from MDR in https://phabricator.khanacademy.org/D35249.
|
|
24
|
-
function constructSATStyles(
|
|
25
|
-
reviewMode: boolean,
|
|
26
|
-
correct: boolean,
|
|
27
|
-
checked: boolean,
|
|
28
|
-
multipleSelect: boolean,
|
|
29
|
-
): {
|
|
30
|
-
color: string,
|
|
31
|
-
backgroundColor: ?string,
|
|
32
|
-
borderColor: string,
|
|
33
|
-
borderRadius: number,
|
|
34
|
-
} {
|
|
35
|
-
let backgroundColor;
|
|
36
|
-
let borderColor = styleConstants.satBlue;
|
|
37
|
-
let color = styleConstants.satBlue;
|
|
38
|
-
if (reviewMode) {
|
|
39
|
-
if (correct) {
|
|
40
|
-
borderColor = styleConstants.satCorrectColor;
|
|
41
|
-
color = checked
|
|
42
|
-
? styleConstants.white
|
|
43
|
-
: styleConstants.satCorrectColor;
|
|
44
|
-
backgroundColor = checked
|
|
45
|
-
? styleConstants.satCorrectColor
|
|
46
|
-
: styleConstants.white;
|
|
47
|
-
} else if (checked) {
|
|
48
|
-
borderColor = styleConstants.satIncorrectColor;
|
|
49
|
-
color = styleConstants.white;
|
|
50
|
-
backgroundColor = styleConstants.satIncorrectColor;
|
|
51
|
-
}
|
|
52
|
-
} else if (checked) {
|
|
53
|
-
color = styleConstants.white;
|
|
54
|
-
backgroundColor = styleConstants.satBlue;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
let borderRadius;
|
|
58
|
-
if (multipleSelect) {
|
|
59
|
-
borderRadius = 3;
|
|
60
|
-
} else {
|
|
61
|
-
borderRadius = SAT_ICON_SIZE;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return {color, backgroundColor, borderColor, borderRadius};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function SATChoiceIcon(props: SATChoiceIconProps): React.Node {
|
|
68
|
-
const {letter, crossedOut, reviewMode, correct, checked, multipleSelect} =
|
|
69
|
-
props;
|
|
70
|
-
|
|
71
|
-
const {color, backgroundColor, borderColor, borderRadius} =
|
|
72
|
-
constructSATStyles(reviewMode, correct, checked, multipleSelect);
|
|
73
|
-
|
|
74
|
-
return (
|
|
75
|
-
<div className={css(sharedStyles.iconWrapper)}>
|
|
76
|
-
<div
|
|
77
|
-
className={css(styles.satCircle)}
|
|
78
|
-
data-test-id="choice-icon__sat-choice-icon"
|
|
79
|
-
style={{backgroundColor, borderColor, borderRadius}}
|
|
80
|
-
/>
|
|
81
|
-
<div style={{color}} className={css(styles.letter)}>
|
|
82
|
-
<span>{letter}</span>
|
|
83
|
-
</div>
|
|
84
|
-
{crossedOut && <CrossOutLine color={borderColor} sat={true} />}
|
|
85
|
-
</div>
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const styles = StyleSheet.create({
|
|
90
|
-
satCircle: {
|
|
91
|
-
display: "block",
|
|
92
|
-
borderStyle: "solid",
|
|
93
|
-
borderWidth: 2,
|
|
94
|
-
content: `''`,
|
|
95
|
-
height: SAT_ICON_SIZE,
|
|
96
|
-
width: SAT_ICON_SIZE,
|
|
97
|
-
top: 1,
|
|
98
|
-
left: 1,
|
|
99
|
-
},
|
|
100
|
-
letter: {
|
|
101
|
-
// These properties make sure that this element has the exact
|
|
102
|
-
// same size as `circle` so that we can center things
|
|
103
|
-
// inside of it.
|
|
104
|
-
border: "2px solid transparent",
|
|
105
|
-
width: SAT_ICON_SIZE,
|
|
106
|
-
height: SAT_ICON_SIZE,
|
|
107
|
-
position: "absolute",
|
|
108
|
-
top: 1,
|
|
109
|
-
|
|
110
|
-
// Center contained items.
|
|
111
|
-
display: "flex",
|
|
112
|
-
alignItems: "center",
|
|
113
|
-
justifyContent: "center",
|
|
114
|
-
|
|
115
|
-
fontFamily: styleConstants.boldFontFamily,
|
|
116
|
-
fontSize: 13,
|
|
117
|
-
},
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
export default SATChoiceIcon;
|