@khanacademy/wonder-blocks-form 3.0.0 → 3.1.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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # @khanacademy/wonder-blocks-form
2
2
 
3
+ ## 3.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - c13dc28e: Don't wrap Choices in a Fragment to so that each child has a 'key' prop
8
+
9
+ ## 3.1.0
10
+
11
+ ### Minor Changes
12
+
13
+ - d3f459bf: Allow CheckboxGroup and RadioGroup to accept falsy children
14
+
3
15
  ## 3.0.0
4
16
 
5
17
  ### Major Changes
package/dist/es/index.js CHANGED
@@ -550,6 +550,7 @@ class CheckboxGroup extends React.Component {
550
550
  style,
551
551
  testId
552
552
  } = this.props;
553
+ const allChildren = React.Children.toArray(children).filter(Boolean);
553
554
  return React.createElement(StyledFieldset$1, {
554
555
  "data-test-id": testId,
555
556
  style: styles$2.fieldset
@@ -563,13 +564,13 @@ class CheckboxGroup extends React.Component {
563
564
  style: styles$2.error
564
565
  }, errorMessage), (label || description || errorMessage) && React.createElement(Strut, {
565
566
  size: Spacing.small_12
566
- }), React.Children.map(children, (child, index) => {
567
+ }), allChildren.map((child, index) => {
567
568
  const {
568
569
  style,
569
570
  value
570
571
  } = child.props;
571
572
  const checked = selectedValues.includes(value);
572
- return React.createElement(React.Fragment, null, React.cloneElement(child, {
573
+ return React.cloneElement(child, {
573
574
  checked: checked,
574
575
  error: !!errorMessage,
575
576
  groupName: groupName,
@@ -578,7 +579,7 @@ class CheckboxGroup extends React.Component {
578
579
  onChange: () => this.handleChange(value, checked),
579
580
  style: [index > 0 && styles$2.defaultLineGap, style],
580
581
  variant: "checkbox"
581
- }));
582
+ });
582
583
  })));
583
584
  }
584
585
 
@@ -602,6 +603,7 @@ class RadioGroup extends React.Component {
602
603
  style,
603
604
  testId
604
605
  } = this.props;
606
+ const allChildren = React.Children.toArray(children).filter(Boolean);
605
607
  return React.createElement(StyledFieldset, {
606
608
  "data-test-id": testId,
607
609
  style: styles$2.fieldset
@@ -615,13 +617,13 @@ class RadioGroup extends React.Component {
615
617
  style: styles$2.error
616
618
  }, errorMessage), (label || description || errorMessage) && React.createElement(Strut, {
617
619
  size: Spacing.small_12
618
- }), React.Children.map(children, (child, index) => {
620
+ }), allChildren.map((child, index) => {
619
621
  const {
620
622
  style,
621
623
  value
622
624
  } = child.props;
623
625
  const checked = selectedValue === value;
624
- return React.createElement(React.Fragment, null, React.cloneElement(child, {
626
+ return React.cloneElement(child, {
625
627
  checked: checked,
626
628
  error: !!errorMessage,
627
629
  groupName: groupName,
@@ -630,7 +632,7 @@ class RadioGroup extends React.Component {
630
632
  onChange: () => this.handleChange(value),
631
633
  style: [index > 0 && styles$2.defaultLineGap, style],
632
634
  variant: "radio"
633
- }));
635
+ });
634
636
  })));
635
637
  }
636
638
 
package/dist/index.js CHANGED
@@ -848,6 +848,7 @@ class CheckboxGroup extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
848
848
  style,
849
849
  testId
850
850
  } = this.props;
851
+ const allChildren = react__WEBPACK_IMPORTED_MODULE_0__["Children"].toArray(children).filter(Boolean);
851
852
  return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["createElement"](StyledFieldset, {
852
853
  "data-test-id": testId,
853
854
  style: _group_styles_js__WEBPACK_IMPORTED_MODULE_5__[/* default */ "a"].fieldset
@@ -861,13 +862,13 @@ class CheckboxGroup extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
861
862
  style: _group_styles_js__WEBPACK_IMPORTED_MODULE_5__[/* default */ "a"].error
862
863
  }, errorMessage), (label || description || errorMessage) && /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["createElement"](_khanacademy_wonder_blocks_layout__WEBPACK_IMPORTED_MODULE_2__["Strut"], {
863
864
  size: _khanacademy_wonder_blocks_spacing__WEBPACK_IMPORTED_MODULE_3___default.a.small_12
864
- }), react__WEBPACK_IMPORTED_MODULE_0__["Children"].map(children, (child, index) => {
865
+ }), allChildren.map((child, index) => {
865
866
  const {
866
867
  style,
867
868
  value
868
869
  } = child.props;
869
870
  const checked = selectedValues.includes(value);
870
- return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["createElement"](react__WEBPACK_IMPORTED_MODULE_0__["Fragment"], null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["cloneElement"](child, {
871
+ return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["cloneElement"](child, {
871
872
  checked: checked,
872
873
  error: !!errorMessage,
873
874
  groupName: groupName,
@@ -876,7 +877,7 @@ class CheckboxGroup extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
876
877
  onChange: () => this.handleChange(value, checked),
877
878
  style: [index > 0 && _group_styles_js__WEBPACK_IMPORTED_MODULE_5__[/* default */ "a"].defaultLineGap, style],
878
879
  variant: "checkbox"
879
- }));
880
+ });
880
881
  })));
881
882
  }
882
883
 
@@ -959,6 +960,7 @@ class RadioGroup extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
959
960
  style,
960
961
  testId
961
962
  } = this.props;
963
+ const allChildren = react__WEBPACK_IMPORTED_MODULE_0__["Children"].toArray(children).filter(Boolean);
962
964
  return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["createElement"](StyledFieldset, {
963
965
  "data-test-id": testId,
964
966
  style: _group_styles_js__WEBPACK_IMPORTED_MODULE_5__[/* default */ "a"].fieldset
@@ -972,13 +974,13 @@ class RadioGroup extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
972
974
  style: _group_styles_js__WEBPACK_IMPORTED_MODULE_5__[/* default */ "a"].error
973
975
  }, errorMessage), (label || description || errorMessage) && /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["createElement"](_khanacademy_wonder_blocks_layout__WEBPACK_IMPORTED_MODULE_2__["Strut"], {
974
976
  size: _khanacademy_wonder_blocks_spacing__WEBPACK_IMPORTED_MODULE_3___default.a.small_12
975
- }), react__WEBPACK_IMPORTED_MODULE_0__["Children"].map(children, (child, index) => {
977
+ }), allChildren.map((child, index) => {
976
978
  const {
977
979
  style,
978
980
  value
979
981
  } = child.props;
980
982
  const checked = selectedValue === value;
981
- return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["createElement"](react__WEBPACK_IMPORTED_MODULE_0__["Fragment"], null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["cloneElement"](child, {
983
+ return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["cloneElement"](child, {
982
984
  checked: checked,
983
985
  error: !!errorMessage,
984
986
  groupName: groupName,
@@ -987,7 +989,7 @@ class RadioGroup extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
987
989
  onChange: () => this.handleChange(value),
988
990
  style: [index > 0 && _group_styles_js__WEBPACK_IMPORTED_MODULE_5__[/* default */ "a"].defaultLineGap, style],
989
991
  variant: "radio"
990
- }));
992
+ });
991
993
  })));
992
994
  }
993
995
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khanacademy/wonder-blocks-form",
3
- "version": "3.0.0",
3
+ "version": "3.1.1",
4
4
  "design": "v1",
5
5
  "description": "Form components for Wonder Blocks.",
6
6
  "main": "dist/index.js",
@@ -232,6 +232,41 @@ MultipleChoiceStyling.parameters = {
232
232
  },
233
233
  };
234
234
 
235
+ export const FiltersOutFalsyChildren: StoryComponentType = () => {
236
+ return (
237
+ <CheckboxGroup
238
+ groupName="pokemon"
239
+ selectedValues={["pepperoni", "sausage"]}
240
+ onChange={() => {}}
241
+ label="Pokemon"
242
+ description="Your first Pokemon."
243
+ >
244
+ <Choice label="Pepperoni" value="pepperoni" />
245
+ <Choice
246
+ label="Sausage"
247
+ value="sausage"
248
+ description="Imported from Italy"
249
+ />
250
+ <Choice label="Extra cheese" value="cheese" />
251
+ <Choice label="Green pepper" value="pepper" />
252
+ {false && <Choice label="Mushroom" value="mushroom" />}
253
+ </CheckboxGroup>
254
+ );
255
+ };
256
+
257
+ FiltersOutFalsyChildren.parameters = {
258
+ docs: {
259
+ storyDescription: `This example shows that children can be falsy values and
260
+ that those falsy values are filtered out when rendering children. In this
261
+ case, one of the children is \`{false && <Choice .../>}\` which results in
262
+ that choice being filtered out.`,
263
+ },
264
+ chromatic: {
265
+ // The unit tests already verify that false-y children aren't rendered.
266
+ disableSnapshot: true,
267
+ },
268
+ };
269
+
235
270
  const styles = StyleSheet.create({
236
271
  // Row styling
237
272
  wrapper: {
@@ -166,6 +166,41 @@ MultipleChoiceStyling.parameters = {
166
166
  },
167
167
  };
168
168
 
169
+ export const FiltersOutFalsyChildren: StoryComponentType = () => {
170
+ return (
171
+ <RadioGroup
172
+ groupName="pokemon"
173
+ selectedValue="bulbasaur"
174
+ onChange={() => {}}
175
+ label="Pokemon"
176
+ description="Your first Pokemon."
177
+ >
178
+ <Choice label="Bulbasaur" value="bulbasaur" />
179
+ <Choice
180
+ label="Charmander"
181
+ value="charmander"
182
+ description="Oops, we ran out of Charmanders"
183
+ disabled
184
+ />
185
+ <Choice label="Squirtle" value="squirtle" />
186
+ {false && <Choice label="Pikachu" value="pikachu" />}
187
+ </RadioGroup>
188
+ );
189
+ };
190
+
191
+ FiltersOutFalsyChildren.parameters = {
192
+ docs: {
193
+ storyDescription: `This example shows that children can be falsy values and
194
+ that those falsy values are filtered out when rendering children. In this
195
+ case, one of the children is \`{false && <Choice .../>}\` which results in
196
+ that choice being filtered out.`,
197
+ },
198
+ chromatic: {
199
+ // The unit tests already verify that false-y children aren't rendered.
200
+ disableSnapshot: true,
201
+ },
202
+ };
203
+
169
204
  const styles = StyleSheet.create({
170
205
  choice: {
171
206
  margin: 0,
@@ -132,5 +132,31 @@ describe("CheckboxGroup", () => {
132
132
  // Assert
133
133
  expect(screen.getByText("strong")).toBeInTheDocument();
134
134
  });
135
+
136
+ it("should filter out false-y children when rendering", () => {
137
+ // Arrange, Act
138
+ render(
139
+ <CheckboxGroup
140
+ label="label"
141
+ description="description"
142
+ groupName="test"
143
+ onChange={() => {}}
144
+ selectedValues={[]}
145
+ >
146
+ <Choice label="a" value="a" aria-labelledby="test-a" />
147
+ {false && (
148
+ <Choice label="b" value="b" aria-labelledby="test-b" />
149
+ )}
150
+ <Choice label="c" value="c" aria-labelledby="test-c" />
151
+ {undefined}
152
+ {null}
153
+ </CheckboxGroup>,
154
+ );
155
+
156
+ // Assert
157
+ const checkboxes = screen.getAllByRole("checkbox");
158
+
159
+ expect(checkboxes).toHaveLength(2);
160
+ });
135
161
  });
136
162
  });
@@ -108,51 +108,75 @@ describe("RadioGroup", () => {
108
108
  describe("flexible props", () => {
109
109
  it("should render with a React.Node label", () => {
110
110
  // Arrange, Act
111
- const action = () =>
112
- render(
113
- <RadioGroup
114
- label={
115
- <span>
116
- label with <strong>strong</strong> text
117
- </span>
118
- }
119
- groupName="test"
120
- onChange={() => {}}
121
- selectedValue={"a"}
122
- >
123
- <Choice label="a" value="a" aria-labelledby="test-a" />
124
- <Choice label="b" value="b" aria-labelledby="test-b" />
125
- <Choice label="c" value="c" aria-labelledby="test-c" />
126
- </RadioGroup>,
127
- );
111
+ render(
112
+ <RadioGroup
113
+ label={
114
+ <span>
115
+ label with <strong>strong</strong> text
116
+ </span>
117
+ }
118
+ groupName="test"
119
+ onChange={() => {}}
120
+ selectedValue={"a"}
121
+ >
122
+ <Choice label="a" value="a" aria-labelledby="test-a" />
123
+ <Choice label="b" value="b" aria-labelledby="test-b" />
124
+ <Choice label="c" value="c" aria-labelledby="test-c" />
125
+ </RadioGroup>,
126
+ );
128
127
 
129
128
  // Assert
130
- expect(action).not.toThrow();
129
+ expect(screen.getByText("strong")).toBeInTheDocument();
131
130
  });
132
131
 
133
132
  it("should render with a React.Node description", () => {
134
133
  // Arrange, Act
135
- const action = () =>
136
- render(
137
- <RadioGroup
138
- label="label"
139
- description={
140
- <span>
141
- description with <strong>strong</strong> text
142
- </span>
143
- }
144
- groupName="test"
145
- onChange={() => {}}
146
- selectedValue={"a"}
147
- >
148
- <Choice label="a" value="a" aria-labelledby="test-a" />
134
+ render(
135
+ <RadioGroup
136
+ label="label"
137
+ description={
138
+ <span>
139
+ description with <strong>strong</strong> text
140
+ </span>
141
+ }
142
+ groupName="test"
143
+ onChange={() => {}}
144
+ selectedValue={"a"}
145
+ >
146
+ <Choice label="a" value="a" aria-labelledby="test-a" />
147
+ <Choice label="b" value="b" aria-labelledby="test-b" />
148
+ <Choice label="c" value="c" aria-labelledby="test-c" />
149
+ </RadioGroup>,
150
+ );
151
+
152
+ // Assert
153
+ expect(screen.getByText("strong")).toBeInTheDocument();
154
+ });
155
+
156
+ it("should filter out false-y children when rendering", () => {
157
+ // Arrange, Act
158
+ render(
159
+ <RadioGroup
160
+ label="label"
161
+ description="description"
162
+ groupName="test"
163
+ onChange={() => {}}
164
+ selectedValue={"a"}
165
+ >
166
+ <Choice label="a" value="a" aria-labelledby="test-a" />
167
+ {false && (
149
168
  <Choice label="b" value="b" aria-labelledby="test-b" />
150
- <Choice label="c" value="c" aria-labelledby="test-c" />
151
- </RadioGroup>,
152
- );
169
+ )}
170
+ <Choice label="c" value="c" aria-labelledby="test-c" />
171
+ {undefined}
172
+ {null}
173
+ </RadioGroup>,
174
+ );
153
175
 
154
176
  // Assert
155
- expect(action).not.toThrow();
177
+ const radios = screen.getAllByRole("radio");
178
+
179
+ expect(radios).toHaveLength(2);
156
180
  });
157
181
  });
158
182
  });
@@ -16,7 +16,7 @@ type CheckboxGroupProps = {|
16
16
  /**
17
17
  * Children should be Choice components.
18
18
  */
19
- children: Array<React.Element<Choice>>,
19
+ children: Array<?(React.Element<Choice> | false)>,
20
20
 
21
21
  /**
22
22
  * Group name for this checkbox or radio group. Should be unique for all
@@ -128,6 +128,8 @@ export default class CheckboxGroup extends React.Component<CheckboxGroupProps> {
128
128
  testId,
129
129
  } = this.props;
130
130
 
131
+ const allChildren = React.Children.toArray(children).filter(Boolean);
132
+
131
133
  return (
132
134
  <StyledFieldset data-test-id={testId} style={styles.fieldset}>
133
135
  {/* We have a View here because fieldset cannot be used with flexbox*/}
@@ -151,27 +153,19 @@ export default class CheckboxGroup extends React.Component<CheckboxGroupProps> {
151
153
  <Strut size={Spacing.small_12} />
152
154
  )}
153
155
 
154
- {React.Children.map(children, (child, index) => {
156
+ {allChildren.map((child, index) => {
155
157
  const {style, value} = child.props;
156
158
  const checked = selectedValues.includes(value);
157
- return (
158
- <React.Fragment>
159
- {React.cloneElement(child, {
160
- checked: checked,
161
- error: !!errorMessage,
162
- groupName: groupName,
163
- id: `${groupName}-${value}`,
164
- key: value,
165
- onChange: () =>
166
- this.handleChange(value, checked),
167
- style: [
168
- index > 0 && styles.defaultLineGap,
169
- style,
170
- ],
171
- variant: "checkbox",
172
- })}
173
- </React.Fragment>
174
- );
159
+ return React.cloneElement(child, {
160
+ checked: checked,
161
+ error: !!errorMessage,
162
+ groupName: groupName,
163
+ id: `${groupName}-${value}`,
164
+ key: value,
165
+ onChange: () => this.handleChange(value, checked),
166
+ style: [index > 0 && styles.defaultLineGap, style],
167
+ variant: "checkbox",
168
+ });
175
169
  })}
176
170
  </View>
177
171
  </StyledFieldset>
@@ -16,7 +16,7 @@ type RadioGroupProps = {|
16
16
  /**
17
17
  * Children should be Choice components.
18
18
  */
19
- children: Array<React.Element<Choice>>,
19
+ children: Array<?(React.Element<Choice> | false)>,
20
20
 
21
21
  /**
22
22
  * Group name for this checkbox or radio group. Should be unique for all
@@ -118,6 +118,8 @@ export default class RadioGroup extends React.Component<RadioGroupProps> {
118
118
  testId,
119
119
  } = this.props;
120
120
 
121
+ const allChildren = React.Children.toArray(children).filter(Boolean);
122
+
121
123
  return (
122
124
  <StyledFieldset data-test-id={testId} style={styles.fieldset}>
123
125
  {/* We have a View here because fieldset cannot be used with flexbox*/}
@@ -141,26 +143,19 @@ export default class RadioGroup extends React.Component<RadioGroupProps> {
141
143
  <Strut size={Spacing.small_12} />
142
144
  )}
143
145
 
144
- {React.Children.map(children, (child, index) => {
146
+ {allChildren.map((child, index) => {
145
147
  const {style, value} = child.props;
146
148
  const checked = selectedValue === value;
147
- return (
148
- <React.Fragment>
149
- {React.cloneElement(child, {
150
- checked: checked,
151
- error: !!errorMessage,
152
- groupName: groupName,
153
- id: `${groupName}-${value}`,
154
- key: value,
155
- onChange: () => this.handleChange(value),
156
- style: [
157
- index > 0 && styles.defaultLineGap,
158
- style,
159
- ],
160
- variant: "radio",
161
- })}
162
- </React.Fragment>
163
- );
149
+ return React.cloneElement(child, {
150
+ checked: checked,
151
+ error: !!errorMessage,
152
+ groupName: groupName,
153
+ id: `${groupName}-${value}`,
154
+ key: value,
155
+ onChange: () => this.handleChange(value),
156
+ style: [index > 0 && styles.defaultLineGap, style],
157
+ variant: "radio",
158
+ });
164
159
  })}
165
160
  </View>
166
161
  </StyledFieldset>