@khanacademy/wonder-blocks-form 2.2.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.
Files changed (38) hide show
  1. package/LICENSE +21 -0
  2. package/dist/es/index.js +1100 -0
  3. package/dist/index.js +1419 -0
  4. package/dist/index.js.flow +2 -0
  5. package/docs.md +1 -0
  6. package/package.json +35 -0
  7. package/src/__tests__/__snapshots__/custom-snapshot.test.js.snap +1349 -0
  8. package/src/__tests__/__snapshots__/generated-snapshot.test.js.snap +6126 -0
  9. package/src/__tests__/custom-snapshot.test.js +66 -0
  10. package/src/__tests__/generated-snapshot.test.js +654 -0
  11. package/src/components/__tests__/checkbox-group.test.js +84 -0
  12. package/src/components/__tests__/field-heading.test.js +182 -0
  13. package/src/components/__tests__/labeled-text-field.test.js +442 -0
  14. package/src/components/__tests__/radio-group.test.js +84 -0
  15. package/src/components/__tests__/text-field.test.js +424 -0
  16. package/src/components/checkbox-core.js +201 -0
  17. package/src/components/checkbox-group.js +161 -0
  18. package/src/components/checkbox-group.md +200 -0
  19. package/src/components/checkbox.js +94 -0
  20. package/src/components/checkbox.md +134 -0
  21. package/src/components/choice-internal.js +206 -0
  22. package/src/components/choice.js +104 -0
  23. package/src/components/field-heading.js +157 -0
  24. package/src/components/field-heading.md +43 -0
  25. package/src/components/group-styles.js +35 -0
  26. package/src/components/labeled-text-field.js +265 -0
  27. package/src/components/labeled-text-field.md +535 -0
  28. package/src/components/labeled-text-field.stories.js +359 -0
  29. package/src/components/radio-core.js +176 -0
  30. package/src/components/radio-group.js +142 -0
  31. package/src/components/radio-group.md +129 -0
  32. package/src/components/radio.js +93 -0
  33. package/src/components/radio.md +26 -0
  34. package/src/components/text-field.js +326 -0
  35. package/src/components/text-field.md +770 -0
  36. package/src/components/text-field.stories.js +513 -0
  37. package/src/index.js +18 -0
  38. package/src/util/types.js +77 -0
@@ -0,0 +1,161 @@
1
+ // @flow
2
+
3
+ import * as React from "react";
4
+
5
+ import {View, addStyle} from "@khanacademy/wonder-blocks-core";
6
+ import {Strut} from "@khanacademy/wonder-blocks-layout";
7
+ import Spacing from "@khanacademy/wonder-blocks-spacing";
8
+ import {
9
+ type Typography,
10
+ LabelMedium,
11
+ LabelSmall,
12
+ } from "@khanacademy/wonder-blocks-typography";
13
+ import type {StyleType} from "@khanacademy/wonder-blocks-core";
14
+
15
+ import styles from "./group-styles.js";
16
+ import typeof Choice from "./choice.js";
17
+
18
+ // Keep synced with CheckboxGroupProps in ../util/types.js
19
+ type CheckboxGroupProps = {|
20
+ /**
21
+ * Children should be Choice components.
22
+ */
23
+ children: Array<React.Element<Choice>>,
24
+
25
+ /**
26
+ * Group name for this checkbox or radio group. Should be unique for all
27
+ * such groups displayed on a page.
28
+ */
29
+ groupName: string,
30
+
31
+ /**
32
+ * Optional label for the group. This label is optional to allow for
33
+ * greater flexibility in implementing checkbox and radio groups.
34
+ */
35
+ label?: string | React.Element<Typography>,
36
+
37
+ /**
38
+ * Optional description for the group.
39
+ */
40
+ description?: string | React.Element<Typography>,
41
+
42
+ /**
43
+ * Optional error message. If supplied, the group will be displayed in an
44
+ * error state, along with this error message. If no error state is desired,
45
+ * simply do not supply this prop, or pass along null.
46
+ */
47
+ errorMessage?: string,
48
+
49
+ /**
50
+ * Custom styling for this group of checkboxes.
51
+ */
52
+ style?: StyleType,
53
+
54
+ /**
55
+ * Callback for when selection of the group has changed. Passes the newly
56
+ * selected values.
57
+ */
58
+ onChange: (selectedValues: Array<string>) => mixed,
59
+
60
+ /**
61
+ * An array of the values of the selected values in this checkbox group.
62
+ */
63
+ selectedValues: Array<string>,
64
+
65
+ /**
66
+ * Test ID used for e2e testing.
67
+ */
68
+ testId?: string,
69
+ |};
70
+
71
+ const StyledFieldset = addStyle<"fieldset">("fieldset");
72
+ const StyledLegend = addStyle<"legend">("legend");
73
+
74
+ /**
75
+ * A checkbox group allows multiple selection. This component auto-populates
76
+ * many props for its children Choice components. The Choice component is
77
+ * exposed for the user to apply custom styles or to indicate which choices are
78
+ * disabled.
79
+ */
80
+ export default class CheckboxGroup extends React.Component<CheckboxGroupProps> {
81
+ handleChange(changedValue: string, originalCheckedState: boolean) {
82
+ const {onChange, selectedValues} = this.props;
83
+
84
+ if (originalCheckedState) {
85
+ const index = selectedValues.indexOf(changedValue);
86
+ const updatedSelection = [
87
+ ...selectedValues.slice(0, index),
88
+ ...selectedValues.slice(index + 1),
89
+ ];
90
+ onChange(updatedSelection);
91
+ } else {
92
+ onChange([...selectedValues, changedValue]);
93
+ }
94
+ }
95
+
96
+ render(): React.Node {
97
+ const {
98
+ children,
99
+ label,
100
+ description,
101
+ errorMessage,
102
+ groupName,
103
+ selectedValues,
104
+ style,
105
+ testId,
106
+ } = this.props;
107
+
108
+ return (
109
+ <StyledFieldset data-test-id={testId} style={styles.fieldset}>
110
+ {/* We have a View here because fieldset cannot be used with flexbox*/}
111
+ <View style={style}>
112
+ {typeof label === "string" ? (
113
+ <StyledLegend style={styles.legend}>
114
+ <LabelMedium>{label}</LabelMedium>
115
+ </StyledLegend>
116
+ ) : (
117
+ label && label
118
+ )}
119
+ {typeof description === "string" ? (
120
+ <LabelSmall style={styles.description}>
121
+ {description}
122
+ </LabelSmall>
123
+ ) : (
124
+ description && description
125
+ )}
126
+ {errorMessage && (
127
+ <LabelSmall style={styles.error}>
128
+ {errorMessage}
129
+ </LabelSmall>
130
+ )}
131
+ {(label || description || errorMessage) && (
132
+ <Strut size={Spacing.small_12} />
133
+ )}
134
+
135
+ {React.Children.map(children, (child, index) => {
136
+ const {style, value} = child.props;
137
+ const checked = selectedValues.includes(value);
138
+ return (
139
+ <React.Fragment>
140
+ {React.cloneElement(child, {
141
+ checked: checked,
142
+ error: !!errorMessage,
143
+ groupName: groupName,
144
+ id: `${groupName}-${value}`,
145
+ key: value,
146
+ onChange: () =>
147
+ this.handleChange(value, checked),
148
+ style: [
149
+ index > 0 && styles.defaultLineGap,
150
+ style,
151
+ ],
152
+ variant: "checkbox",
153
+ })}
154
+ </React.Fragment>
155
+ );
156
+ })}
157
+ </View>
158
+ </StyledFieldset>
159
+ );
160
+ }
161
+ }
@@ -0,0 +1,200 @@
1
+ This example has two items with descriptions, and it sets an error state to the
2
+ entire group if more than three items are selected.
3
+
4
+ Try out the keyboard navigation! Use tab and shift+tab to navigate among the
5
+ choices, and use space to select/de-select each option.
6
+
7
+ ```js
8
+ import {StyleSheet} from "aphrodite";
9
+ import {View} from "@khanacademy/wonder-blocks-core";
10
+ import {CheckboxGroup, Choice} from "@khanacademy/wonder-blocks-form";
11
+
12
+ const styles = StyleSheet.create({
13
+ wrapper: {
14
+ width: 300,
15
+ },
16
+ });
17
+
18
+ class CheckboxGroupPizzaExample extends React.Component {
19
+ constructor() {
20
+ super();
21
+ this.state = {
22
+ selectedValues: ["pineapple"],
23
+ };
24
+ this.handleChange = this.handleChange.bind(this);
25
+ }
26
+
27
+ handleChange(change) {
28
+ console.log(`${change} was selected!`);
29
+ const error = this.checkForError(change);
30
+ this.setState({
31
+ selectedValues: change,
32
+ error: error,
33
+ });
34
+ }
35
+
36
+ checkForError(input) {
37
+ if (input.length > 3) {
38
+ return "You have selected too many toppings";
39
+ }
40
+ }
41
+
42
+ render() {
43
+ return <CheckboxGroup
44
+ label="Pizza order"
45
+ description="You may choose at most three toppings"
46
+ errorMessage={this.state.error}
47
+ groupName="Toppings"
48
+ onChange={this.handleChange}
49
+ selectedValues={this.state.selectedValues}
50
+ >
51
+ <Choice label="Pepperoni" value="pepperoni" />
52
+ <Choice label="Sausage" value="sausage" description="Imported from Italy" />
53
+ <Choice label="Extra cheese" value="cheese" />
54
+ <Choice label="Green pepper" value="pepper" />
55
+ <Choice label="Mushroom" value="mushroom" />
56
+ <Choice label="Pineapple" value="pineapple" description="Does in fact belong on pizzas" />
57
+ </CheckboxGroup>
58
+ }
59
+ }
60
+ <View style={styles.wrapper}>
61
+ <CheckboxGroupPizzaExample />
62
+ </View>
63
+ ```
64
+
65
+ This example shows how one can add custom styles to the checkbox group and to
66
+ each component to achieve desired custom layouts. This context is inspired by
67
+ the class selector modal. The label is created separately because we are
68
+ reflowing all the elements in the group to row.
69
+
70
+ ```js
71
+ import {StyleSheet} from "aphrodite";
72
+ import Color from "@khanacademy/wonder-blocks-color";
73
+ import {View} from "@khanacademy/wonder-blocks-core";
74
+ import {CheckboxGroup, Choice} from "@khanacademy/wonder-blocks-form";
75
+ import {LabelLarge} from "@khanacademy/wonder-blocks-typography";
76
+
77
+ const styles = StyleSheet.create({
78
+ wrapper: {
79
+ width: 650,
80
+ },
81
+ group: {
82
+ flexDirection: "row",
83
+ flexWrap: "wrap",
84
+ },
85
+ choice: {
86
+ marginTop: 8,
87
+ width: 200,
88
+ },
89
+ title: {
90
+ paddingBottom: 8,
91
+ borderBottom: `1px solid ${Color.offBlack64}`,
92
+ },
93
+ });
94
+
95
+ class ClassSelectorExample extends React.Component {
96
+ constructor() {
97
+ super();
98
+ this.state = {
99
+ selectedValues: [],
100
+ };
101
+ this.handleChange = this.handleChange.bind(this);
102
+ }
103
+
104
+ handleChange(change) {
105
+ console.log(`${change} was selected!`);
106
+ this.setState({
107
+ selectedValues: change,
108
+ });
109
+ }
110
+
111
+ render() {
112
+ return <CheckboxGroup
113
+ groupName="science-classes"
114
+ onChange={this.handleChange}
115
+ selectedValues={this.state.selectedValues}
116
+ style={styles.group}
117
+ >
118
+ <Choice label="Biology" value="1" style={styles.choice} />
119
+ <Choice label="AP®︎ Biology" value="2" style={styles.choice} />
120
+ <Choice label="High school biology" value="3" style={styles.choice} />
121
+ <Choice label="Cosmology and astronomy" value="4" style={styles.choice} />
122
+ <Choice label="Electrical engineering" value="5" style={styles.choice} />
123
+ <Choice label="Health and medicine" value="6" style={styles.choice} />
124
+ </CheckboxGroup>
125
+ }
126
+ }
127
+ <View style={styles.wrapper}>
128
+ <LabelLarge style={styles.title}>Science</LabelLarge>
129
+ <ClassSelectorExample />
130
+ </View>
131
+ ```
132
+
133
+ This example shows how to use custom styling to change the appearance of the
134
+ checkbox group to look more like a multiple choice question. You may also provide
135
+ custom typography to the label and description.
136
+
137
+ ```js
138
+ import {CheckboxGroup, Choice} from "@khanacademy/wonder-blocks-form";
139
+ import {View} from "@khanacademy/wonder-blocks-core";
140
+ import Color from "@khanacademy/wonder-blocks-color";
141
+ import {LabelLarge, LabelXSmall} from "@khanacademy/wonder-blocks-typography";
142
+ import {StyleSheet} from "aphrodite";
143
+
144
+ const styles = StyleSheet.create({
145
+ choice: {
146
+ margin: 0,
147
+ height: 48,
148
+ borderTop: "solid 1px #CCC",
149
+ justifyContent: "center",
150
+ ":last-child": {
151
+ borderBottom: "solid 1px #CCC",
152
+ },
153
+ },
154
+ description: {
155
+ marginTop: 5,
156
+ color: Color.offBlack64,
157
+ },
158
+ });
159
+
160
+ class ClassSelectorExample extends React.Component {
161
+ constructor() {
162
+ super();
163
+ this.state = {
164
+ selectedValues: [],
165
+ };
166
+ this.handleChange = this.handleChange.bind(this);
167
+ }
168
+
169
+ handleChange(change) {
170
+ console.log(`${change} was selected!`);
171
+ this.setState({
172
+ selectedValues: change,
173
+ });
174
+ }
175
+
176
+ render() {
177
+ return <CheckboxGroup
178
+ label={<LabelLarge>Select all prime numbers</LabelLarge>}
179
+ description={
180
+ <LabelXSmall style={styles.description}>
181
+ Hint: There is at least one prime number
182
+ </LabelXSmall>
183
+ }
184
+ groupName="science-classes"
185
+ onChange={this.handleChange}
186
+ selectedValues={this.state.selectedValues}
187
+ >
188
+ <Choice label="1" value="1" style={styles.choice} />
189
+ <Choice label="2" value="2" style={styles.choice} />
190
+ <Choice label="3" value="3" style={styles.choice} />
191
+ <Choice label="4" value="4" style={styles.choice} />
192
+ <Choice label="5" value="5" style={styles.choice} />
193
+ </CheckboxGroup>
194
+ }
195
+ }
196
+
197
+ <View>
198
+ <ClassSelectorExample />
199
+ </View>
200
+ ```
@@ -0,0 +1,94 @@
1
+ // @flow
2
+
3
+ import * as React from "react";
4
+
5
+ import type {AriaProps, StyleType} from "@khanacademy/wonder-blocks-core";
6
+ import ChoiceInternal from "./choice-internal.js";
7
+
8
+ // Keep synced with ChoiceComponentProps in ../util/types.js
9
+ type ChoiceComponentProps = {|
10
+ ...AriaProps,
11
+
12
+ /**
13
+ * Whether this component is checked
14
+ */
15
+ checked: boolean,
16
+
17
+ /**
18
+ * Whether this component is disabled
19
+ */
20
+ disabled: boolean,
21
+
22
+ /**
23
+ * Whether this component should show an error state
24
+ */
25
+ error: boolean,
26
+
27
+ /**
28
+ * Callback when this component is selected. The newCheckedState is the
29
+ * new checked state of the component.
30
+ */
31
+ onChange: (newCheckedState: boolean) => mixed,
32
+
33
+ /**
34
+ * Optional label for the field.
35
+ */
36
+ label?: string,
37
+
38
+ /**
39
+ * Optional description for the field.
40
+ */
41
+ description?: string,
42
+
43
+ /**
44
+ * Unique identifier attached to the HTML input element. If used, need to
45
+ * guarantee that the ID is unique within everything rendered on a page.
46
+ * Used to match `<label>` with `<input>` elements for screenreaders.
47
+ */
48
+ id?: string,
49
+
50
+ /**
51
+ * Optional styling for the container. Does not style the component.
52
+ */
53
+ style?: StyleType,
54
+
55
+ /**
56
+ * Adds CSS classes to the Checkbox.
57
+ */
58
+ className?: string,
59
+
60
+ /**
61
+ * Optional test ID for e2e testing
62
+ */
63
+ testId?: string,
64
+
65
+ /**
66
+ * Name for the checkbox or radio button group. Only applicable for group
67
+ * contexts, auto-populated by group components via Choice.
68
+ * @ignore
69
+ */
70
+ groupName?: string,
71
+ |};
72
+
73
+ type DefaultProps = {|
74
+ disabled: $PropertyType<ChoiceComponentProps, "disabled">,
75
+ error: $PropertyType<ChoiceComponentProps, "error">,
76
+ |};
77
+
78
+ /**
79
+ * ☑️ A nicely styled checkbox for all your checking needs. Can optionally take
80
+ * label and description props.
81
+ *
82
+ * If you want a whole group of Checkbox[es] that are related, see the Choice
83
+ * and CheckboxGroup components.
84
+ */
85
+ export default class Checkbox extends React.Component<ChoiceComponentProps> {
86
+ static defaultProps: DefaultProps = {
87
+ disabled: false,
88
+ error: false,
89
+ };
90
+
91
+ render(): React.Node {
92
+ return <ChoiceInternal variant="checkbox" {...this.props} />;
93
+ }
94
+ }
@@ -0,0 +1,134 @@
1
+ The checkbox has various styles for clickable states. Here are sets of default checkboxes, checkboxes in an error state, and disabled checkboxes.
2
+ ```js
3
+ import {View} from "@khanacademy/wonder-blocks-core";
4
+ import {Checkbox} from "@khanacademy/wonder-blocks-form";
5
+ import {StyleSheet} from "aphrodite";
6
+
7
+ const styles = StyleSheet.create({
8
+ row: {
9
+ flexDirection: "row",
10
+ },
11
+ marginRight: {
12
+ marginRight: 16,
13
+ }
14
+ });
15
+
16
+ const handleChange = (checked) => console.log(`clicked on checkbox, will be checked=${checked.toString()}`);
17
+
18
+ <View style={styles.row}>
19
+ <Checkbox error={false} checked={false} style={styles.marginRight} onChange={handleChange} />
20
+ <Checkbox error={false} checked={true} style={styles.marginRight} onChange={handleChange} />
21
+ <Checkbox error={true} checked={false} style={styles.marginRight} onChange={handleChange} />
22
+ <Checkbox error={true} checked={true} style={styles.marginRight} onChange={handleChange} />
23
+ <Checkbox disabled={true} checked={false} style={styles.marginRight} onChange={handleChange} />
24
+ <Checkbox disabled={true} checked={true} style={styles.marginRight} onChange={handleChange} />
25
+ </View>
26
+ ```
27
+
28
+ The checkbox can have a optional label and description. This allows it to be
29
+ used as a settings-like item. The user of this component is responsible for
30
+ keeping track of checked state and providing an onChange callback.
31
+
32
+ ```js
33
+ import {View} from "@khanacademy/wonder-blocks-core";
34
+ import {Checkbox} from "@khanacademy/wonder-blocks-form";
35
+ import {LabelMedium, LabelSmall} from "@khanacademy/wonder-blocks-typography";
36
+ import {StyleSheet} from "aphrodite";
37
+
38
+ class Settings extends React.Component {
39
+ constructor() {
40
+ super();
41
+ this.state = {
42
+ assignment: false,
43
+ };
44
+ this.handleChange = this.handleChange.bind(this);
45
+ }
46
+
47
+ handleChange(checked) {
48
+ this.setState({
49
+ assignment: checked,
50
+ });
51
+ // Potentially do something here with this updated state information.
52
+ }
53
+
54
+ render() {
55
+ const handleChanged = (checked) => console.log(`clicked on checkbox with checked=${checked.toString()}`);
56
+ const headingText = "Functions";
57
+
58
+ return <View>
59
+ <Checkbox
60
+ label="Receive assignment reminders for Algebra"
61
+ description="You will receive a reminder 24 hours before each deadline"
62
+ checked={this.state.assignment}
63
+ id="assignment"
64
+ onChange={this.handleChange}
65
+ testId="algebra-assignment-test"
66
+ variant="checkbox"
67
+ />
68
+ </View>;
69
+ }
70
+ }
71
+
72
+ <Settings />
73
+ ```
74
+
75
+ Sometimes one may wish to use a checkbox in a different context (label may not
76
+ be right next to the checkbox), like in this example content item. Use a
77
+ `<label htmlFor={id}>` element where the id matches the `id` prop of the
78
+ Checkbox. This is for accessibility purposes, and doing this also automatically
79
+ makes the label a click target for the checkbox.
80
+ ```js
81
+ import {View} from "@khanacademy/wonder-blocks-core";
82
+ import {Checkbox} from "@khanacademy/wonder-blocks-form";
83
+ import {LabelMedium, LabelSmall} from "@khanacademy/wonder-blocks-typography";
84
+ import {StyleSheet} from "aphrodite";
85
+
86
+ const styles = StyleSheet.create({
87
+ wrapper: {
88
+ flexDirection: "row",
89
+ alignItems: "center",
90
+ justifyContent: "space-evenly",
91
+ },
92
+ topic: {
93
+ maxWidth: 600,
94
+ },
95
+ });
96
+
97
+ class ContentItem extends React.Component {
98
+ constructor() {
99
+ super();
100
+ this.state = {
101
+ checked: false,
102
+ }
103
+ }
104
+
105
+ handleChange(checked) {
106
+ this.setState({
107
+ checked: checked,
108
+ });
109
+ // Potentially do something here with this updated state information.
110
+ }
111
+
112
+ render() {
113
+ const handleChanged = (checked) => console.log(`clicked on checkbox with checked=${checked.toString()}`);
114
+ const headingText = "Functions";
115
+ const descriptionText = `A great cook knows how to take basic ingredients and
116
+ prepare a delicious meal. In this topic, you will become function-chefs! You
117
+ will learn how to combine functions with arithmetic operations and how to
118
+ compose functions.`;
119
+ return <View style={styles.wrapper}>
120
+ <View style={styles.topic}>
121
+ <label htmlFor="topic-123"><LabelMedium>{headingText}</LabelMedium></label>
122
+ <LabelSmall>{descriptionText}</LabelSmall>
123
+ </View>
124
+ <Checkbox
125
+ checked={this.state.checked}
126
+ id="topic-123"
127
+ onChange={(checked) => this.handleChange(checked)}
128
+ />
129
+ </View>;
130
+ }
131
+ }
132
+
133
+ <ContentItem />
134
+ ```