@khanacademy/perseus 0.3.4 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/dist/es/index.css +436 -1
- package/dist/es/index.js +8232 -5591
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +436 -1
- package/dist/index.js +17876 -15237
- package/dist/index.js.map +1 -1
- package/dist/strings.js +0 -1
- package/package.json +9 -8
- package/src/__tests__/__snapshots__/item-renderer_test.jsx.snap +2 -3
- package/src/__tests__/__snapshots__/server-item-renderer_test.jsx.snap +2 -3
- package/src/__tests__/renderer-api_test.jsx +3 -27
- package/src/components/__stories__/svg-image.stories.jsx +11 -0
- package/src/components/fixed-to-responsive.jsx +1 -0
- package/src/components/input-with-examples.jsx +3 -1
- package/src/components/svg-image.jsx +10 -7
- package/src/components/text-input.jsx +65 -16
- package/src/definition-context.js +59 -0
- package/src/index.js +5 -1
- package/src/multi-items/__tests__/__snapshots__/multi-renderer_test.js.snap +438 -523
- package/src/multi-items/__tests__/multi-renderer_test.js +9 -9
- package/src/renderer.jsx +13 -6
- package/src/types.js +3 -4
- package/src/widgets/__stories__/definition.stories.jsx +44 -0
- package/src/widgets/__tests__/__snapshots__/categorizer_test.jsx.snap +76 -8
- package/src/widgets/__tests__/__snapshots__/graded-group-set-jipt_test.jsx.snap +6 -9
- package/src/widgets/__tests__/__snapshots__/graded-group-set_test.jsx.snap +2 -3
- package/src/widgets/__tests__/__snapshots__/grapher_test.jsx.snap +19 -2
- package/src/widgets/__tests__/__snapshots__/group_test.jsx.snap +338 -390
- package/src/widgets/__tests__/__snapshots__/image_test.jsx.snap +38 -4
- package/src/widgets/__tests__/__snapshots__/interactive-graph_test.jsx.snap +114 -12
- package/src/widgets/__tests__/__snapshots__/matrix_test.jsx.snap +120 -48
- package/src/widgets/__tests__/__snapshots__/numeric-input_test.jsx.snap +8 -12
- package/src/widgets/__tests__/__snapshots__/radio_test.jsx.snap +3615 -4517
- package/src/widgets/__tests__/graded-group-set-jipt_test.jsx +0 -1
- package/src/widgets/__tests__/grapher.cypress.jsx +236 -176
- package/src/widgets/__tests__/group_test.jsx +4 -4
- package/src/widgets/__tests__/interactive-graph.cypress.jsx +371 -291
- package/src/widgets/__tests__/radio_test.jsx +73 -114
- package/src/widgets/definition.jsx +34 -29
- package/src/widgets/numeric-input.jsx +2 -15
- package/src/widgets/radio/base-radio.jsx +1 -1
- package/src/widgets/radio/choice-icon.jsx +11 -10
- package/src/widgets/radio/choice.jsx +164 -396
- package/src/widgets/radio/cross-out-button.jsx +3 -5
- package/src/widgets/radio/option-status.jsx +4 -5
- package/src/widgets/radio/__stories__/cross-out-menu.stories.jsx +0 -59
- package/src/widgets/radio/__stories__/cross-out-tooltip.stories.jsx +0 -59
- package/src/widgets/radio/__stories__/toggleable-radio-button.stories.jsx +0 -28
- package/src/widgets/radio/cross-out-menu-wrapper.jsx +0 -492
- package/src/widgets/radio/cross-out-tooltip.jsx +0 -129
- package/src/widgets/radio/toggleable-radio-button.jsx +0 -133
|
@@ -1,33 +1,37 @@
|
|
|
1
1
|
/* eslint-disable react/no-unsafe */
|
|
2
2
|
// @flow
|
|
3
|
+
import Button from "@khanacademy/wonder-blocks-button";
|
|
4
|
+
import Clickable from "@khanacademy/wonder-blocks-clickable";
|
|
5
|
+
import Color from "@khanacademy/wonder-blocks-color";
|
|
6
|
+
import {View} from "@khanacademy/wonder-blocks-core";
|
|
7
|
+
import {Strut} from "@khanacademy/wonder-blocks-layout";
|
|
8
|
+
import {Popover, PopoverContent} from "@khanacademy/wonder-blocks-popover";
|
|
9
|
+
import Spacing from "@khanacademy/wonder-blocks-spacing";
|
|
3
10
|
import {StyleSheet, css} from "aphrodite";
|
|
4
11
|
import classNames from "classnames";
|
|
5
12
|
import PropTypes from "prop-types";
|
|
6
13
|
import * as React from "react";
|
|
7
14
|
import _ from "underscore";
|
|
8
15
|
|
|
9
|
-
import
|
|
16
|
+
import Icon from "../../components/icon.jsx";
|
|
10
17
|
import * as styleConstants from "../../styles/constants.js";
|
|
11
18
|
import mediaQueries from "../../styles/media-queries.js";
|
|
12
|
-
import sharedStyles from "../../styles/shared.js";
|
|
13
19
|
|
|
14
20
|
import ChoiceIcon from "./choice-icon.jsx";
|
|
15
|
-
import CrossOutMenuWrapper from "./cross-out-menu-wrapper.jsx";
|
|
16
21
|
import OptionStatus from "./option-status.jsx";
|
|
17
|
-
import
|
|
18
|
-
|
|
19
|
-
const focusedStyleMixin = {
|
|
20
|
-
backgroundColor: styleConstants.satSelectedBackgroundColor,
|
|
21
|
-
outline: `2px solid ${styleConstants.satBlue}`,
|
|
22
|
-
// Render the outline higher than the next element's border
|
|
23
|
-
zIndex: 1,
|
|
24
|
-
};
|
|
22
|
+
import {getChoiceLetter} from "./util.js";
|
|
25
23
|
|
|
26
24
|
const intermediateCheckboxPadding = `16px 16px`;
|
|
27
25
|
const intermediateCheckboxPaddingPhone = `12px 16px`;
|
|
28
26
|
|
|
29
27
|
export const TouchIgnoreTimeout = 10;
|
|
30
28
|
|
|
29
|
+
const ellipsisHorizontalIcon = {
|
|
30
|
+
path: "M27.218 6.82l0 13.578q0 2.852-1.984 4.836t-4.836 1.984l-13.578 0q-2.852 0-4.836-1.984t-1.984-4.836l0-13.578q0-2.852 1.984-4.836t4.836-1.984l13.578 0q2.852 0 4.836 1.984t1.984 4.836zm36.27 0l0 13.578q0 2.852-1.984 4.836t-4.836 1.984l-13.578 0q-2.852 0-4.836-1.984t-1.984-4.836l0-13.578q0-2.852 1.984-4.836t4.836-1.984l13.578 0q2.852 0 4.836 1.984t1.984 4.836zm36.27 0l0 13.578q0 2.852-1.984 4.836t-4.836 1.984l-13.578 0q-2.852 0-4.836-1.984t-1.984-4.836l0-13.578q0-2.852 1.984-4.836t4.836-1.984l13.578 0q2.852 0 4.836 1.984t1.984 4.836z",
|
|
31
|
+
width: 100,
|
|
32
|
+
height: 27.284,
|
|
33
|
+
};
|
|
34
|
+
|
|
31
35
|
type State = {|
|
|
32
36
|
isInputFocused: boolean,
|
|
33
37
|
isInputActive: boolean,
|
|
@@ -126,86 +130,6 @@ class Choice extends React.Component<$FlowFixMe, State> {
|
|
|
126
130
|
}
|
|
127
131
|
}
|
|
128
132
|
|
|
129
|
-
componentWillUnmount() {
|
|
130
|
-
if (this.justFinishedTimeoutID != null) {
|
|
131
|
-
// TODO(jeff, CP-3128): Use Wonder Blocks Timing API
|
|
132
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
133
|
-
clearTimeout(this.justFinishedTimeoutID);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
onInputFocus: () => void = () => {
|
|
138
|
-
this.setState({isInputFocused: true});
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
onInputBlur: () => void = () => {
|
|
142
|
-
this.setState({isInputFocused: false});
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
onInputMouseDown: (SyntheticMouseEvent<> | SyntheticTouchEvent<>) => void =
|
|
146
|
-
(e) => {
|
|
147
|
-
if (e.type === "mousedown" && this.justFinishedTouch) {
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
this.setState({isInputActive: true});
|
|
152
|
-
|
|
153
|
-
// Simulate Chrome's radio button behavior in all browsers: when the
|
|
154
|
-
// mouse goes down or up, the radio button should become focused.
|
|
155
|
-
// That way, the newly-selected answer becomes highlighted after click.
|
|
156
|
-
if (this.props.apiOptions.satStyling && this._input) {
|
|
157
|
-
this._input.focus();
|
|
158
|
-
}
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
onInputMouseUp: (SyntheticMouseEvent<> | SyntheticTouchEvent<>) => void = (
|
|
162
|
-
e,
|
|
163
|
-
) => {
|
|
164
|
-
if (e.type === "mouseup" && this.justFinishedTouch) {
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// NOTE(emily): We do some special handling here of touch events to
|
|
169
|
-
// make the "active" effect look better. In particular, when you click
|
|
170
|
-
// using touch events, we get a series of events going
|
|
171
|
-
// touchstart -> (delay) -> touchend -> mousedown -> mouseup -> click
|
|
172
|
-
// In order to make sure that we don't turn the active state off and on
|
|
173
|
-
// and off again during the touchend -> mousedown -> mouseup series, we
|
|
174
|
-
// set a flag (this.justFinishedTouch) after the touchend, and ignore
|
|
175
|
-
// the mousedown and mouseup events. Then, a little while later, we
|
|
176
|
-
// turn the flag off. Instead of turning the active state off right at
|
|
177
|
-
// the beginning, we wait for a little bit to sync it up better with
|
|
178
|
-
// the click event.
|
|
179
|
-
if (e.type === "touchend") {
|
|
180
|
-
this.justFinishedTouch = true;
|
|
181
|
-
|
|
182
|
-
// TODO(jeff, CP-3128): Use Wonder Blocks Timing API
|
|
183
|
-
/* eslint-disable no-restricted-syntax */
|
|
184
|
-
if (this.justFinishedTimeoutID != null) {
|
|
185
|
-
clearTimeout(this.justFinishedTimeoutID);
|
|
186
|
-
}
|
|
187
|
-
this.justFinishedTimeoutID = setTimeout(() => {
|
|
188
|
-
this.setState({isInputActive: false});
|
|
189
|
-
this.justFinishedTouch = false;
|
|
190
|
-
}, TouchIgnoreTimeout);
|
|
191
|
-
/* eslint-enable no-restricted-syntax */
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
this.setState({isInputActive: false});
|
|
196
|
-
|
|
197
|
-
// Simulate Chrome's radio button behavior in all browsers: when the
|
|
198
|
-
// mouse goes down or up, the radio button should become focused.
|
|
199
|
-
// That way, the newly-selected answer becomes highlighted after click.
|
|
200
|
-
if (this.props.apiOptions.satStyling && this._input) {
|
|
201
|
-
this._input.focus();
|
|
202
|
-
}
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
onInputMouseOut: () => void = () => {
|
|
206
|
-
this.setState({isInputActive: false});
|
|
207
|
-
};
|
|
208
|
-
|
|
209
133
|
inputRef: ($FlowFixMe) => void = (ref) => {
|
|
210
134
|
this._input = ref;
|
|
211
135
|
};
|
|
@@ -235,7 +159,10 @@ class Choice extends React.Component<$FlowFixMe, State> {
|
|
|
235
159
|
);
|
|
236
160
|
};
|
|
237
161
|
|
|
238
|
-
renderChoiceIcon: (
|
|
162
|
+
renderChoiceIcon: (args: {|
|
|
163
|
+
isFocused: boolean,
|
|
164
|
+
isPressed: boolean,
|
|
165
|
+
|}) => React.Node = ({isFocused, isPressed}) => {
|
|
239
166
|
const {radioStyleVersion, primaryProductColor} =
|
|
240
167
|
this.props.apiOptions.styling;
|
|
241
168
|
const finalStyles =
|
|
@@ -252,8 +179,8 @@ class Choice extends React.Component<$FlowFixMe, State> {
|
|
|
252
179
|
pos={this.props.pos}
|
|
253
180
|
correct={this.props.correct}
|
|
254
181
|
crossedOut={this.props.crossedOut}
|
|
255
|
-
pressed={
|
|
256
|
-
focused={
|
|
182
|
+
pressed={isPressed}
|
|
183
|
+
focused={isFocused}
|
|
257
184
|
checked={this.props.checked}
|
|
258
185
|
showCorrectness={this.props.showCorrectness}
|
|
259
186
|
reviewMode={this.props.reviewMode}
|
|
@@ -264,26 +191,6 @@ class Choice extends React.Component<$FlowFixMe, State> {
|
|
|
264
191
|
);
|
|
265
192
|
};
|
|
266
193
|
|
|
267
|
-
_setChecked: (boolean) => void = (willBeChecked) => {
|
|
268
|
-
if (willBeChecked) {
|
|
269
|
-
// If we're checking a crossed-out option, let's also uncross it.
|
|
270
|
-
this._sendChange({checked: true, crossedOut: false});
|
|
271
|
-
} else {
|
|
272
|
-
this._sendChange({checked: false});
|
|
273
|
-
}
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
// NOTE(mdr): This method expects to be auto-bound. If this component is
|
|
277
|
-
// converted to an ES6 class, take care to auto-bind this method!
|
|
278
|
-
_setCrossedOut: (boolean) => void = (willBeCrossedOut) => {
|
|
279
|
-
if (willBeCrossedOut) {
|
|
280
|
-
// If we're crossing out a checked option, let's also uncheck it.
|
|
281
|
-
this._sendChange({checked: false, crossedOut: true});
|
|
282
|
-
} else {
|
|
283
|
-
this._sendChange({crossedOut: false});
|
|
284
|
-
}
|
|
285
|
-
};
|
|
286
|
-
|
|
287
194
|
// Call `this.props.onChange` with the given values. Any keys that are not
|
|
288
195
|
// specified will be filled in with the current value. (For example, if
|
|
289
196
|
// `checked` is specified but `crossedOut` is not, then `crossedOut` will
|
|
@@ -291,15 +198,11 @@ class Choice extends React.Component<$FlowFixMe, State> {
|
|
|
291
198
|
//
|
|
292
199
|
// This enables us to use shorthand inside this component, while
|
|
293
200
|
// maintaining a consistent API for the parent.
|
|
294
|
-
_sendChange: (
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
const crossedOut =
|
|
299
|
-
newValues.crossedOut != null
|
|
300
|
-
? newValues.crossedOut
|
|
301
|
-
: this.props.crossedOut;
|
|
302
|
-
|
|
201
|
+
_sendChange: ({|checked?: boolean, crossedOut?: boolean|}) => void = (
|
|
202
|
+
newValues,
|
|
203
|
+
) => {
|
|
204
|
+
const checked = newValues.checked ?? this.props.checked;
|
|
205
|
+
const crossedOut = newValues.crossedOut ?? this.props.crossedOut;
|
|
303
206
|
this.props.onChange({checked, crossedOut});
|
|
304
207
|
};
|
|
305
208
|
|
|
@@ -312,99 +215,6 @@ class Choice extends React.Component<$FlowFixMe, State> {
|
|
|
312
215
|
|
|
313
216
|
render(): React.Node {
|
|
314
217
|
const sat = this.props.apiOptions.satStyling;
|
|
315
|
-
const isMobile = this.props.apiOptions.isMobile;
|
|
316
|
-
|
|
317
|
-
const {radioStyleVersion} = this.props.apiOptions.styling;
|
|
318
|
-
const finalStyles =
|
|
319
|
-
typeof radioStyleVersion === "undefined"
|
|
320
|
-
? false
|
|
321
|
-
: radioStyleVersion === "final";
|
|
322
|
-
|
|
323
|
-
const className = classNames(
|
|
324
|
-
this.props.className,
|
|
325
|
-
"checkbox-label",
|
|
326
|
-
css(
|
|
327
|
-
styles.label,
|
|
328
|
-
isMobile && sharedStyles.disableTextSelection,
|
|
329
|
-
!sat && styles.responsiveLabel,
|
|
330
|
-
sat && styles.satLabel,
|
|
331
|
-
),
|
|
332
|
-
);
|
|
333
|
-
|
|
334
|
-
const satRadioMenuContainer = classNames(
|
|
335
|
-
css(
|
|
336
|
-
this.state.isInputFocused && styles.satDescriptionInputFocused,
|
|
337
|
-
this.state.isInputActive && styles.satDescriptionInputActive,
|
|
338
|
-
),
|
|
339
|
-
);
|
|
340
|
-
|
|
341
|
-
// If we're showing an answer to be incorrect, we render it as
|
|
342
|
-
// unchecked, regardless of the `checked` state we've stored.
|
|
343
|
-
//
|
|
344
|
-
// This is because the existence of a selected radio button makes it
|
|
345
|
-
// impossible to tab-navigate to any other choices (standard radio
|
|
346
|
-
// behavior), but the selected answer is now disabled and not tabbable,
|
|
347
|
-
// which means that non-mouse/non-touch users can't select another
|
|
348
|
-
// answer to try again!
|
|
349
|
-
//
|
|
350
|
-
// (This behavior is only necessary for type "radio", but we also apply
|
|
351
|
-
// it for type "checkbox", for consistency.)
|
|
352
|
-
const showingIncorrectness =
|
|
353
|
-
this.props.showCorrectness && !this.props.correct;
|
|
354
|
-
const inputIsChecked = this.props.checked && !showingIncorrectness;
|
|
355
|
-
|
|
356
|
-
// There's two different input components we could use (the builtin
|
|
357
|
-
// input component, or the ToggleableRadioButton component). These are
|
|
358
|
-
// the props that we will pass to either.
|
|
359
|
-
const commonInputProps = {
|
|
360
|
-
type: this.props.type,
|
|
361
|
-
name: this.props.groupName,
|
|
362
|
-
id: `${this.props.groupName}-choice-${this.props.pos}`,
|
|
363
|
-
checked: inputIsChecked,
|
|
364
|
-
disabled: this.props.disabled,
|
|
365
|
-
onFocus: this.onInputFocus,
|
|
366
|
-
onBlur: this.onInputBlur,
|
|
367
|
-
className: css(
|
|
368
|
-
// intermediate styles are not different for radio and
|
|
369
|
-
// checkbox, and have a separate active state.
|
|
370
|
-
!finalStyles && sharedStyles.perseusInteractive,
|
|
371
|
-
!finalStyles && styles.input,
|
|
372
|
-
!finalStyles && sharedStyles.responsiveInput,
|
|
373
|
-
!finalStyles && !sat && sharedStyles.responsiveRadioInput,
|
|
374
|
-
!finalStyles &&
|
|
375
|
-
!sat &&
|
|
376
|
-
this.state.isInputActive &&
|
|
377
|
-
sharedStyles.responsiveRadioInputActive,
|
|
378
|
-
finalStyles && sharedStyles.perseusSrOnly,
|
|
379
|
-
sat && sharedStyles.perseusSrOnly,
|
|
380
|
-
sat && this.props.reviewMode && styles.satReviewInput,
|
|
381
|
-
),
|
|
382
|
-
};
|
|
383
|
-
|
|
384
|
-
let input = null;
|
|
385
|
-
if (this.props.type === "radio") {
|
|
386
|
-
// This is a special radio button that allows a user to deselect
|
|
387
|
-
// it by merely clicking/selecting it again.
|
|
388
|
-
input = (
|
|
389
|
-
<ToggleableRadioButton
|
|
390
|
-
onChecked={(willBeChecked) =>
|
|
391
|
-
this._setChecked(willBeChecked)
|
|
392
|
-
}
|
|
393
|
-
goToPrevChoice={this.props.goToPrevChoice}
|
|
394
|
-
goToNextChoice={this.props.goToNextChoice}
|
|
395
|
-
inputRef={this.inputRef}
|
|
396
|
-
{...commonInputProps}
|
|
397
|
-
/>
|
|
398
|
-
);
|
|
399
|
-
} else {
|
|
400
|
-
input = (
|
|
401
|
-
<input
|
|
402
|
-
onChange={(event) => this._setChecked(event.target.checked)}
|
|
403
|
-
ref={this.inputRef}
|
|
404
|
-
{...commonInputProps}
|
|
405
|
-
/>
|
|
406
|
-
);
|
|
407
|
-
}
|
|
408
218
|
|
|
409
219
|
const {reviewMode, correct, checked} = this.props;
|
|
410
220
|
// HACK: while most of the styling for rendering SAT items is handled
|
|
@@ -428,25 +238,6 @@ class Choice extends React.Component<$FlowFixMe, State> {
|
|
|
428
238
|
),
|
|
429
239
|
);
|
|
430
240
|
|
|
431
|
-
const checkboxContentClassName = classNames(
|
|
432
|
-
"checkbox",
|
|
433
|
-
css(
|
|
434
|
-
sharedStyles.perseusInteractive,
|
|
435
|
-
!sat && styles.choiceIconWrapper,
|
|
436
|
-
sat && styles.satCheckboxOptionContent,
|
|
437
|
-
),
|
|
438
|
-
);
|
|
439
|
-
|
|
440
|
-
const checkboxAndOptionClassName = classNames(
|
|
441
|
-
"checkbox-and-option",
|
|
442
|
-
css(
|
|
443
|
-
!sat && styles.intermediateResponsiveCheckbox,
|
|
444
|
-
!sat &&
|
|
445
|
-
reviewMode &&
|
|
446
|
-
styles.intermediateResponsiveCheckboxReview,
|
|
447
|
-
),
|
|
448
|
-
);
|
|
449
|
-
|
|
450
241
|
const rationaleClassName = classNames(
|
|
451
242
|
"perseus-radio-rationale-content",
|
|
452
243
|
css(
|
|
@@ -456,97 +247,163 @@ class Choice extends React.Component<$FlowFixMe, State> {
|
|
|
456
247
|
),
|
|
457
248
|
);
|
|
458
249
|
|
|
459
|
-
// In edit mode, we must allow selection of the contentEditable
|
|
460
|
-
// element inside, therefore we cannot use a label, which makes
|
|
461
|
-
// selection of anything inside automatically select the input
|
|
462
|
-
// element instead
|
|
463
|
-
const LabelOrDiv = this.props.editMode ? "div" : "label";
|
|
464
|
-
|
|
465
|
-
// We only show cross-out in certain products, and
|
|
466
|
-
// only when you're still _answering_ the question. We also show this
|
|
467
|
-
// for the SAT product area.
|
|
468
|
-
const crossOutEnabled = this.props.apiOptions.crossOutEnabled;
|
|
469
|
-
const showCrossOutMenu = crossOutEnabled && !this.props.showCorrectness;
|
|
470
|
-
|
|
471
250
|
// We want to show the choices as dimmed out when the choices are
|
|
472
251
|
// disabled. However, we don't want to do this in the SAT product and
|
|
473
252
|
// we also don't want to do this when we're in review mode in the
|
|
474
253
|
// content library.
|
|
475
254
|
const showDimmed =
|
|
476
255
|
(!sat && !reviewMode && this.props.apiOptions.readOnly) ||
|
|
477
|
-
|
|
256
|
+
this.props.crossedOut;
|
|
478
257
|
|
|
479
258
|
return (
|
|
480
|
-
<div
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
259
|
+
<div
|
|
260
|
+
style={{
|
|
261
|
+
dispay: "flex",
|
|
262
|
+
flexDirection: "column",
|
|
263
|
+
}}
|
|
264
|
+
>
|
|
265
|
+
<div
|
|
266
|
+
style={{
|
|
267
|
+
display: "flex",
|
|
268
|
+
flexDirection: "row",
|
|
269
|
+
width: "100%",
|
|
270
|
+
opacity: showDimmed ? 0.5 : 1.0,
|
|
271
|
+
}}
|
|
490
272
|
>
|
|
491
|
-
<
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
273
|
+
<Clickable
|
|
274
|
+
onClick={() => {
|
|
275
|
+
// If we're checking a crossed-out option, let's
|
|
276
|
+
// also uncross it.
|
|
277
|
+
this._sendChange({
|
|
278
|
+
checked: true,
|
|
279
|
+
crossedOut: false,
|
|
280
|
+
});
|
|
281
|
+
}}
|
|
282
|
+
className={descriptionClassName}
|
|
283
|
+
disabled={
|
|
284
|
+
this.props.disabled ||
|
|
285
|
+
this.props.apiOptions.staticRender ||
|
|
286
|
+
this.props.apiOptions.readOnly
|
|
496
287
|
}
|
|
497
|
-
|
|
498
|
-
|
|
288
|
+
aria-label={`Select Choice ${getChoiceLetter(
|
|
289
|
+
this.props.pos,
|
|
290
|
+
)}`}
|
|
291
|
+
aria-checked={this.props.checked}
|
|
292
|
+
role={"checkbox"}
|
|
293
|
+
style={{flex: 1}}
|
|
499
294
|
>
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
295
|
+
{({hovered, focused, pressed}) => (
|
|
296
|
+
<div
|
|
297
|
+
style={{
|
|
298
|
+
display: "flex",
|
|
299
|
+
flexDirection: "row",
|
|
300
|
+
justifyContent: "center",
|
|
301
|
+
alignContent: "center",
|
|
302
|
+
paddingTop: Spacing.xSmall_8,
|
|
303
|
+
paddingBottom: Spacing.xSmall_8,
|
|
304
|
+
}}
|
|
305
|
+
>
|
|
306
|
+
<span>
|
|
307
|
+
{this.renderChoiceIcon({
|
|
308
|
+
isFocused: focused,
|
|
309
|
+
isPressed: pressed,
|
|
310
|
+
})}
|
|
512
311
|
</span>
|
|
513
|
-
{/* A pseudo-label. <label> is slightly broken on iOS,
|
|
514
|
-
so this works around that. Unfortunately, it is
|
|
515
|
-
simplest to just work around that everywhere. */}
|
|
516
312
|
<span
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
reviewMode &&
|
|
524
|
-
styles.satReviewRadioOptionContent,
|
|
525
|
-
),
|
|
526
|
-
)}
|
|
527
|
-
style={{cursor: "default"}}
|
|
313
|
+
style={{
|
|
314
|
+
paddingLeft: Spacing.small_12,
|
|
315
|
+
textAlign: "left",
|
|
316
|
+
flex: 1,
|
|
317
|
+
paddingTop: 4,
|
|
318
|
+
}}
|
|
528
319
|
>
|
|
529
|
-
<div
|
|
530
|
-
className={css(
|
|
531
|
-
styles.optionStatusContainer,
|
|
532
|
-
)}
|
|
533
|
-
>
|
|
534
|
-
{this.renderOptionStatus()}
|
|
535
|
-
</div>
|
|
320
|
+
<div>{this.renderOptionStatus()}</div>
|
|
536
321
|
<div>{this.props.content}</div>
|
|
537
322
|
</span>
|
|
538
323
|
</div>
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
324
|
+
)}
|
|
325
|
+
</Clickable>
|
|
326
|
+
|
|
327
|
+
{this.props.apiOptions.crossOutEnabled && !reviewMode && (
|
|
328
|
+
<Popover
|
|
329
|
+
dismissEnabled
|
|
330
|
+
content={({close}) => (
|
|
331
|
+
<PopoverContent
|
|
332
|
+
title="Cross out"
|
|
333
|
+
content="Cross out option"
|
|
334
|
+
closeButtonVisible
|
|
335
|
+
actions={
|
|
336
|
+
<View>
|
|
337
|
+
<Strut size={Spacing.medium_16} />
|
|
338
|
+
<Button
|
|
339
|
+
kind="primary"
|
|
340
|
+
aria-label={`Cross out Choice ${getChoiceLetter(
|
|
341
|
+
this.props.pos,
|
|
342
|
+
)}`}
|
|
343
|
+
disabled={
|
|
344
|
+
this.props.apiOptions
|
|
345
|
+
.readOnly || reviewMode
|
|
346
|
+
}
|
|
347
|
+
onClick={() => {
|
|
348
|
+
if (
|
|
349
|
+
!this.props.crossedOut
|
|
350
|
+
) {
|
|
351
|
+
// If we're crossing
|
|
352
|
+
// out a checked
|
|
353
|
+
// option, let's also
|
|
354
|
+
// uncheck it.
|
|
355
|
+
this._sendChange({
|
|
356
|
+
checked: false,
|
|
357
|
+
crossedOut: true,
|
|
358
|
+
});
|
|
359
|
+
} else {
|
|
360
|
+
this._sendChange({
|
|
361
|
+
crossedOut: false,
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
close();
|
|
365
|
+
}}
|
|
366
|
+
>
|
|
367
|
+
{this.props.crossedOut
|
|
368
|
+
? "Bring back"
|
|
369
|
+
: "Cross out"}
|
|
370
|
+
</Button>
|
|
371
|
+
</View>
|
|
372
|
+
}
|
|
373
|
+
/>
|
|
374
|
+
)}
|
|
375
|
+
>
|
|
376
|
+
{({open}) => (
|
|
377
|
+
<Clickable
|
|
378
|
+
onClick={open}
|
|
379
|
+
aria-label={`Open menu for Choice ${getChoiceLetter(
|
|
380
|
+
this.props.pos,
|
|
381
|
+
)}`}
|
|
382
|
+
disabled={
|
|
383
|
+
this.props.apiOptions.staticRender
|
|
384
|
+
}
|
|
543
385
|
>
|
|
544
|
-
{
|
|
545
|
-
|
|
386
|
+
{({hovered, focused, pressed}) => (
|
|
387
|
+
<Icon
|
|
388
|
+
icon={ellipsisHorizontalIcon}
|
|
389
|
+
size={3}
|
|
390
|
+
color={Color.offBlack64}
|
|
391
|
+
/>
|
|
392
|
+
)}
|
|
393
|
+
</Clickable>
|
|
546
394
|
)}
|
|
547
|
-
</
|
|
548
|
-
|
|
549
|
-
</
|
|
395
|
+
</Popover>
|
|
396
|
+
)}
|
|
397
|
+
</div>
|
|
398
|
+
|
|
399
|
+
{this.props.showRationale && (
|
|
400
|
+
<div
|
|
401
|
+
className={rationaleClassName}
|
|
402
|
+
data-test-id={`perseus-radio-rationale-content-${this.props.pos}`}
|
|
403
|
+
>
|
|
404
|
+
{this.props.rationale}
|
|
405
|
+
</div>
|
|
406
|
+
)}
|
|
550
407
|
</div>
|
|
551
408
|
);
|
|
552
409
|
}
|
|
@@ -567,16 +424,6 @@ const styles = StyleSheet.create({
|
|
|
567
424
|
padding: "17px 14px",
|
|
568
425
|
},
|
|
569
426
|
|
|
570
|
-
satDescriptionInputFocused: {
|
|
571
|
-
...focusedStyleMixin,
|
|
572
|
-
display: "block",
|
|
573
|
-
},
|
|
574
|
-
|
|
575
|
-
satDescriptionInputActive: {
|
|
576
|
-
...focusedStyleMixin,
|
|
577
|
-
backgroundColor: styleConstants.satActiveBackgroundColor,
|
|
578
|
-
},
|
|
579
|
-
|
|
580
427
|
satDescriptionCorrect: {
|
|
581
428
|
color: styleConstants.satCorrectColor,
|
|
582
429
|
},
|
|
@@ -590,53 +437,6 @@ const styles = StyleSheet.create({
|
|
|
590
437
|
backgroundColor: styleConstants.satIncorrectBackgroundColor,
|
|
591
438
|
},
|
|
592
439
|
|
|
593
|
-
input: {
|
|
594
|
-
display: "inline-block",
|
|
595
|
-
width: 20,
|
|
596
|
-
margin: 3,
|
|
597
|
-
marginLeft: -20,
|
|
598
|
-
marginRight: 0,
|
|
599
|
-
float: "none",
|
|
600
|
-
},
|
|
601
|
-
|
|
602
|
-
satReviewInput: {
|
|
603
|
-
pointerEvents: "none",
|
|
604
|
-
},
|
|
605
|
-
|
|
606
|
-
satRadioOptionContent: {
|
|
607
|
-
userSelect: "text",
|
|
608
|
-
display: "block",
|
|
609
|
-
marginLeft: 45,
|
|
610
|
-
// Overriding here, not sure why typically set
|
|
611
|
-
// to "cursor: default" in js
|
|
612
|
-
cursor: "inherit",
|
|
613
|
-
},
|
|
614
|
-
|
|
615
|
-
satReviewRadioOptionContent: {
|
|
616
|
-
fontWeight: "bold",
|
|
617
|
-
},
|
|
618
|
-
|
|
619
|
-
satCheckboxOptionContent: {
|
|
620
|
-
position: "absolute",
|
|
621
|
-
display: "block",
|
|
622
|
-
top: "50%",
|
|
623
|
-
margin: "-16px 0 0 0",
|
|
624
|
-
width: "auto",
|
|
625
|
-
},
|
|
626
|
-
|
|
627
|
-
choiceIconWrapper: {
|
|
628
|
-
display: "flex",
|
|
629
|
-
marginRight: 12,
|
|
630
|
-
|
|
631
|
-
// NOTE(mdr): Without this style, the bubbles shrink on iOS
|
|
632
|
-
// when answer text gets long.
|
|
633
|
-
flexShrink: 0,
|
|
634
|
-
},
|
|
635
|
-
|
|
636
|
-
optionStatusContainer: {
|
|
637
|
-
display: "block",
|
|
638
|
-
},
|
|
639
|
-
|
|
640
440
|
rationale: {
|
|
641
441
|
display: "block",
|
|
642
442
|
},
|
|
@@ -655,38 +455,6 @@ const styles = StyleSheet.create({
|
|
|
655
455
|
marginTop: 13,
|
|
656
456
|
marginLeft: 45,
|
|
657
457
|
},
|
|
658
|
-
|
|
659
|
-
label: {
|
|
660
|
-
display: "block",
|
|
661
|
-
transition: "opacity 0.2s ease-out",
|
|
662
|
-
},
|
|
663
|
-
|
|
664
|
-
responsiveLabel: {
|
|
665
|
-
WebkitTapHighlightColor: "transparent",
|
|
666
|
-
display: "flex",
|
|
667
|
-
|
|
668
|
-
// Ensure that all items consume the full height of the choice.
|
|
669
|
-
// That way, the cross-out menu click target is full-height,
|
|
670
|
-
// which makes it easier to click!
|
|
671
|
-
alignItems: "stretch",
|
|
672
|
-
},
|
|
673
|
-
|
|
674
|
-
satLabel: {
|
|
675
|
-
cursor: "pointer",
|
|
676
|
-
},
|
|
677
|
-
|
|
678
|
-
intermediateResponsiveCheckbox: {
|
|
679
|
-
display: "flex",
|
|
680
|
-
alignItems: "center",
|
|
681
|
-
|
|
682
|
-
padding: intermediateCheckboxPadding,
|
|
683
|
-
[mediaQueries.smOrSmaller]: {
|
|
684
|
-
padding: intermediateCheckboxPaddingPhone,
|
|
685
|
-
},
|
|
686
|
-
},
|
|
687
|
-
intermediateResponsiveCheckboxReview: {
|
|
688
|
-
alignItems: "flex-start",
|
|
689
|
-
},
|
|
690
458
|
});
|
|
691
459
|
|
|
692
460
|
export default Choice;
|