@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
|
@@ -11,8 +11,42 @@ import Button from "@khanacademy/wonder-blocks-button";
|
|
|
11
11
|
|
|
12
12
|
import type {StoryComponentType} from "@storybook/react";
|
|
13
13
|
|
|
14
|
+
import ComponentInfo from "../../../../../.storybook/components/component-info.js";
|
|
15
|
+
import {name, version} from "../../../package.json";
|
|
16
|
+
import TextFieldArgTypes from "./text-field.argtypes.js";
|
|
17
|
+
|
|
14
18
|
export default {
|
|
15
19
|
title: "Form / TextField",
|
|
20
|
+
component: TextField,
|
|
21
|
+
parameters: {
|
|
22
|
+
componentSubtitle: ((
|
|
23
|
+
<ComponentInfo name={name} version={version} />
|
|
24
|
+
): any),
|
|
25
|
+
},
|
|
26
|
+
argTypes: TextFieldArgTypes,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const Default: StoryComponentType = (args) => {
|
|
30
|
+
return <TextField {...args} />;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
Default.args = {
|
|
34
|
+
id: "some-id",
|
|
35
|
+
type: "text",
|
|
36
|
+
value: "",
|
|
37
|
+
disabled: false,
|
|
38
|
+
placeholder: "",
|
|
39
|
+
required: false,
|
|
40
|
+
light: false,
|
|
41
|
+
testId: "",
|
|
42
|
+
readOnly: false,
|
|
43
|
+
autoComplete: "off",
|
|
44
|
+
validate: () => {},
|
|
45
|
+
onValidate: () => {},
|
|
46
|
+
onChange: () => {},
|
|
47
|
+
onKeyDown: () => {},
|
|
48
|
+
onFocus: () => {},
|
|
49
|
+
onBlur: () => {},
|
|
16
50
|
};
|
|
17
51
|
|
|
18
52
|
export const Text: StoryComponentType = () => {
|
|
@@ -40,6 +74,13 @@ export const Text: StoryComponentType = () => {
|
|
|
40
74
|
);
|
|
41
75
|
};
|
|
42
76
|
|
|
77
|
+
Text.parameters = {
|
|
78
|
+
docs: {
|
|
79
|
+
storyDescription:
|
|
80
|
+
"An input field with type `text` takes all kinds of characters.",
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
|
|
43
84
|
export const Required: StoryComponentType = () => {
|
|
44
85
|
const [value, setValue] = React.useState("");
|
|
45
86
|
|
|
@@ -55,7 +96,7 @@ export const Required: StoryComponentType = () => {
|
|
|
55
96
|
|
|
56
97
|
return (
|
|
57
98
|
<TextField
|
|
58
|
-
id="tf-
|
|
99
|
+
id="tf-2"
|
|
59
100
|
type="text"
|
|
60
101
|
value={value}
|
|
61
102
|
onChange={handleChange}
|
|
@@ -65,6 +106,19 @@ export const Required: StoryComponentType = () => {
|
|
|
65
106
|
);
|
|
66
107
|
};
|
|
67
108
|
|
|
109
|
+
Required.parameters = {
|
|
110
|
+
docs: {
|
|
111
|
+
storyDescription: `A required field will have error styling if the
|
|
112
|
+
field is left blank. To observe this, type something into the
|
|
113
|
+
field, backspace all the way, and then shift focus out of the field.`,
|
|
114
|
+
},
|
|
115
|
+
chromatic: {
|
|
116
|
+
// Disabling snapshot because it doesn't show the error style
|
|
117
|
+
// until after the user interacts with this field.
|
|
118
|
+
disableSnapshot: true,
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
|
|
68
122
|
export const Number: StoryComponentType = () => {
|
|
69
123
|
const [value, setValue] = React.useState("12345");
|
|
70
124
|
|
|
@@ -80,7 +134,7 @@ export const Number: StoryComponentType = () => {
|
|
|
80
134
|
|
|
81
135
|
return (
|
|
82
136
|
<TextField
|
|
83
|
-
id="tf-
|
|
137
|
+
id="tf-3"
|
|
84
138
|
type="number"
|
|
85
139
|
value={value}
|
|
86
140
|
placeholder="Number"
|
|
@@ -90,6 +144,13 @@ export const Number: StoryComponentType = () => {
|
|
|
90
144
|
);
|
|
91
145
|
};
|
|
92
146
|
|
|
147
|
+
Number.parameters = {
|
|
148
|
+
docs: {
|
|
149
|
+
storyDescription:
|
|
150
|
+
"An input field with type `number` will only take numeric characters as input.",
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
|
|
93
154
|
export const Password: StoryComponentType = () => {
|
|
94
155
|
const [value, setValue] = React.useState("Password123");
|
|
95
156
|
const [errorMessage, setErrorMessage] = React.useState();
|
|
@@ -129,7 +190,7 @@ export const Password: StoryComponentType = () => {
|
|
|
129
190
|
return (
|
|
130
191
|
<View>
|
|
131
192
|
<TextField
|
|
132
|
-
id="tf-
|
|
193
|
+
id="tf-4"
|
|
133
194
|
type="password"
|
|
134
195
|
value={value}
|
|
135
196
|
placeholder="Password"
|
|
@@ -150,6 +211,15 @@ export const Password: StoryComponentType = () => {
|
|
|
150
211
|
);
|
|
151
212
|
};
|
|
152
213
|
|
|
214
|
+
Password.parameters = {
|
|
215
|
+
docs: {
|
|
216
|
+
storyDescription: `An input field with type \`password\` will
|
|
217
|
+
obscure the input value. It also often contains validation.
|
|
218
|
+
In this example, the password must be over 8 characters long and
|
|
219
|
+
must contain a numeric value.`,
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
|
|
153
223
|
export const Email: StoryComponentType = () => {
|
|
154
224
|
const [value, setValue] = React.useState("khan@khanacademy.org");
|
|
155
225
|
const [errorMessage, setErrorMessage] = React.useState();
|
|
@@ -187,7 +257,7 @@ export const Email: StoryComponentType = () => {
|
|
|
187
257
|
return (
|
|
188
258
|
<View>
|
|
189
259
|
<TextField
|
|
190
|
-
id="tf-
|
|
260
|
+
id="tf-5"
|
|
191
261
|
type="email"
|
|
192
262
|
value={value}
|
|
193
263
|
placeholder="Email"
|
|
@@ -208,6 +278,15 @@ export const Email: StoryComponentType = () => {
|
|
|
208
278
|
);
|
|
209
279
|
};
|
|
210
280
|
|
|
281
|
+
Email.parameters = {
|
|
282
|
+
docs: {
|
|
283
|
+
storyDescription: `An input field with type \`email\` will automatically
|
|
284
|
+
validate an input on submit to ensure it's either formatted properly
|
|
285
|
+
or blank. \`TextField\` will run validation on blur if the
|
|
286
|
+
\`validate\` prop is passed in, as in this example.`,
|
|
287
|
+
},
|
|
288
|
+
};
|
|
289
|
+
|
|
211
290
|
export const Telephone: StoryComponentType = () => {
|
|
212
291
|
const [value, setValue] = React.useState("123-456-7890");
|
|
213
292
|
const [errorMessage, setErrorMessage] = React.useState();
|
|
@@ -245,7 +324,7 @@ export const Telephone: StoryComponentType = () => {
|
|
|
245
324
|
return (
|
|
246
325
|
<View>
|
|
247
326
|
<TextField
|
|
248
|
-
id="tf-
|
|
327
|
+
id="tf-6"
|
|
249
328
|
type="tel"
|
|
250
329
|
value={value}
|
|
251
330
|
placeholder="Telephone"
|
|
@@ -266,6 +345,15 @@ export const Telephone: StoryComponentType = () => {
|
|
|
266
345
|
);
|
|
267
346
|
};
|
|
268
347
|
|
|
348
|
+
Telephone.parameters = {
|
|
349
|
+
docs: {
|
|
350
|
+
storyDescription: `An input field with type \`tel\` will NOT
|
|
351
|
+
validate an input on submit by default as telephone numbers
|
|
352
|
+
can vary considerably. \`TextField\` will run validation on blur
|
|
353
|
+
if the \`validate\` prop is passed in, as in this example.`,
|
|
354
|
+
},
|
|
355
|
+
};
|
|
356
|
+
|
|
269
357
|
export const Error: StoryComponentType = () => {
|
|
270
358
|
const [value, setValue] = React.useState("khan");
|
|
271
359
|
const [errorMessage, setErrorMessage] = React.useState();
|
|
@@ -303,7 +391,7 @@ export const Error: StoryComponentType = () => {
|
|
|
303
391
|
return (
|
|
304
392
|
<View>
|
|
305
393
|
<TextField
|
|
306
|
-
id="tf-
|
|
394
|
+
id="tf-7"
|
|
307
395
|
type="email"
|
|
308
396
|
value={value}
|
|
309
397
|
placeholder="Email"
|
|
@@ -324,15 +412,12 @@ export const Error: StoryComponentType = () => {
|
|
|
324
412
|
);
|
|
325
413
|
};
|
|
326
414
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
disabled={true}
|
|
334
|
-
/>
|
|
335
|
-
);
|
|
415
|
+
Error.parameters = {
|
|
416
|
+
docs: {
|
|
417
|
+
storyDescription: `If an input value fails validation,
|
|
418
|
+
\`TextField\` will have error styling.`,
|
|
419
|
+
},
|
|
420
|
+
};
|
|
336
421
|
|
|
337
422
|
export const Light: StoryComponentType = () => {
|
|
338
423
|
const [value, setValue] = React.useState("khan@khanacademy.org");
|
|
@@ -371,7 +456,7 @@ export const Light: StoryComponentType = () => {
|
|
|
371
456
|
return (
|
|
372
457
|
<View style={styles.darkBackground}>
|
|
373
458
|
<TextField
|
|
374
|
-
id="tf-
|
|
459
|
+
id="tf-9"
|
|
375
460
|
type="email"
|
|
376
461
|
value={value}
|
|
377
462
|
placeholder="Email"
|
|
@@ -395,6 +480,98 @@ export const Light: StoryComponentType = () => {
|
|
|
395
480
|
);
|
|
396
481
|
};
|
|
397
482
|
|
|
483
|
+
Light.parameters = {
|
|
484
|
+
docs: {
|
|
485
|
+
storyDescription: `If the \`light\` prop is set to true,
|
|
486
|
+
\`TextField\` will have light styling. This is intended to be used
|
|
487
|
+
on a dark background. There is also a specific light styling for the
|
|
488
|
+
error state, as seen in the \`ErrorLight\` story.`,
|
|
489
|
+
},
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
export const ErrorLight: StoryComponentType = () => {
|
|
493
|
+
const [value, setValue] = React.useState("khan");
|
|
494
|
+
const [errorMessage, setErrorMessage] = React.useState();
|
|
495
|
+
const [focused, setFocused] = React.useState(false);
|
|
496
|
+
|
|
497
|
+
const handleChange = (newValue: string) => {
|
|
498
|
+
setValue(newValue);
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
const validate = (value: string) => {
|
|
502
|
+
const emailRegex = /^[^@\s]+@[^@\s.]+\.[^@.\s]+$/;
|
|
503
|
+
if (!emailRegex.test(value)) {
|
|
504
|
+
return "Please enter a valid email";
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
const handleValidate = (errorMessage: ?string) => {
|
|
509
|
+
setErrorMessage(errorMessage);
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
const handleKeyDown = (event: SyntheticKeyboardEvent<HTMLInputElement>) => {
|
|
513
|
+
if (event.key === "Enter") {
|
|
514
|
+
event.currentTarget.blur();
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
const handleFocus = () => {
|
|
519
|
+
setFocused(true);
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
const handleBlur = () => {
|
|
523
|
+
setFocused(false);
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
return (
|
|
527
|
+
<View style={styles.darkBackground}>
|
|
528
|
+
<TextField
|
|
529
|
+
id="tf-7"
|
|
530
|
+
type="email"
|
|
531
|
+
value={value}
|
|
532
|
+
placeholder="Email"
|
|
533
|
+
light={true}
|
|
534
|
+
validate={validate}
|
|
535
|
+
onValidate={handleValidate}
|
|
536
|
+
onChange={handleChange}
|
|
537
|
+
onKeyDown={handleKeyDown}
|
|
538
|
+
onFocus={handleFocus}
|
|
539
|
+
onBlur={handleBlur}
|
|
540
|
+
/>
|
|
541
|
+
{!focused && errorMessage && (
|
|
542
|
+
<View>
|
|
543
|
+
<Strut size={Spacing.xSmall_8} />
|
|
544
|
+
<_Text style={styles.errorMessage}>{errorMessage}</_Text>
|
|
545
|
+
</View>
|
|
546
|
+
)}
|
|
547
|
+
</View>
|
|
548
|
+
);
|
|
549
|
+
};
|
|
550
|
+
|
|
551
|
+
ErrorLight.parameters = {
|
|
552
|
+
docs: {
|
|
553
|
+
storyDescription: `If an input value fails validation and the
|
|
554
|
+
\`light\` prop is true, \`TextField\` will have light error styling.`,
|
|
555
|
+
},
|
|
556
|
+
};
|
|
557
|
+
|
|
558
|
+
export const Disabled: StoryComponentType = () => (
|
|
559
|
+
<TextField
|
|
560
|
+
id="tf-8"
|
|
561
|
+
value=""
|
|
562
|
+
placeholder="This field is disabled."
|
|
563
|
+
onChange={() => {}}
|
|
564
|
+
disabled={true}
|
|
565
|
+
/>
|
|
566
|
+
);
|
|
567
|
+
|
|
568
|
+
Disabled.parameters = {
|
|
569
|
+
docs: {
|
|
570
|
+
storyDescription: `If the \`disabled\` prop is set to true,
|
|
571
|
+
\`TextField\` will have disabled styling and will not be interactable.`,
|
|
572
|
+
},
|
|
573
|
+
};
|
|
574
|
+
|
|
398
575
|
export const CustomStyle: StoryComponentType = () => {
|
|
399
576
|
const [value, setValue] = React.useState("");
|
|
400
577
|
|
|
@@ -410,7 +587,7 @@ export const CustomStyle: StoryComponentType = () => {
|
|
|
410
587
|
|
|
411
588
|
return (
|
|
412
589
|
<TextField
|
|
413
|
-
id="tf-
|
|
590
|
+
id="tf-10"
|
|
414
591
|
style={styles.customField}
|
|
415
592
|
type="text"
|
|
416
593
|
value={value}
|
|
@@ -421,6 +598,15 @@ export const CustomStyle: StoryComponentType = () => {
|
|
|
421
598
|
);
|
|
422
599
|
};
|
|
423
600
|
|
|
601
|
+
CustomStyle.parameters = {
|
|
602
|
+
docs: {
|
|
603
|
+
storyDescription: `\`TextField\` can take in custom styles that
|
|
604
|
+
override the default styles. This example has custom styles for the
|
|
605
|
+
\`backgroundColor\`, \`color\`, \`border\`, \`maxWidth\`, and
|
|
606
|
+
placeholder \`color\` properties.`,
|
|
607
|
+
},
|
|
608
|
+
};
|
|
609
|
+
|
|
424
610
|
export const Ref: StoryComponentType = () => {
|
|
425
611
|
const [value, setValue] = React.useState("");
|
|
426
612
|
const inputRef: RefObject<typeof HTMLInputElement> = React.createRef();
|
|
@@ -444,7 +630,7 @@ export const Ref: StoryComponentType = () => {
|
|
|
444
630
|
return (
|
|
445
631
|
<View>
|
|
446
632
|
<TextField
|
|
447
|
-
id="tf-
|
|
633
|
+
id="tf-11"
|
|
448
634
|
type="text"
|
|
449
635
|
value={value}
|
|
450
636
|
placeholder="Text"
|
|
@@ -460,6 +646,23 @@ export const Ref: StoryComponentType = () => {
|
|
|
460
646
|
);
|
|
461
647
|
};
|
|
462
648
|
|
|
649
|
+
Ref.parameters = {
|
|
650
|
+
docs: {
|
|
651
|
+
storyDescription: `If you need to save a reference to the input
|
|
652
|
+
field, you can do so by using the \`ref\` prop. In this example,
|
|
653
|
+
we want the input field to receive focus when the button is
|
|
654
|
+
pressed. We can do this by creating a React ref of type
|
|
655
|
+
\`HTMLInputElement\` and passing it into \`TextField\`'s \`ref\` prop.
|
|
656
|
+
Now we can use the ref variable in the \`handleSubmit\` function to
|
|
657
|
+
shift focus to the field.`,
|
|
658
|
+
chromatic: {
|
|
659
|
+
// Disabling snapshot because this is testing interaction,
|
|
660
|
+
// not visuals.
|
|
661
|
+
disableSnapshot: true,
|
|
662
|
+
},
|
|
663
|
+
},
|
|
664
|
+
};
|
|
665
|
+
|
|
463
666
|
export const ReadOnly: StoryComponentType = () => {
|
|
464
667
|
const [value, setValue] = React.useState("Khan");
|
|
465
668
|
|
|
@@ -475,7 +678,7 @@ export const ReadOnly: StoryComponentType = () => {
|
|
|
475
678
|
|
|
476
679
|
return (
|
|
477
680
|
<TextField
|
|
478
|
-
id="tf-
|
|
681
|
+
id="tf-12"
|
|
479
682
|
type="text"
|
|
480
683
|
value={value}
|
|
481
684
|
placeholder="Text"
|
|
@@ -486,6 +689,20 @@ export const ReadOnly: StoryComponentType = () => {
|
|
|
486
689
|
);
|
|
487
690
|
};
|
|
488
691
|
|
|
692
|
+
ReadOnly.parameters = {
|
|
693
|
+
docs: {
|
|
694
|
+
storyDescription: `An input field with the prop \`readOnly\` set
|
|
695
|
+
to true is not interactable. It looks the same as if it were not
|
|
696
|
+
read only, and it can still receive focus, but the interaction
|
|
697
|
+
point will not appear and the input will not change.`,
|
|
698
|
+
chromatic: {
|
|
699
|
+
// Disabling snapshot because this is testing interaction,
|
|
700
|
+
// not visuals.
|
|
701
|
+
disableSnapshot: true,
|
|
702
|
+
},
|
|
703
|
+
},
|
|
704
|
+
};
|
|
705
|
+
|
|
489
706
|
export const AutoComplete: StoryComponentType = () => {
|
|
490
707
|
const [value, setValue] = React.useState("");
|
|
491
708
|
|
|
@@ -500,18 +717,38 @@ export const AutoComplete: StoryComponentType = () => {
|
|
|
500
717
|
};
|
|
501
718
|
|
|
502
719
|
return (
|
|
503
|
-
<
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
720
|
+
<form>
|
|
721
|
+
<TextField
|
|
722
|
+
id="tf-13"
|
|
723
|
+
type="text"
|
|
724
|
+
value={value}
|
|
725
|
+
placeholder="Name"
|
|
726
|
+
onChange={handleChange}
|
|
727
|
+
onKeyDown={handleKeyDown}
|
|
728
|
+
style={styles.fieldWithButton}
|
|
729
|
+
autoComplete="name"
|
|
730
|
+
/>
|
|
731
|
+
<Button type="submit">Submit</Button>
|
|
732
|
+
</form>
|
|
512
733
|
);
|
|
513
734
|
};
|
|
514
735
|
|
|
736
|
+
AutoComplete.parameters = {
|
|
737
|
+
docs: {
|
|
738
|
+
storyDescription: `If \`TextField\`'s \`autocomplete\` prop is set,
|
|
739
|
+
the browser can predict values for the input. When the user starts
|
|
740
|
+
to type in the field, a list of options will show up based on
|
|
741
|
+
values that may have been submitted at a previous time.
|
|
742
|
+
In this example, the text field provides options after you
|
|
743
|
+
input a value, press the submit button, and refresh the page.`,
|
|
744
|
+
chromatic: {
|
|
745
|
+
// Disabling snapshot because this is testing interaction,
|
|
746
|
+
// not visuals.
|
|
747
|
+
disableSnapshot: true,
|
|
748
|
+
},
|
|
749
|
+
},
|
|
750
|
+
};
|
|
751
|
+
|
|
515
752
|
const styles = StyleSheet.create({
|
|
516
753
|
errorMessage: {
|
|
517
754
|
color: Color.red,
|
|
@@ -537,4 +774,7 @@ const styles = StyleSheet.create({
|
|
|
537
774
|
button: {
|
|
538
775
|
maxWidth: 150,
|
|
539
776
|
},
|
|
777
|
+
fieldWithButton: {
|
|
778
|
+
marginBottom: Spacing.medium_16,
|
|
779
|
+
},
|
|
540
780
|
});
|
|
@@ -76,6 +76,33 @@ const StyledLegend = addStyle<"legend">("legend");
|
|
|
76
76
|
* many props for its children Choice components. The Choice component is
|
|
77
77
|
* exposed for the user to apply custom styles or to indicate which choices are
|
|
78
78
|
* disabled.
|
|
79
|
+
*
|
|
80
|
+
* ### Usage
|
|
81
|
+
*
|
|
82
|
+
* ```jsx
|
|
83
|
+
* import {Choice, CheckboxGroup} from "@khanacademy/wonder-blocks-form";
|
|
84
|
+
*
|
|
85
|
+
* const [selectedValues, setSelectedValues] = React.useState([]);
|
|
86
|
+
*
|
|
87
|
+
* <CheckboxGroup
|
|
88
|
+
* label="some-label"
|
|
89
|
+
* description="some-description"
|
|
90
|
+
* groupName="some-group-name"
|
|
91
|
+
* onChange={setSelectedValues}
|
|
92
|
+
* selectedValues={selectedValues}
|
|
93
|
+
* />
|
|
94
|
+
* // Add as many choices as necessary
|
|
95
|
+
* <Choice
|
|
96
|
+
* label="Choice 1"
|
|
97
|
+
* value="some-choice-value"
|
|
98
|
+
* />
|
|
99
|
+
* <Choice
|
|
100
|
+
* label="Choice 2"
|
|
101
|
+
* value="some-choice-value-2"
|
|
102
|
+
* description="Some choice description."
|
|
103
|
+
* />
|
|
104
|
+
* </CheckboxGroup>
|
|
105
|
+
* ```
|
|
79
106
|
*/
|
|
80
107
|
export default class CheckboxGroup extends React.Component<CheckboxGroupProps> {
|
|
81
108
|
handleChange(changedValue: string, originalCheckedState: boolean) {
|
|
@@ -79,8 +79,22 @@ type DefaultProps = {|
|
|
|
79
79
|
* ☑️ A nicely styled checkbox for all your checking needs. Can optionally take
|
|
80
80
|
* label and description props.
|
|
81
81
|
*
|
|
82
|
+
* If used by itself, a checkbox provides two options - checked and unchecked.
|
|
83
|
+
* A group of checkboxes can be used to allow a user to select multiple values
|
|
84
|
+
* from a list of options.
|
|
85
|
+
*
|
|
82
86
|
* If you want a whole group of Checkbox[es] that are related, see the Choice
|
|
83
87
|
* and CheckboxGroup components.
|
|
88
|
+
*
|
|
89
|
+
* ### Usage
|
|
90
|
+
*
|
|
91
|
+
* ```jsx
|
|
92
|
+
* import {Checkbox} from "@khanacademy/wonder-blocks-form";
|
|
93
|
+
*
|
|
94
|
+
* const [checked, setChecked] = React.useState(false);
|
|
95
|
+
*
|
|
96
|
+
* <Checkbox checked={checked} onChange={setChecked} />
|
|
97
|
+
* ```
|
|
84
98
|
*/
|
|
85
99
|
export default class Checkbox extends React.Component<ChoiceComponentProps> {
|
|
86
100
|
static defaultProps: DefaultProps = {
|
package/src/components/choice.js
CHANGED
|
@@ -80,7 +80,65 @@ type DefaultProps = {|
|
|
|
80
80
|
*
|
|
81
81
|
* If you wish to use just a single field, use Checkbox or Radio with the
|
|
82
82
|
* optional label and description props.
|
|
83
|
-
|
|
83
|
+
*
|
|
84
|
+
* ### Checkbox Usage
|
|
85
|
+
*
|
|
86
|
+
* ```jsx
|
|
87
|
+
* import {Choice, CheckboxGroup} from "@khanacademy/wonder-blocks-form";
|
|
88
|
+
*
|
|
89
|
+
* const [selectedValues, setSelectedValues] = React.useState([]);
|
|
90
|
+
*
|
|
91
|
+
* // Checkbox usage
|
|
92
|
+
* <CheckboxGroup
|
|
93
|
+
* label="some-label"
|
|
94
|
+
* description="some-description"
|
|
95
|
+
* groupName="some-group-name"
|
|
96
|
+
* onChange={setSelectedValues}
|
|
97
|
+
* selectedValues={selectedValues}
|
|
98
|
+
* />
|
|
99
|
+
* // Add as many choices as necessary
|
|
100
|
+
* <Choice
|
|
101
|
+
* label="Choice 1"
|
|
102
|
+
* value="some-choice-value"
|
|
103
|
+
* description="Some choice description."
|
|
104
|
+
* />
|
|
105
|
+
* <Choice
|
|
106
|
+
* label="Choice 2"
|
|
107
|
+
* value="some-choice-value-2"
|
|
108
|
+
* description="Some choice description."
|
|
109
|
+
* />
|
|
110
|
+
* </CheckboxGroup>
|
|
111
|
+
* ```
|
|
112
|
+
*
|
|
113
|
+
* ### Radio Usage
|
|
114
|
+
*
|
|
115
|
+
* ```jsx
|
|
116
|
+
* import {Choice, RadioGroup} from "@khanacademy/wonder-blocks-form";
|
|
117
|
+
*
|
|
118
|
+
* const [selectedValue, setSelectedValue] = React.useState("");
|
|
119
|
+
*
|
|
120
|
+
* <RadioGroup
|
|
121
|
+
* label="some-label"
|
|
122
|
+
* description="some-description"
|
|
123
|
+
* groupName="some-group-name"
|
|
124
|
+
* onChange={setSelectedValue}>
|
|
125
|
+
* selectedValues={selectedValue}
|
|
126
|
+
* />
|
|
127
|
+
* // Add as many choices as necessary
|
|
128
|
+
* <Choice
|
|
129
|
+
* label="Choice 1"
|
|
130
|
+
* value="some-choice-value"
|
|
131
|
+
* description="Some choice description."
|
|
132
|
+
* />
|
|
133
|
+
* <Choice
|
|
134
|
+
* label="Choice 2"
|
|
135
|
+
* value="some-choice-value-2"
|
|
136
|
+
* description="Some choice description."
|
|
137
|
+
* />
|
|
138
|
+
* </RadioGroup>
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
export default class Choice extends React.Component<Props> {
|
|
84
142
|
static defaultProps: DefaultProps = {
|
|
85
143
|
checked: false,
|
|
86
144
|
disabled: false,
|
|
@@ -293,6 +293,26 @@ type ExportProps = $Diff<
|
|
|
293
293
|
WithForwardRef,
|
|
294
294
|
>;
|
|
295
295
|
|
|
296
|
+
/**
|
|
297
|
+
* A LabeledTextField is an element used to accept a single line of text
|
|
298
|
+
* from the user paired with a label, description, and error field elements.
|
|
299
|
+
*
|
|
300
|
+
* ### Usage
|
|
301
|
+
*
|
|
302
|
+
* ```jsx
|
|
303
|
+
* import {LabeledTextField} from "@khanacademy/wonder-blocks-form";
|
|
304
|
+
*
|
|
305
|
+
* const [value, setValue] = React.useState("");
|
|
306
|
+
*
|
|
307
|
+
* <LabeledTextField
|
|
308
|
+
* label="Label"
|
|
309
|
+
* description="Hello, this is the description for this field"
|
|
310
|
+
* placeholder="Placeholder"
|
|
311
|
+
* value={value}
|
|
312
|
+
* onChange={setValue}
|
|
313
|
+
* />
|
|
314
|
+
* ```
|
|
315
|
+
*/
|
|
296
316
|
const LabeledTextField: React.AbstractComponent<ExportProps, HTMLInputElement> =
|
|
297
317
|
React.forwardRef<ExportProps, HTMLInputElement>((props, ref) => (
|
|
298
318
|
<LabeledTextFieldInternal {...props} forwardedRef={ref} />
|
|
@@ -73,6 +73,33 @@ const StyledLegend = addStyle<"legend">("legend");
|
|
|
73
73
|
* indicate which choices are disabled. The use of the groupName prop is
|
|
74
74
|
* important to maintain expected keyboard navigation behavior for
|
|
75
75
|
* accessibility.
|
|
76
|
+
*
|
|
77
|
+
* ### Usage
|
|
78
|
+
*
|
|
79
|
+
* ```jsx
|
|
80
|
+
* import {Choice, RadioGroup} from "@khanacademy/wonder-blocks-form";
|
|
81
|
+
*
|
|
82
|
+
* const [selectedValue, setSelectedValue] = React.useState([]);
|
|
83
|
+
*
|
|
84
|
+
* <RadioGroup
|
|
85
|
+
* label="some-label"
|
|
86
|
+
* description="some-description"
|
|
87
|
+
* groupName="some-group-name"
|
|
88
|
+
* onChange={setSelectedValue}
|
|
89
|
+
* selectedValue={selectedValue}
|
|
90
|
+
* />
|
|
91
|
+
* // Add as many choices as necessary
|
|
92
|
+
* <Choice
|
|
93
|
+
* label="Choice 1"
|
|
94
|
+
* value="some-choice-value"
|
|
95
|
+
* />
|
|
96
|
+
* <Choice
|
|
97
|
+
* label="Choice 2"
|
|
98
|
+
* value="some-choice-value-2"
|
|
99
|
+
* description="Some choice description."
|
|
100
|
+
* />
|
|
101
|
+
* </RadioGroup>
|
|
102
|
+
* ```
|
|
76
103
|
*/
|
|
77
104
|
export default class RadioGroup extends React.Component<RadioGroupProps> {
|
|
78
105
|
handleChange(changedValue: string) {
|
|
@@ -348,6 +348,23 @@ type ExportProps = $Diff<
|
|
|
348
348
|
WithForwardRef,
|
|
349
349
|
>;
|
|
350
350
|
|
|
351
|
+
/**
|
|
352
|
+
* A TextField is an element used to accept a single line of text from the user.
|
|
353
|
+
*
|
|
354
|
+
* ### Usage
|
|
355
|
+
*
|
|
356
|
+
* ```jsx
|
|
357
|
+
* import {TextField} from "@khanacademy/wonder-blocks-form";
|
|
358
|
+
*
|
|
359
|
+
* const [value, setValue] = React.useState("");
|
|
360
|
+
*
|
|
361
|
+
* <TextField
|
|
362
|
+
* id="some-unique-text-field-id"
|
|
363
|
+
* value={value}
|
|
364
|
+
* onChange={setValue}
|
|
365
|
+
* />
|
|
366
|
+
* ```
|
|
367
|
+
*/
|
|
351
368
|
const TextField: React.AbstractComponent<ExportProps, HTMLInputElement> =
|
|
352
369
|
React.forwardRef<ExportProps, HTMLInputElement>((props, ref) => (
|
|
353
370
|
<TextFieldInternal {...props} forwardedRef={ref} />
|