@khanacademy/wonder-blocks-form 2.4.3 → 2.4.6
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 +26 -0
- package/dist/index.js +164 -0
- package/package.json +7 -7
- package/src/components/__docs__/checkbox-accessibility.stories.mdx +147 -0
- package/src/components/__docs__/checkbox-group.stories.js +266 -0
- package/src/components/__docs__/checkbox.stories.js +167 -0
- package/src/components/__docs__/choice.stories.js +86 -0
- package/src/components/__docs__/labeled-text-field.argtypes.js +248 -0
- package/src/components/{labeled-text-field.stories.js → __docs__/labeled-text-field.stories.js} +274 -35
- package/src/components/__docs__/radio-group.stories.js +182 -0
- package/src/components/__docs__/radio.stories.js +160 -0
- package/src/components/__docs__/text-field.argtypes.js +206 -0
- package/src/components/{text-field.stories.js → __docs__/text-field.stories.js} +268 -28
- package/src/components/checkbox-group.js +27 -0
- package/src/components/checkbox.js +14 -0
- package/src/components/choice.js +59 -1
- package/src/components/labeled-text-field.js +20 -0
- package/src/components/radio-group.js +27 -0
- package/src/components/text-field.js +17 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import {StyleSheet} from "aphrodite";
|
|
4
|
+
|
|
5
|
+
import {View} from "@khanacademy/wonder-blocks-core";
|
|
6
|
+
import {LabelMedium, LabelSmall} from "@khanacademy/wonder-blocks-typography";
|
|
7
|
+
import type {StoryComponentType} from "@storybook/react";
|
|
8
|
+
|
|
9
|
+
import Checkbox from "../checkbox.js";
|
|
10
|
+
|
|
11
|
+
import ComponentInfo from "../../../../../.storybook/components/component-info.js";
|
|
12
|
+
import {name, version} from "../../../package.json";
|
|
13
|
+
|
|
14
|
+
export default {
|
|
15
|
+
title: "Form / Checkbox",
|
|
16
|
+
component: Checkbox,
|
|
17
|
+
parameters: {
|
|
18
|
+
componentSubtitle: ((
|
|
19
|
+
<ComponentInfo name={name} version={version} />
|
|
20
|
+
): any),
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const Default: StoryComponentType = (args) => <Checkbox {...args} />;
|
|
25
|
+
|
|
26
|
+
Default.args = {
|
|
27
|
+
checked: false,
|
|
28
|
+
onChange: () => {},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
Default.parameters = {
|
|
32
|
+
chromatic: {
|
|
33
|
+
// We already have screenshots of another story that covers
|
|
34
|
+
// this and more cases.
|
|
35
|
+
disableSnapshot: true,
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const Controlled: StoryComponentType = () => {
|
|
40
|
+
const [checked, setChecked] = React.useState(false);
|
|
41
|
+
return <Checkbox checked={checked} onChange={setChecked} />;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
Controlled.parameters = {
|
|
45
|
+
chromatic: {
|
|
46
|
+
// Disabling because this doesn't test visuals, its for testing
|
|
47
|
+
// that `state` works as expected.
|
|
48
|
+
disableSnapshot: true,
|
|
49
|
+
},
|
|
50
|
+
docs: {
|
|
51
|
+
storyDescription:
|
|
52
|
+
"Use state to keep track of whether the checkbox is checked or not",
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const Variants: StoryComponentType = () => (
|
|
57
|
+
<View style={styles.row}>
|
|
58
|
+
<Checkbox
|
|
59
|
+
error={false}
|
|
60
|
+
checked={false}
|
|
61
|
+
style={styles.marginRight}
|
|
62
|
+
onChange={() => {}}
|
|
63
|
+
/>
|
|
64
|
+
<Checkbox
|
|
65
|
+
error={false}
|
|
66
|
+
checked={true}
|
|
67
|
+
style={styles.marginRight}
|
|
68
|
+
onChange={() => {}}
|
|
69
|
+
/>
|
|
70
|
+
<Checkbox
|
|
71
|
+
error={true}
|
|
72
|
+
checked={false}
|
|
73
|
+
style={styles.marginRight}
|
|
74
|
+
onChange={() => {}}
|
|
75
|
+
/>
|
|
76
|
+
<Checkbox
|
|
77
|
+
error={true}
|
|
78
|
+
checked={true}
|
|
79
|
+
style={styles.marginRight}
|
|
80
|
+
onChange={() => {}}
|
|
81
|
+
/>
|
|
82
|
+
<Checkbox
|
|
83
|
+
disabled={true}
|
|
84
|
+
checked={false}
|
|
85
|
+
style={styles.marginRight}
|
|
86
|
+
onChange={() => {}}
|
|
87
|
+
/>
|
|
88
|
+
<Checkbox
|
|
89
|
+
disabled={true}
|
|
90
|
+
checked={true}
|
|
91
|
+
style={styles.marginRight}
|
|
92
|
+
onChange={() => {}}
|
|
93
|
+
/>
|
|
94
|
+
</View>
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
Variants.parameters = {
|
|
98
|
+
docs: {
|
|
99
|
+
storyDescription:
|
|
100
|
+
"The checkbox has various styles for clickable states. Here are sets of default checkboxes, checkboxes in an error state, and disabled checkboxes.",
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export const WithLabel: StoryComponentType = () => {
|
|
105
|
+
const [checked, setChecked] = React.useState(false);
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<Checkbox
|
|
109
|
+
label="Receive assignment reminders for Algebra"
|
|
110
|
+
description="You will receive a reminder 24 hours before each deadline"
|
|
111
|
+
checked={checked}
|
|
112
|
+
onChange={setChecked}
|
|
113
|
+
/>
|
|
114
|
+
);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
WithLabel.parameters = {
|
|
118
|
+
docs: {
|
|
119
|
+
storyDescription:
|
|
120
|
+
"The checkbox can have an optional label and description. This allows it to be used as a settings-like item. The user of this component is responsible for keeping track of checked state and providing an onChange callback.",
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export const AdditionalClickTarget: StoryComponentType = () => {
|
|
125
|
+
const [checked, setChecked] = React.useState(false);
|
|
126
|
+
const headingText = "Functions";
|
|
127
|
+
const descriptionText = `A great cook knows how to take basic
|
|
128
|
+
ingredients and prepare a delicious meal. In this topic, you will
|
|
129
|
+
become function-chefs! You will learn how to combine functions
|
|
130
|
+
with arithmetic operations and how to compose functions.`;
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
<View style={styles.wrapper}>
|
|
134
|
+
<View style={styles.topic}>
|
|
135
|
+
<label htmlFor="topic-123">
|
|
136
|
+
<LabelMedium>{headingText}</LabelMedium>
|
|
137
|
+
</label>
|
|
138
|
+
<LabelSmall>{descriptionText}</LabelSmall>
|
|
139
|
+
</View>
|
|
140
|
+
<Checkbox checked={checked} id="topic-123" onChange={setChecked} />
|
|
141
|
+
</View>
|
|
142
|
+
);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
AdditionalClickTarget.parameters = {
|
|
146
|
+
docs: {
|
|
147
|
+
storyDescription:
|
|
148
|
+
"Sometimes one may wish to use a checkbox in a different context (label may not be right next to the checkbox), like in this example content item. Use a `<label htmlFor={id}>` element where the id matches the `id` prop of the Checkbox. This is for accessibility purposes, and doing this also automatically makes the label a click target for the checkbox.",
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const styles = StyleSheet.create({
|
|
153
|
+
row: {
|
|
154
|
+
flexDirection: "row",
|
|
155
|
+
},
|
|
156
|
+
marginRight: {
|
|
157
|
+
marginRight: 16,
|
|
158
|
+
},
|
|
159
|
+
wrapper: {
|
|
160
|
+
flexDirection: "row",
|
|
161
|
+
alignItems: "center",
|
|
162
|
+
justifyContent: "space-evenly",
|
|
163
|
+
},
|
|
164
|
+
topic: {
|
|
165
|
+
maxWidth: 600,
|
|
166
|
+
},
|
|
167
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import {StyleSheet} from "aphrodite";
|
|
4
|
+
|
|
5
|
+
import {View} from "@khanacademy/wonder-blocks-core";
|
|
6
|
+
import {
|
|
7
|
+
Choice,
|
|
8
|
+
CheckboxGroup,
|
|
9
|
+
RadioGroup,
|
|
10
|
+
} from "@khanacademy/wonder-blocks-form";
|
|
11
|
+
import {Strut} from "@khanacademy/wonder-blocks-layout";
|
|
12
|
+
import Spacing from "@khanacademy/wonder-blocks-spacing";
|
|
13
|
+
|
|
14
|
+
import type {StoryComponentType} from "@storybook/react";
|
|
15
|
+
|
|
16
|
+
import ComponentInfo from "../../../../../.storybook/components/component-info.js";
|
|
17
|
+
import {name, version} from "../../../package.json";
|
|
18
|
+
|
|
19
|
+
export default {
|
|
20
|
+
title: "Form / Choice",
|
|
21
|
+
component: Choice,
|
|
22
|
+
parameters: {
|
|
23
|
+
componentSubtitle: ((
|
|
24
|
+
<ComponentInfo name={name} version={version} />
|
|
25
|
+
): any),
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const Default: StoryComponentType = (args) => {
|
|
30
|
+
const [selectedValues, setSelectedValues] = React.useState([]);
|
|
31
|
+
const [selectedValue, setSelectedValue] = React.useState("");
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<View style={styles.row}>
|
|
35
|
+
<CheckboxGroup
|
|
36
|
+
label="Pizza order"
|
|
37
|
+
description="Choose as many toppings as you would like."
|
|
38
|
+
groupName="Toppings"
|
|
39
|
+
onChange={setSelectedValues}
|
|
40
|
+
selectedValues={selectedValues}
|
|
41
|
+
>
|
|
42
|
+
<Choice label="Pepperoni" value="pepperoni" />
|
|
43
|
+
<Choice
|
|
44
|
+
label="Sausage"
|
|
45
|
+
value="sausage"
|
|
46
|
+
description="Imported from Italy"
|
|
47
|
+
/>
|
|
48
|
+
<Choice label="Extra cheese" value="cheese" />
|
|
49
|
+
<Choice label="Green pepper" value="pepper" />
|
|
50
|
+
<Choice label="Mushroom" value="mushroom" />
|
|
51
|
+
<Choice {...args} />
|
|
52
|
+
</CheckboxGroup>
|
|
53
|
+
<Strut size={Spacing.xLarge_32} />
|
|
54
|
+
<RadioGroup
|
|
55
|
+
label="Pizza order"
|
|
56
|
+
description="Choose only one topping."
|
|
57
|
+
groupName="Toppings"
|
|
58
|
+
onChange={setSelectedValue}
|
|
59
|
+
selectedValue={selectedValue}
|
|
60
|
+
>
|
|
61
|
+
<Choice label="Pepperoni" value="pepperoni" />
|
|
62
|
+
<Choice
|
|
63
|
+
label="Sausage"
|
|
64
|
+
value="sausage"
|
|
65
|
+
description="Imported from Italy"
|
|
66
|
+
/>
|
|
67
|
+
<Choice label="Extra cheese" value="cheese" />
|
|
68
|
+
<Choice label="Green pepper" value="pepper" />
|
|
69
|
+
<Choice label="Mushroom" value="mushroom" />
|
|
70
|
+
<Choice {...args} />
|
|
71
|
+
</RadioGroup>
|
|
72
|
+
</View>
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
Default.args = {
|
|
77
|
+
label: "Pineapple (Control)",
|
|
78
|
+
value: "pineapple",
|
|
79
|
+
description: "Does in fact belong on pizzas",
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const styles = StyleSheet.create({
|
|
83
|
+
row: {
|
|
84
|
+
flexDirection: "row",
|
|
85
|
+
},
|
|
86
|
+
});
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
id: {
|
|
5
|
+
description: `An optional unique identifier for the TextField.
|
|
6
|
+
If no id is specified, a unique id will be auto-generated.`,
|
|
7
|
+
table: {
|
|
8
|
+
type: {
|
|
9
|
+
summary: "string",
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
control: {
|
|
13
|
+
type: "text",
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
type: {
|
|
17
|
+
description:
|
|
18
|
+
"Determines the type of input. Defaults to text. This may change the appearance or type of characters allowed.",
|
|
19
|
+
table: {
|
|
20
|
+
type: {
|
|
21
|
+
summary: `"text" | "password" | "email" | "number" | "tel"`,
|
|
22
|
+
},
|
|
23
|
+
defaultValue: {
|
|
24
|
+
summary: "text",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
options: ["text", "password", "email", "number", "tel"],
|
|
28
|
+
control: {
|
|
29
|
+
type: "select",
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
label: {
|
|
33
|
+
description: "Provide a label for the TextField.",
|
|
34
|
+
type: {required: true},
|
|
35
|
+
table: {
|
|
36
|
+
type: {
|
|
37
|
+
summary: "string | React.Element<Typography>",
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
control: {
|
|
41
|
+
type: "text",
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
description: {
|
|
45
|
+
description: "Provide a description for the TextField.",
|
|
46
|
+
table: {
|
|
47
|
+
type: {
|
|
48
|
+
summary: "string | React.Element<Typography>",
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
control: {
|
|
52
|
+
type: "text",
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
value: {
|
|
56
|
+
description: "The input value.",
|
|
57
|
+
type: {required: true},
|
|
58
|
+
table: {
|
|
59
|
+
type: {
|
|
60
|
+
summary: "string",
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
control: {
|
|
64
|
+
type: "text",
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
autoComplete: {
|
|
68
|
+
description: "Specifies if the TextField allows autocomplete.",
|
|
69
|
+
table: {
|
|
70
|
+
type: {
|
|
71
|
+
summary: "string",
|
|
72
|
+
detail: `There is a large number of options, including "on", "off", "username", "current-password", and many others.`,
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
control: {
|
|
76
|
+
type: "text",
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
disabled: {
|
|
80
|
+
description: "Makes a read-only input field that cannot be focused.",
|
|
81
|
+
table: {
|
|
82
|
+
type: {
|
|
83
|
+
summary: "boolean",
|
|
84
|
+
},
|
|
85
|
+
defaultValue: {
|
|
86
|
+
summary: "false",
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
control: {
|
|
90
|
+
type: "boolean",
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
light: {
|
|
94
|
+
description:
|
|
95
|
+
"Change the field’s sub-components to fit a dark background.",
|
|
96
|
+
table: {
|
|
97
|
+
type: {
|
|
98
|
+
summary: "boolean",
|
|
99
|
+
},
|
|
100
|
+
defaultValue: {
|
|
101
|
+
summary: "false",
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
control: {
|
|
105
|
+
type: "boolean",
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
placeholder: {
|
|
109
|
+
description: "Provide hints or examples of what to enter.",
|
|
110
|
+
table: {
|
|
111
|
+
type: {
|
|
112
|
+
summary: "string",
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
control: {
|
|
116
|
+
type: "text",
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
required: {
|
|
120
|
+
description:
|
|
121
|
+
"Whether this field is required to to continue, or the error message to render if this field is left blank. Pass in a message instead of `true` if possible.",
|
|
122
|
+
table: {
|
|
123
|
+
type: {
|
|
124
|
+
summary: "boolean | string",
|
|
125
|
+
detail: "The string will not be used if a `validate` prop is passed in.",
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
control: {
|
|
129
|
+
type: "boolean",
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
readOnly: {
|
|
133
|
+
description: "Specifies if the TextField is read-only.",
|
|
134
|
+
table: {
|
|
135
|
+
type: {
|
|
136
|
+
summary: "boolean",
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
control: {
|
|
140
|
+
type: "boolean",
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
style: {
|
|
144
|
+
description: "Custom styles for the input.",
|
|
145
|
+
table: {
|
|
146
|
+
type: {
|
|
147
|
+
summary: "StyleType",
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
testId: {
|
|
152
|
+
description: "Optional test ID for e2e testing.",
|
|
153
|
+
table: {
|
|
154
|
+
type: {
|
|
155
|
+
summary: "string",
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
control: {
|
|
159
|
+
type: "text",
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
validate: {
|
|
163
|
+
description:
|
|
164
|
+
"Provide a validation for the input value. Return a string error message or null | void for a valid input.",
|
|
165
|
+
table: {
|
|
166
|
+
type: {
|
|
167
|
+
summary: "(value: string) => ?string",
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
control: {
|
|
171
|
+
type: "null",
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Accessibility
|
|
177
|
+
*/
|
|
178
|
+
ariaDescribedby: {
|
|
179
|
+
description:
|
|
180
|
+
"Identifies the element or elements that describes this text field.",
|
|
181
|
+
table: {
|
|
182
|
+
category: "Accessibility",
|
|
183
|
+
type: {
|
|
184
|
+
summary: "string | Array<string>",
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
control: {
|
|
188
|
+
type: "text",
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Events
|
|
194
|
+
*/
|
|
195
|
+
onValidate: {
|
|
196
|
+
description: "Called when the TextField input is validated.",
|
|
197
|
+
table: {
|
|
198
|
+
category: "Events",
|
|
199
|
+
type: {
|
|
200
|
+
summary: "(errorMessage: ?string) => mixed",
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
onChange: {
|
|
205
|
+
description:
|
|
206
|
+
"Called when the value has changed. Use this in conjunction with the `value` prop to update the string rendered in the input field.",
|
|
207
|
+
type: {required: true},
|
|
208
|
+
table: {
|
|
209
|
+
category: "Events",
|
|
210
|
+
type: {
|
|
211
|
+
summary: "(newValue: string) => mixed",
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
onKeyDown: {
|
|
216
|
+
action: "keyDown",
|
|
217
|
+
description: "Called when a key is pressed.",
|
|
218
|
+
table: {
|
|
219
|
+
category: "Events",
|
|
220
|
+
type: {
|
|
221
|
+
summary:
|
|
222
|
+
"(event: SyntheticKeyboardEvent<HTMLInputElement>) => mixed",
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
onFocus: {
|
|
227
|
+
action: "focus",
|
|
228
|
+
description: "Called when the element has been focused.",
|
|
229
|
+
table: {
|
|
230
|
+
category: "Events",
|
|
231
|
+
type: {
|
|
232
|
+
summary:
|
|
233
|
+
"(event: SyntheticFocusEvent<HTMLInputElement>) => mixed",
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
onBlur: {
|
|
238
|
+
action: "blur",
|
|
239
|
+
description: "Called when the element has been blurred.",
|
|
240
|
+
table: {
|
|
241
|
+
category: "Events",
|
|
242
|
+
type: {
|
|
243
|
+
summary:
|
|
244
|
+
"(event: SyntheticFocusEvent<HTMLInputElement>) => mixed",
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
};
|