@khanacademy/wonder-blocks-form 2.4.8 → 3.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 (32) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/es/index.js +15 -13
  3. package/dist/index.js +75 -77
  4. package/docs.md +5 -1
  5. package/package.json +2 -2
  6. package/src/__docs__/_overview_.stories.mdx +15 -0
  7. package/src/components/__docs__/checkbox-group.stories.js +35 -1
  8. package/src/components/__docs__/labeled-text-field.argtypes.js +2 -2
  9. package/src/components/__docs__/labeled-text-field.stories.js +25 -0
  10. package/src/components/__docs__/radio-group.stories.js +35 -0
  11. package/src/components/__docs__/radio.stories.js +3 -2
  12. package/src/components/__tests__/checkbox-group.test.js +144 -67
  13. package/src/components/__tests__/field-heading.test.js +40 -0
  14. package/src/components/__tests__/radio-group.test.js +155 -58
  15. package/src/components/checkbox-group.js +9 -15
  16. package/src/components/checkbox.js +2 -2
  17. package/src/components/choice-internal.js +5 -3
  18. package/src/components/choice.js +2 -2
  19. package/src/components/field-heading.js +27 -43
  20. package/src/components/labeled-text-field.js +2 -3
  21. package/src/components/radio-group.js +6 -4
  22. package/src/components/radio.js +2 -2
  23. package/src/index.js +0 -2
  24. package/src/__tests__/__snapshots__/generated-snapshot.test.js.snap +0 -6126
  25. package/src/__tests__/generated-snapshot.test.js +0 -654
  26. package/src/components/checkbox-group.md +0 -200
  27. package/src/components/checkbox.md +0 -134
  28. package/src/components/field-heading.md +0 -43
  29. package/src/components/labeled-text-field.md +0 -535
  30. package/src/components/radio-group.md +0 -129
  31. package/src/components/radio.md +0 -26
  32. package/src/components/text-field.md +0 -770
@@ -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,
@@ -3,15 +3,16 @@ import * as React from "react";
3
3
  import {StyleSheet} from "aphrodite";
4
4
 
5
5
  import {View} from "@khanacademy/wonder-blocks-core";
6
- import {Radio} from "@khanacademy/wonder-blocks-form";
7
6
  import {LabelMedium, LabelSmall} from "@khanacademy/wonder-blocks-typography";
8
7
  import type {StoryComponentType} from "@storybook/react";
9
8
 
10
9
  import ComponentInfo from "../../../../../.storybook/components/component-info.js";
11
10
  import {name, version} from "../../../package.json";
12
11
 
12
+ import Radio from "../radio.js";
13
+
13
14
  export default {
14
- title: "Form / Radio",
15
+ title: "Form / Radio (internal)",
15
16
  component: Radio,
16
17
  parameters: {
17
18
  componentSubtitle: ((
@@ -1,85 +1,162 @@
1
1
  //@flow
2
2
  import * as React from "react";
3
- import {mount} from "enzyme";
4
- import "jest-enzyme";
3
+ import {render, screen} from "@testing-library/react";
4
+ import userEvent from "@testing-library/user-event";
5
5
 
6
6
  import CheckboxGroup from "../checkbox-group.js";
7
7
  import Choice from "../choice.js";
8
8
 
9
9
  describe("CheckboxGroup", () => {
10
- let group;
11
- const onChange = jest.fn();
12
-
13
- beforeEach(() => {
14
- group = mount(
15
- <CheckboxGroup
16
- label="Test"
17
- description="test description"
18
- groupName="test"
19
- onChange={onChange}
20
- selectedValues={["a", "b"]}
21
- >
22
- <Choice label="a" value="a" aria-labelledby="test-a" />
23
- <Choice label="b" value="b" aria-labelledby="test-b" />
24
- <Choice label="c" value="c" aria-labelledby="test-c" />
25
- </CheckboxGroup>,
26
- );
27
- });
10
+ describe("behavior", () => {
11
+ const TestComponent = ({errorMessage}: {|errorMessage?: string|}) => {
12
+ const [selectedValues, setSelectedValue] = React.useState([
13
+ "a",
14
+ "b",
15
+ ]);
16
+ const handleChange = (selectedValues) => {
17
+ setSelectedValue(selectedValues);
18
+ };
19
+ return (
20
+ <CheckboxGroup
21
+ label="Test"
22
+ description="test description"
23
+ groupName="test"
24
+ onChange={handleChange}
25
+ selectedValues={selectedValues}
26
+ errorMessage={errorMessage}
27
+ >
28
+ <Choice label="a" value="a" aria-labelledby="test-a" />
29
+ <Choice label="b" value="b" aria-labelledby="test-b" />
30
+ <Choice label="c" value="c" aria-labelledby="test-c" />
31
+ </CheckboxGroup>
32
+ );
33
+ };
28
34
 
29
- it("has the correct items checked", () => {
30
- const a = group.find(Choice).at(0);
31
- const b = group.find(Choice).at(1);
32
- const c = group.find(Choice).at(2);
35
+ it("has the correct items checked", () => {
36
+ // Arrange, Act
37
+ render(<TestComponent />);
33
38
 
34
- // a starts off checked
35
- expect(a.prop("checked")).toEqual(true);
36
- expect(b.prop("checked")).toEqual(true);
37
- expect(c.prop("checked")).toEqual(false);
38
- });
39
+ const checkboxes = screen.getAllByRole("checkbox");
39
40
 
40
- it("changes selection when selectedValue changes", () => {
41
- group.setProps({selectedValues: ["b"]});
42
- const a = group.find(Choice).at(0);
43
- const b = group.find(Choice).at(1);
44
- const c = group.find(Choice).at(2);
41
+ // Assert
42
+ // a starts off checked
43
+ expect(checkboxes[0]).toBeChecked();
44
+ expect(checkboxes[1]).toBeChecked();
45
+ expect(checkboxes[2]).not.toBeChecked();
46
+ });
45
47
 
46
- // now only b is checked
47
- expect(a.prop("checked")).toEqual(false);
48
- expect(b.prop("checked")).toEqual(true);
49
- expect(c.prop("checked")).toEqual(false);
50
- });
48
+ it("clicking a selected choice deselects it", () => {
49
+ // Arrange
50
+ render(<TestComponent />);
51
51
 
52
- it("displays error state for all Choice children", () => {
53
- group.setProps({errorMessage: "there's an error"});
54
- const a = group.find(Choice).at(0);
55
- const b = group.find(Choice).at(1);
56
- const c = group.find(Choice).at(2);
52
+ const checkboxes = screen.getAllByRole("checkbox");
57
53
 
58
- expect(a.prop("error")).toEqual(true);
59
- expect(b.prop("error")).toEqual(true);
60
- expect(c.prop("error")).toEqual(true);
61
- });
54
+ // Act
55
+ userEvent.click(checkboxes[0]);
56
+
57
+ // Assert
58
+ expect(checkboxes[0]).not.toBeChecked();
59
+ expect(checkboxes[1]).toBeChecked();
60
+ expect(checkboxes[2]).not.toBeChecked();
61
+ });
62
+
63
+ it("should set aria-invalid on choices when there's an error message", () => {
64
+ // Arrange, Act
65
+ render(<TestComponent errorMessage="there's an error" />);
66
+
67
+ const checkboxes = screen.getAllByRole("checkbox");
68
+
69
+ // Assert
70
+ expect(checkboxes[0]).toHaveAttribute("aria-invalid", "true");
71
+ expect(checkboxes[1]).toHaveAttribute("aria-invalid", "true");
72
+ expect(checkboxes[2]).toHaveAttribute("aria-invalid", "true");
73
+ });
62
74
 
63
- it("calls onChange for each new selection", () => {
64
- // a is clicked
65
- const a = group.find(Choice).at(0);
66
- const aTarget = a.find("ClickableBehavior");
67
- aTarget.simulate("click");
68
- expect(onChange).toHaveBeenCalledTimes(1);
69
-
70
- // now b is clicked, onChange should also be called
71
- const b = group.find(Choice).at(1);
72
- const bTarget = b.find("ClickableBehavior");
73
- bTarget.simulate("click");
74
- expect(onChange).toHaveBeenCalledTimes(2);
75
+ it("checks that aria attributes have been added correctly", () => {
76
+ // Arrange, Act
77
+ render(<TestComponent />);
78
+
79
+ const checkboxes = screen.getAllByRole("checkbox");
80
+
81
+ // Assert
82
+ expect(checkboxes[0]).toHaveAttribute("aria-labelledby", "test-a");
83
+ expect(checkboxes[1]).toHaveAttribute("aria-labelledby", "test-b");
84
+ expect(checkboxes[2]).toHaveAttribute("aria-labelledby", "test-c");
85
+ });
75
86
  });
76
87
 
77
- it("checks that aria attributes have been added correctly", () => {
78
- const a = group.find(Choice).at(0);
79
- const b = group.find(Choice).at(1);
80
- const c = group.find(Choice).at(2);
81
- expect(a.find("input").prop("aria-labelledby")).toEqual("test-a");
82
- expect(b.find("input").prop("aria-labelledby")).toEqual("test-b");
83
- expect(c.find("input").prop("aria-labelledby")).toEqual("test-c");
88
+ describe("flexible props", () => {
89
+ it("should render with a React.Node label", () => {
90
+ // Arrange, Act
91
+ render(
92
+ <CheckboxGroup
93
+ label={
94
+ <span>
95
+ label with <strong>strong</strong> text
96
+ </span>
97
+ }
98
+ groupName="test"
99
+ onChange={() => {}}
100
+ selectedValues={[]}
101
+ >
102
+ <Choice label="a" value="a" aria-labelledby="test-a" />
103
+ <Choice label="b" value="b" aria-labelledby="test-b" />
104
+ <Choice label="c" value="c" aria-labelledby="test-c" />
105
+ </CheckboxGroup>,
106
+ );
107
+
108
+ // Assert
109
+ expect(screen.getByText("strong")).toBeInTheDocument();
110
+ });
111
+
112
+ it("should render with a React.Node description", () => {
113
+ // Arrange, Act
114
+ render(
115
+ <CheckboxGroup
116
+ label="label"
117
+ description={
118
+ <span>
119
+ description with <strong>strong</strong> text
120
+ </span>
121
+ }
122
+ groupName="test"
123
+ onChange={() => {}}
124
+ selectedValues={[]}
125
+ >
126
+ <Choice label="a" value="a" aria-labelledby="test-a" />
127
+ <Choice label="b" value="b" aria-labelledby="test-b" />
128
+ <Choice label="c" value="c" aria-labelledby="test-c" />
129
+ </CheckboxGroup>,
130
+ );
131
+
132
+ // Assert
133
+ expect(screen.getByText("strong")).toBeInTheDocument();
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
+ });
84
161
  });
85
162
  });
@@ -4,6 +4,13 @@ import {mount} from "enzyme";
4
4
  import "jest-enzyme";
5
5
  import {StyleSheet} from "aphrodite";
6
6
 
7
+ import {I18nInlineMarkup} from "@khanacademy/wonder-blocks-i18n";
8
+ import {
9
+ Body,
10
+ LabelMedium,
11
+ LabelSmall,
12
+ } from "@khanacademy/wonder-blocks-typography";
13
+
7
14
  import FieldHeading from "../field-heading.js";
8
15
  import TextField from "../text-field.js";
9
16
 
@@ -180,4 +187,37 @@ describe("FieldHeading", () => {
180
187
  const container = wrapper.find("View").at(0);
181
188
  expect(container).toHaveStyle(styles.style1);
182
189
  });
190
+
191
+ it("should render a LabelSmall when the 'label' prop is a I18nInlineMarkup", () => {
192
+ // Arrange
193
+
194
+ // Act
195
+ const wrapper = mount(
196
+ <FieldHeading
197
+ field={<TextField id="tf-1" value="" onChange={() => {}} />}
198
+ label={<I18nInlineMarkup>Hello, world!</I18nInlineMarkup>}
199
+ />,
200
+ );
201
+
202
+ // Assert
203
+ const label = wrapper.find(LabelMedium);
204
+ expect(label).toExist();
205
+ });
206
+
207
+ it("should render a LabelSmall when the 'description' prop is a I18nInlineMarkup", () => {
208
+ // Arrange
209
+
210
+ // Act
211
+ const wrapper = mount(
212
+ <FieldHeading
213
+ field={<TextField id="tf-1" value="" onChange={() => {}} />}
214
+ label={<Body>Hello, world</Body>}
215
+ description={<I18nInlineMarkup>description</I18nInlineMarkup>}
216
+ />,
217
+ );
218
+
219
+ // Assert
220
+ const label = wrapper.find(LabelSmall);
221
+ expect(label).toExist();
222
+ });
183
223
  });
@@ -1,85 +1,182 @@
1
1
  //@flow
2
2
  import * as React from "react";
3
- import {mount} from "enzyme";
4
- import "jest-enzyme";
3
+ import {render, screen} from "@testing-library/react";
4
+ import userEvent from "@testing-library/user-event";
5
5
 
6
6
  import RadioGroup from "../radio-group.js";
7
7
  import Choice from "../choice.js";
8
8
 
9
9
  describe("RadioGroup", () => {
10
- let group;
11
- const onChange = jest.fn();
12
-
13
- beforeEach(() => {
14
- group = mount(
10
+ const TestComponent = ({
11
+ errorMessage,
12
+ onChange,
13
+ }: {|
14
+ errorMessage?: string,
15
+ onChange?: () => mixed,
16
+ |}) => {
17
+ const [selectedValue, setSelectedValue] = React.useState("a");
18
+ const handleChange = (selectedValue) => {
19
+ setSelectedValue(selectedValue);
20
+ onChange?.();
21
+ };
22
+ return (
15
23
  <RadioGroup
16
24
  label="Test"
17
25
  description="test description"
18
26
  groupName="test"
19
- onChange={onChange}
20
- selectedValue="a"
27
+ onChange={handleChange}
28
+ selectedValue={selectedValue}
29
+ errorMessage={errorMessage}
21
30
  >
22
31
  <Choice label="a" value="a" aria-labelledby="test-a" />
23
32
  <Choice label="b" value="b" aria-labelledby="test-b" />
24
33
  <Choice label="c" value="c" aria-labelledby="test-c" />
25
- </RadioGroup>,
34
+ </RadioGroup>
26
35
  );
27
- });
36
+ };
28
37
 
29
- it("selects only one item at a time", () => {
30
- const a = group.find(Choice).at(0);
31
- const b = group.find(Choice).at(1);
32
- const c = group.find(Choice).at(2);
38
+ describe("behavior", () => {
39
+ it("selects only one item at a time", () => {
40
+ // Arrange, Act
41
+ render(<TestComponent />);
33
42
 
34
- // a starts off checked
35
- expect(a.prop("checked")).toEqual(true);
36
- expect(b.prop("checked")).toEqual(false);
37
- expect(c.prop("checked")).toEqual(false);
38
- });
43
+ const radios = screen.getAllByRole("radio");
39
44
 
40
- it("changes selection when selectedValue changes", () => {
41
- group.setProps({selectedValue: "b"});
42
- const a = group.find(Choice).at(0);
43
- const b = group.find(Choice).at(1);
44
- const c = group.find(Choice).at(2);
45
+ // Assert
46
+ // a starts off checked
47
+ expect(radios[0]).toBeChecked();
48
+ expect(radios[1]).not.toBeChecked();
49
+ expect(radios[2]).not.toBeChecked();
50
+ });
45
51
 
46
- // now b is checked
47
- expect(a.prop("checked")).toEqual(false);
48
- expect(b.prop("checked")).toEqual(true);
49
- expect(c.prop("checked")).toEqual(false);
50
- });
52
+ it("changes selection when selectedValue changes", () => {
53
+ // Arrange
54
+ render(<TestComponent />);
51
55
 
52
- it("displays error state for all Choice children", () => {
53
- group.setProps({errorMessage: "there's an error"});
54
- const a = group.find(Choice).at(0);
55
- const b = group.find(Choice).at(1);
56
- const c = group.find(Choice).at(2);
56
+ const radios = screen.getAllByRole("radio");
57
57
 
58
- expect(a.prop("error")).toEqual(true);
59
- expect(b.prop("error")).toEqual(true);
60
- expect(c.prop("error")).toEqual(true);
61
- });
58
+ // Act
59
+ userEvent.click(radios[1]);
60
+
61
+ // Assert
62
+ // a starts off checked
63
+ expect(radios[0]).not.toBeChecked();
64
+ expect(radios[1]).toBeChecked();
65
+ expect(radios[2]).not.toBeChecked();
66
+ });
67
+
68
+ it("should set aria-invalid on choices when there's an error message", () => {
69
+ // Arrange, Act
70
+ render(<TestComponent errorMessage="there's an error" />);
71
+
72
+ const radios = screen.getAllByRole("radio");
73
+
74
+ // Assert
75
+ expect(radios[0]).toHaveAttribute("aria-invalid", "true");
76
+ expect(radios[1]).toHaveAttribute("aria-invalid", "true");
77
+ expect(radios[2]).toHaveAttribute("aria-invalid", "true");
78
+ });
79
+
80
+ it("doesn't change when an already selected item is reselected", () => {
81
+ // Arrange
82
+ const handleChange = jest.fn();
83
+ render(<TestComponent onChange={handleChange} />);
84
+
85
+ const radios = screen.getAllByRole("radio");
62
86
 
63
- it("doesn't change when an already selected item is reselected", () => {
64
- // a is already selected, onChange shouldn't be called
65
- const a = group.find(Choice).at(0);
66
- const aTarget = a.find("ClickableBehavior");
67
- aTarget.simulate("click");
68
- expect(onChange).toHaveBeenCalledTimes(0);
69
-
70
- // now b is clicked, onChange should be called
71
- const b = group.find(Choice).at(1);
72
- const bTarget = b.find("ClickableBehavior");
73
- bTarget.simulate("click");
74
- expect(onChange).toHaveBeenCalledTimes(1);
87
+ // Act
88
+ // a is already selected, onChange shouldn't be called
89
+ userEvent.click(radios[0]);
90
+
91
+ // Assert
92
+ expect(handleChange).toHaveBeenCalledTimes(0);
93
+ });
94
+
95
+ it("checks that aria attributes have been added correctly", () => {
96
+ // Arrange, Act
97
+ render(<TestComponent />);
98
+
99
+ const radios = screen.getAllByRole("radio");
100
+
101
+ // Assert
102
+ expect(radios[0]).toHaveAttribute("aria-labelledby", "test-a");
103
+ expect(radios[1]).toHaveAttribute("aria-labelledby", "test-b");
104
+ expect(radios[2]).toHaveAttribute("aria-labelledby", "test-c");
105
+ });
75
106
  });
76
107
 
77
- it("checks that aria attributes have been added correctly", () => {
78
- const a = group.find(Choice).at(0);
79
- const b = group.find(Choice).at(1);
80
- const c = group.find(Choice).at(2);
81
- expect(a.find("input").prop("aria-labelledby")).toEqual("test-a");
82
- expect(b.find("input").prop("aria-labelledby")).toEqual("test-b");
83
- expect(c.find("input").prop("aria-labelledby")).toEqual("test-c");
108
+ describe("flexible props", () => {
109
+ it("should render with a React.Node label", () => {
110
+ // Arrange, Act
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
+ );
127
+
128
+ // Assert
129
+ expect(screen.getByText("strong")).toBeInTheDocument();
130
+ });
131
+
132
+ it("should render with a React.Node description", () => {
133
+ // Arrange, Act
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 && (
168
+ <Choice label="b" value="b" aria-labelledby="test-b" />
169
+ )}
170
+ <Choice label="c" value="c" aria-labelledby="test-c" />
171
+ {undefined}
172
+ {null}
173
+ </RadioGroup>,
174
+ );
175
+
176
+ // Assert
177
+ const radios = screen.getAllByRole("radio");
178
+
179
+ expect(radios).toHaveLength(2);
180
+ });
84
181
  });
85
182
  });
@@ -5,11 +5,7 @@ import * as React from "react";
5
5
  import {View, addStyle} from "@khanacademy/wonder-blocks-core";
6
6
  import {Strut} from "@khanacademy/wonder-blocks-layout";
7
7
  import Spacing from "@khanacademy/wonder-blocks-spacing";
8
- import {
9
- type Typography,
10
- LabelMedium,
11
- LabelSmall,
12
- } from "@khanacademy/wonder-blocks-typography";
8
+ import {LabelMedium, LabelSmall} from "@khanacademy/wonder-blocks-typography";
13
9
  import type {StyleType} from "@khanacademy/wonder-blocks-core";
14
10
 
15
11
  import styles from "./group-styles.js";
@@ -20,7 +16,7 @@ type CheckboxGroupProps = {|
20
16
  /**
21
17
  * Children should be Choice components.
22
18
  */
23
- children: Array<React.Element<Choice>>,
19
+ children: Array<?(React.Element<Choice> | false)>,
24
20
 
25
21
  /**
26
22
  * Group name for this checkbox or radio group. Should be unique for all
@@ -32,12 +28,12 @@ type CheckboxGroupProps = {|
32
28
  * Optional label for the group. This label is optional to allow for
33
29
  * greater flexibility in implementing checkbox and radio groups.
34
30
  */
35
- label?: string | React.Element<Typography>,
31
+ label?: React.Node,
36
32
 
37
33
  /**
38
34
  * Optional description for the group.
39
35
  */
40
- description?: string | React.Element<Typography>,
36
+ description?: React.Node,
41
37
 
42
38
  /**
43
39
  * Optional error message. If supplied, the group will be displayed in an
@@ -132,23 +128,21 @@ export default class CheckboxGroup extends React.Component<CheckboxGroupProps> {
132
128
  testId,
133
129
  } = this.props;
134
130
 
131
+ const allChildren = React.Children.toArray(children).filter(Boolean);
132
+
135
133
  return (
136
134
  <StyledFieldset data-test-id={testId} style={styles.fieldset}>
137
135
  {/* We have a View here because fieldset cannot be used with flexbox*/}
138
136
  <View style={style}>
139
- {typeof label === "string" ? (
137
+ {label && (
140
138
  <StyledLegend style={styles.legend}>
141
139
  <LabelMedium>{label}</LabelMedium>
142
140
  </StyledLegend>
143
- ) : (
144
- label && label
145
141
  )}
146
- {typeof description === "string" ? (
142
+ {description && (
147
143
  <LabelSmall style={styles.description}>
148
144
  {description}
149
145
  </LabelSmall>
150
- ) : (
151
- description && description
152
146
  )}
153
147
  {errorMessage && (
154
148
  <LabelSmall style={styles.error}>
@@ -159,7 +153,7 @@ export default class CheckboxGroup extends React.Component<CheckboxGroupProps> {
159
153
  <Strut size={Spacing.small_12} />
160
154
  )}
161
155
 
162
- {React.Children.map(children, (child, index) => {
156
+ {allChildren.map((child, index) => {
163
157
  const {style, value} = child.props;
164
158
  const checked = selectedValues.includes(value);
165
159
  return (