@khanacademy/perseus-editor 2.14.2 → 2.15.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.
@@ -1,18 +1,18 @@
1
1
  import * as React from "react";
2
- type ItemExtrasProps = {
3
- calculator: boolean;
4
- chi2Table: boolean;
5
- periodicTable: boolean;
6
- periodicTableWithKey: boolean;
7
- tTable: boolean;
8
- zTable: boolean;
2
+ import type { PerseusAnswerArea } from "@khanacademy/perseus";
3
+ type Props = PerseusAnswerArea & {
4
+ onChange: (props: Partial<PerseusAnswerArea>) => void;
9
5
  };
10
- type Props = ItemExtrasProps & {
11
- onChange: (props: Partial<ItemExtrasProps>) => void;
6
+ type State = {
7
+ financialCalculatorOptionsExpanded: boolean;
12
8
  };
13
- declare class ItemExtrasEditor extends React.Component<Props> {
14
- static defaultProps: ItemExtrasProps;
15
- serialize: () => any;
9
+ declare class ItemExtrasEditor extends React.Component<Props, State> {
10
+ static defaultProps: PerseusAnswerArea;
11
+ state: {
12
+ financialCalculatorOptionsExpanded: boolean;
13
+ };
14
+ componentDidUpdate(prevProps: Readonly<Props>): void;
15
+ serialize: () => PerseusAnswerArea;
16
16
  render(): React.ReactNode;
17
17
  }
18
18
  export default ItemExtrasEditor;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Perseus editors",
4
4
  "author": "Khan Academy",
5
5
  "license": "MIT",
6
- "version": "2.14.2",
6
+ "version": "2.15.1",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
@@ -24,7 +24,7 @@
24
24
  "dependencies": {
25
25
  "@khanacademy/kas": "^0.3.7",
26
26
  "@khanacademy/kmath": "^0.1.8",
27
- "@khanacademy/perseus": "^17.2.0",
27
+ "@khanacademy/perseus": "^17.3.1",
28
28
  "@khanacademy/perseus-core": "1.4.1"
29
29
  },
30
30
  "devDependencies": {
@@ -0,0 +1,41 @@
1
+ /* eslint-disable react/prop-types */
2
+ import * as React from "react";
3
+
4
+ import ItemExtrasEditor from "../item-extras-editor";
5
+
6
+ import type {Meta, StoryObj} from "@storybook/react";
7
+
8
+ import "../styles/perseus-editor.less";
9
+
10
+ type Props = React.ComponentProps<typeof ItemExtrasEditor>;
11
+
12
+ const Wrapper = (props: Props) => {
13
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
14
+ const {onChange, ...rest} = props;
15
+ const [extras, setExtras] =
16
+ React.useState<Partial<typeof ItemExtrasEditor.defaultProps>>(rest);
17
+
18
+ return (
19
+ <ItemExtrasEditor
20
+ {...extras}
21
+ onChange={(e) => {
22
+ props.onChange?.(e); // to register action in storybook
23
+ setExtras((prevExtras) => ({...prevExtras, ...e}));
24
+ }}
25
+ />
26
+ );
27
+ };
28
+
29
+ const story: Meta<Props> = {
30
+ title: "Perseus/Editor/Item Extras Editor",
31
+ component: ItemExtrasEditor,
32
+ render: (args) => <Wrapper {...args} />,
33
+ argTypes: {onChange: {action: "changed"}},
34
+ };
35
+ export default story;
36
+
37
+ type Story = StoryObj<typeof ItemExtrasEditor>;
38
+
39
+ export const Default: Story = {
40
+ args: {...ItemExtrasEditor.defaultProps},
41
+ };
@@ -65,4 +65,21 @@ describe("ItemExtrasEditor", () => {
65
65
  periodicTableWithKey: false,
66
66
  });
67
67
  });
68
+
69
+ it("should call onChange on dependent values when financialCalculator is checked", () => {
70
+ // Arrange
71
+ const onChangeMock = jest.fn();
72
+ render(<ItemExtrasEditor onChange={onChangeMock} />);
73
+ const checkbox = screen.getByLabelText("Show financial calculator:");
74
+
75
+ // Act
76
+ userEvent.click(checkbox);
77
+
78
+ // Assert
79
+ expect(onChangeMock).toHaveBeenCalledWith({
80
+ financialCalculatorMonthlyPayment: true,
81
+ financialCalculatorTotalAmount: true,
82
+ financialCalculatorTimeToPayOff: true,
83
+ });
84
+ });
68
85
  });
@@ -1,178 +1,230 @@
1
- import {components} from "@khanacademy/perseus";
1
+ import {components, ItemExtras} from "@khanacademy/perseus";
2
2
  import * as React from "react";
3
3
 
4
+ import type {PerseusAnswerArea} from "@khanacademy/perseus";
5
+
4
6
  const {InfoTip} = components;
5
7
 
6
- type ItemExtrasProps = {
7
- calculator: boolean;
8
- chi2Table: boolean;
9
- periodicTable: boolean;
10
- periodicTableWithKey: boolean;
11
- tTable: boolean;
12
- zTable: boolean;
8
+ type Props = PerseusAnswerArea & {
9
+ onChange: (props: Partial<PerseusAnswerArea>) => void;
13
10
  };
14
11
 
15
- type Props = ItemExtrasProps & {
16
- onChange: (props: Partial<ItemExtrasProps>) => void;
12
+ type State = {
13
+ financialCalculatorOptionsExpanded: boolean;
17
14
  };
18
15
 
19
- class ItemExtrasEditor extends React.Component<Props> {
20
- static defaultProps: ItemExtrasProps = {
16
+ class ItemExtrasEditor extends React.Component<Props, State> {
17
+ static defaultProps: PerseusAnswerArea = {
21
18
  calculator: false,
22
19
  chi2Table: false,
20
+ financialCalculatorMonthlyPayment: false,
21
+ financialCalculatorTotalAmount: false,
22
+ financialCalculatorTimeToPayOff: false,
23
23
  periodicTable: false,
24
24
  periodicTableWithKey: false,
25
25
  tTable: false,
26
26
  zTable: false,
27
27
  };
28
28
 
29
- serialize: () => any = () => {
30
- return {
31
- calculator: this.props.calculator,
32
- chi2Table: this.props.chi2Table,
33
- periodicTable: this.props.periodicTable,
34
- periodicTableWithKey: this.props.periodicTableWithKey,
35
- tTable: this.props.tTable,
36
- zTable: this.props.zTable,
37
- };
29
+ state = {
30
+ financialCalculatorOptionsExpanded: false,
31
+ };
32
+
33
+ componentDidUpdate(prevProps: Readonly<Props>): void {
34
+ // If no financial calculator options are checked, uncheck the
35
+ // financial calculator option.
36
+ if (
37
+ this.state.financialCalculatorOptionsExpanded &&
38
+ !this.props.financialCalculatorMonthlyPayment &&
39
+ !this.props.financialCalculatorTotalAmount &&
40
+ !this.props.financialCalculatorTimeToPayOff
41
+ ) {
42
+ this.setState({financialCalculatorOptionsExpanded: false});
43
+ }
44
+ }
45
+
46
+ serialize: () => PerseusAnswerArea = () => {
47
+ const data = {...ItemExtrasEditor.defaultProps};
48
+ for (const key of ItemExtras) {
49
+ data[key] = !!this.props[key];
50
+ }
51
+ return data;
38
52
  };
39
53
 
40
54
  render(): React.ReactNode {
41
55
  return (
42
56
  <div className="perseus-answer-editor">
43
57
  <div className="perseus-answer-options">
44
- <div>
45
- <label>
46
- Show calculator:{" "}
47
- <input
48
- type="checkbox"
49
- checked={this.props.calculator}
50
- onChange={(e) => {
51
- this.props.onChange({
52
- calculator: e.target.checked,
53
- });
54
- }}
55
- />
56
- </label>
57
- <InfoTip>
58
- Use the calculator when completing difficult
59
- calculations is NOT the intent of the question.
60
- DON’T use the calculator when testing the student’s
61
- ability to complete different types of computations.
62
- </InfoTip>
63
- </div>
64
-
65
- <div>
66
- <label>
67
- Show periodic table:{" "}
68
- <input
69
- type="checkbox"
70
- checked={this.props.periodicTable}
71
- onChange={(e) => {
72
- this.props.onChange({
73
- periodicTable: e.target.checked,
74
- // If the periodic table is unchecked,
75
- // this needs to be reset. If table is
76
- // checked, it should already be false.
77
- periodicTableWithKey: false,
78
- });
79
- }}
80
- />
81
- </label>
82
- <InfoTip>
83
- This provides the student with the ability to view a
84
- periodic table of the elements, e.g., for answering
85
- chemistry questions.
86
- </InfoTip>
87
- </div>
58
+ <ItemExtraCheckbox
59
+ label="Show calculator:"
60
+ infoTip="Use the calculator when completing difficult calculations is NOT the intent of the question. DON’T use the calculator when testing the student’s ability to complete different types of computations."
61
+ checked={this.props.calculator}
62
+ onChange={(e) => {
63
+ this.props.onChange({
64
+ calculator: e.target.checked,
65
+ });
66
+ }}
67
+ />
88
68
 
89
- {this.props.periodicTable && (
90
- <div>
91
- <label>
92
- Include key/legend with periodic table:{" "}
93
- <input
94
- type="checkbox"
95
- checked={this.props.periodicTableWithKey}
96
- onChange={(e) => {
97
- this.props.onChange({
98
- periodicTableWithKey:
99
- e.target.checked,
100
- });
101
- }}
102
- />
103
- </label>
104
- <InfoTip>
105
- Include a key for HS courses, omit for AP
106
- chemistry.
107
- </InfoTip>
108
- </div>
109
- )}
69
+ <ItemExtraCheckbox
70
+ label="Show financial calculator:"
71
+ infoTip="This provides the student with the ability to view a financial calculator, e.g., for answering financial questions. Once checked, requires at least one of the three options below to be checked."
72
+ checked={this.state.financialCalculatorOptionsExpanded}
73
+ onChange={(e) => {
74
+ this.setState({
75
+ financialCalculatorOptionsExpanded:
76
+ e.target.checked,
77
+ });
78
+ // If the financial calculator is unchecked,
79
+ // this needs to be reset. All checked by
80
+ // default.
81
+ this.props.onChange({
82
+ financialCalculatorMonthlyPayment:
83
+ e.target.checked,
84
+ financialCalculatorTotalAmount:
85
+ e.target.checked,
86
+ financialCalculatorTimeToPayOff:
87
+ e.target.checked,
88
+ });
89
+ }}
90
+ />
110
91
 
111
- <div>
112
- <label>
113
- Show z table (statistics):{" "}
114
- <input
115
- type="checkbox"
116
- checked={this.props.zTable}
92
+ {this.state.financialCalculatorOptionsExpanded && (
93
+ <>
94
+ <ItemExtraCheckbox
95
+ label="Include monthly payment:"
96
+ infoTip="This provides the student with the ability to view a monthly payment calculator; e.g., given a loan amount, interest rate, and term, what is the monthly payment?"
97
+ checked={
98
+ this.props.financialCalculatorMonthlyPayment
99
+ }
117
100
  onChange={(e) => {
118
101
  this.props.onChange({
119
- zTable: e.target.checked,
102
+ financialCalculatorMonthlyPayment:
103
+ e.target.checked,
120
104
  });
121
105
  }}
106
+ indent
122
107
  />
123
- </label>
124
- <InfoTip>
125
- This provides the student with the ability to view a
126
- table of critical values for the z distribution,
127
- e.g. for answering statistics questions.
128
- </InfoTip>
129
- </div>
130
-
131
- <div>
132
- <label>
133
- Show t table (statistics):{" "}
134
- <input
135
- type="checkbox"
136
- checked={this.props.tTable}
108
+ <ItemExtraCheckbox
109
+ label="Include total amount:"
110
+ infoTip="This provides the student with the ability to view a total amount calculator; e.g., given a monthly payment, interest rate, and term, what is the total amount to be paid?"
111
+ checked={
112
+ this.props.financialCalculatorTotalAmount
113
+ }
137
114
  onChange={(e) => {
138
115
  this.props.onChange({
139
- tTable: e.target.checked,
116
+ financialCalculatorTotalAmount:
117
+ e.target.checked,
140
118
  });
141
119
  }}
120
+ indent
142
121
  />
143
- </label>
144
- <InfoTip>
145
- This provides the student with the ability to view a
146
- table of critical values for the Student's t
147
- distribution, e.g. for answering statistics
148
- questions.
149
- </InfoTip>
150
- </div>
151
-
152
- <div>
153
- <label>
154
- Show chi-squared table (statistics):{" "}
155
- <input
156
- type="checkbox"
157
- checked={this.props.chi2Table}
122
+ <ItemExtraCheckbox
123
+ label="Include time-to-pay-off:"
124
+ infoTip="This provides the student with the ability to view a time to pay off calculator; e.g., given a loan amount, interest rate, and monthly payment, how long will it take to pay off the loan?"
125
+ checked={
126
+ this.props.financialCalculatorTimeToPayOff
127
+ }
158
128
  onChange={(e) => {
159
129
  this.props.onChange({
160
- chi2Table: e.target.checked,
130
+ financialCalculatorTimeToPayOff:
131
+ e.target.checked,
161
132
  });
162
133
  }}
134
+ indent
163
135
  />
164
- </label>
165
- <InfoTip>
166
- This provides the student with the ability to view a
167
- table of critical values for the chi-squared
168
- distribution, e.g. for answering statistics
169
- questions.
170
- </InfoTip>
171
- </div>
136
+ </>
137
+ )}
138
+
139
+ <ItemExtraCheckbox
140
+ label="Show periodic table:"
141
+ infoTip="This provides the student with the ability to view a periodic table of the elements, e.g., for answering chemistry questions."
142
+ checked={this.props.periodicTable}
143
+ onChange={(e) => {
144
+ this.props.onChange({
145
+ periodicTable: e.target.checked,
146
+ // If the periodic table is unchecked,
147
+ // this needs to be reset. If table is
148
+ // checked, it should already be false.
149
+ periodicTableWithKey: false,
150
+ });
151
+ }}
152
+ />
153
+
154
+ {this.props.periodicTable && (
155
+ <ItemExtraCheckbox
156
+ label="Include key/legend with periodic table:"
157
+ infoTip="Include a key for HS courses; omit for AP chemistry."
158
+ checked={this.props.periodicTableWithKey}
159
+ onChange={(e) => {
160
+ this.props.onChange({
161
+ periodicTableWithKey: e.target.checked,
162
+ });
163
+ }}
164
+ indent
165
+ />
166
+ )}
167
+
168
+ <ItemExtraCheckbox
169
+ label="Show z table (statistics):"
170
+ infoTip="This provides the student with the ability to view a table of critical values for the z distribution, e.g. for answering statistics questions."
171
+ checked={this.props.zTable}
172
+ onChange={(e) => {
173
+ this.props.onChange({
174
+ zTable: e.target.checked,
175
+ });
176
+ }}
177
+ />
178
+
179
+ <ItemExtraCheckbox
180
+ label="Show t table (statistics):"
181
+ infoTip="This provides the student with the ability to view a table of critical values for the Student's t distribution, e.g. for answering statistics questions."
182
+ checked={this.props.tTable}
183
+ onChange={(e) => {
184
+ this.props.onChange({
185
+ tTable: e.target.checked,
186
+ });
187
+ }}
188
+ />
189
+
190
+ <ItemExtraCheckbox
191
+ label="Show chi-squared table (statistics):"
192
+ infoTip="This provides the student with the ability to view a table of critical values for the chi-squared distribution, e.g. for answering statistics questions."
193
+ checked={this.props.chi2Table}
194
+ onChange={(e) => {
195
+ this.props.onChange({
196
+ chi2Table: e.target.checked,
197
+ });
198
+ }}
199
+ />
172
200
  </div>
173
201
  </div>
174
202
  );
175
203
  }
176
204
  }
177
205
 
206
+ const ItemExtraCheckbox = (props: {
207
+ label: string;
208
+ infoTip: string;
209
+ checked: boolean;
210
+ onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
211
+ indent?: boolean;
212
+ }) => (
213
+ <div
214
+ style={{
215
+ marginLeft: props.indent ? "10px" : "0px",
216
+ }}
217
+ >
218
+ <label>
219
+ {props.label}
220
+ <input
221
+ type="checkbox"
222
+ checked={props.checked}
223
+ onChange={props.onChange}
224
+ />
225
+ </label>
226
+ <InfoTip>{props.infoTip}</InfoTip>
227
+ </div>
228
+ );
229
+
178
230
  export default ItemExtrasEditor;