@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
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# @khanacademy/wonder-blocks-form
|
|
2
2
|
|
|
3
|
+
## 2.4.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [ee6fc773]
|
|
8
|
+
- @khanacademy/wonder-blocks-clickable@2.3.0
|
|
9
|
+
|
|
10
|
+
## 2.4.5
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- Updated dependencies [83486dba]
|
|
15
|
+
- @khanacademy/wonder-blocks-icon@1.2.29
|
|
16
|
+
|
|
17
|
+
## 2.4.4
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- Updated dependencies [5f4a4297]
|
|
22
|
+
- Updated dependencies [2b96fd59]
|
|
23
|
+
- @khanacademy/wonder-blocks-core@4.3.2
|
|
24
|
+
- @khanacademy/wonder-blocks-clickable@2.2.7
|
|
25
|
+
- @khanacademy/wonder-blocks-icon@1.2.28
|
|
26
|
+
- @khanacademy/wonder-blocks-layout@1.4.10
|
|
27
|
+
- @khanacademy/wonder-blocks-typography@1.1.32
|
|
28
|
+
|
|
3
29
|
## 2.4.3
|
|
4
30
|
|
|
5
31
|
### Patch Changes
|
package/dist/index.js
CHANGED
|
@@ -182,8 +182,22 @@ function _extends() { _extends = Object.assign || function (target) { for (var i
|
|
|
182
182
|
* ☑️ A nicely styled checkbox for all your checking needs. Can optionally take
|
|
183
183
|
* label and description props.
|
|
184
184
|
*
|
|
185
|
+
* If used by itself, a checkbox provides two options - checked and unchecked.
|
|
186
|
+
* A group of checkboxes can be used to allow a user to select multiple values
|
|
187
|
+
* from a list of options.
|
|
188
|
+
*
|
|
185
189
|
* If you want a whole group of Checkbox[es] that are related, see the Choice
|
|
186
190
|
* and CheckboxGroup components.
|
|
191
|
+
*
|
|
192
|
+
* ### Usage
|
|
193
|
+
*
|
|
194
|
+
* ```jsx
|
|
195
|
+
* import {Checkbox} from "@khanacademy/wonder-blocks-form";
|
|
196
|
+
*
|
|
197
|
+
* const [checked, setChecked] = React.useState(false);
|
|
198
|
+
*
|
|
199
|
+
* <Checkbox checked={checked} onChange={setChecked} />
|
|
200
|
+
* ```
|
|
187
201
|
*/
|
|
188
202
|
class Checkbox extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
|
|
189
203
|
render() {
|
|
@@ -450,6 +464,24 @@ const styles = aphrodite__WEBPACK_IMPORTED_MODULE_1__["StyleSheet"].create({
|
|
|
450
464
|
boxShadow: `0px 0px 0px 1px ${_khanacademy_wonder_blocks_color__WEBPACK_IMPORTED_MODULE_2___default.a.red}, 0px 0px 0px 2px ${_khanacademy_wonder_blocks_color__WEBPACK_IMPORTED_MODULE_2___default.a.white}`
|
|
451
465
|
}
|
|
452
466
|
});
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* A TextField is an element used to accept a single line of text from the user.
|
|
470
|
+
*
|
|
471
|
+
* ### Usage
|
|
472
|
+
*
|
|
473
|
+
* ```jsx
|
|
474
|
+
* import {TextField} from "@khanacademy/wonder-blocks-form";
|
|
475
|
+
*
|
|
476
|
+
* const [value, setValue] = React.useState("");
|
|
477
|
+
*
|
|
478
|
+
* <TextField
|
|
479
|
+
* id="some-unique-text-field-id"
|
|
480
|
+
* value={value}
|
|
481
|
+
* onChange={setValue}
|
|
482
|
+
* />
|
|
483
|
+
* ```
|
|
484
|
+
*/
|
|
453
485
|
const TextField = /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["forwardRef"]((props, ref) => /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["createElement"](TextFieldInternal, _extends({}, props, {
|
|
454
486
|
forwardedRef: ref
|
|
455
487
|
})));
|
|
@@ -652,6 +684,63 @@ const styles = aphrodite__WEBPACK_IMPORTED_MODULE_1__["StyleSheet"].create({
|
|
|
652
684
|
*
|
|
653
685
|
* If you wish to use just a single field, use Checkbox or Radio with the
|
|
654
686
|
* optional label and description props.
|
|
687
|
+
*
|
|
688
|
+
* ### Checkbox Usage
|
|
689
|
+
*
|
|
690
|
+
* ```jsx
|
|
691
|
+
* import {Choice, CheckboxGroup} from "@khanacademy/wonder-blocks-form";
|
|
692
|
+
*
|
|
693
|
+
* const [selectedValues, setSelectedValues] = React.useState([]);
|
|
694
|
+
*
|
|
695
|
+
* // Checkbox usage
|
|
696
|
+
* <CheckboxGroup
|
|
697
|
+
* label="some-label"
|
|
698
|
+
* description="some-description"
|
|
699
|
+
* groupName="some-group-name"
|
|
700
|
+
* onChange={setSelectedValues}
|
|
701
|
+
* selectedValues={selectedValues}
|
|
702
|
+
* />
|
|
703
|
+
* // Add as many choices as necessary
|
|
704
|
+
* <Choice
|
|
705
|
+
* label="Choice 1"
|
|
706
|
+
* value="some-choice-value"
|
|
707
|
+
* description="Some choice description."
|
|
708
|
+
* />
|
|
709
|
+
* <Choice
|
|
710
|
+
* label="Choice 2"
|
|
711
|
+
* value="some-choice-value-2"
|
|
712
|
+
* description="Some choice description."
|
|
713
|
+
* />
|
|
714
|
+
* </CheckboxGroup>
|
|
715
|
+
* ```
|
|
716
|
+
*
|
|
717
|
+
* ### Radio Usage
|
|
718
|
+
*
|
|
719
|
+
* ```jsx
|
|
720
|
+
* import {Choice, RadioGroup} from "@khanacademy/wonder-blocks-form";
|
|
721
|
+
*
|
|
722
|
+
* const [selectedValue, setSelectedValue] = React.useState("");
|
|
723
|
+
*
|
|
724
|
+
* <RadioGroup
|
|
725
|
+
* label="some-label"
|
|
726
|
+
* description="some-description"
|
|
727
|
+
* groupName="some-group-name"
|
|
728
|
+
* onChange={setSelectedValue}>
|
|
729
|
+
* selectedValues={selectedValue}
|
|
730
|
+
* />
|
|
731
|
+
* // Add as many choices as necessary
|
|
732
|
+
* <Choice
|
|
733
|
+
* label="Choice 1"
|
|
734
|
+
* value="some-choice-value"
|
|
735
|
+
* description="Some choice description."
|
|
736
|
+
* />
|
|
737
|
+
* <Choice
|
|
738
|
+
* label="Choice 2"
|
|
739
|
+
* value="some-choice-value-2"
|
|
740
|
+
* description="Some choice description."
|
|
741
|
+
* />
|
|
742
|
+
* </RadioGroup>
|
|
743
|
+
* ```
|
|
655
744
|
*/
|
|
656
745
|
class Choice extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
|
|
657
746
|
getChoiceComponent(variant) {
|
|
@@ -711,6 +800,33 @@ const StyledLegend = Object(_khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MO
|
|
|
711
800
|
* many props for its children Choice components. The Choice component is
|
|
712
801
|
* exposed for the user to apply custom styles or to indicate which choices are
|
|
713
802
|
* disabled.
|
|
803
|
+
*
|
|
804
|
+
* ### Usage
|
|
805
|
+
*
|
|
806
|
+
* ```jsx
|
|
807
|
+
* import {Choice, CheckboxGroup} from "@khanacademy/wonder-blocks-form";
|
|
808
|
+
*
|
|
809
|
+
* const [selectedValues, setSelectedValues] = React.useState([]);
|
|
810
|
+
*
|
|
811
|
+
* <CheckboxGroup
|
|
812
|
+
* label="some-label"
|
|
813
|
+
* description="some-description"
|
|
814
|
+
* groupName="some-group-name"
|
|
815
|
+
* onChange={setSelectedValues}
|
|
816
|
+
* selectedValues={selectedValues}
|
|
817
|
+
* />
|
|
818
|
+
* // Add as many choices as necessary
|
|
819
|
+
* <Choice
|
|
820
|
+
* label="Choice 1"
|
|
821
|
+
* value="some-choice-value"
|
|
822
|
+
* />
|
|
823
|
+
* <Choice
|
|
824
|
+
* label="Choice 2"
|
|
825
|
+
* value="some-choice-value-2"
|
|
826
|
+
* description="Some choice description."
|
|
827
|
+
* />
|
|
828
|
+
* </CheckboxGroup>
|
|
829
|
+
* ```
|
|
714
830
|
*/
|
|
715
831
|
|
|
716
832
|
class CheckboxGroup extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
|
|
@@ -806,6 +922,33 @@ const StyledLegend = Object(_khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MO
|
|
|
806
922
|
* indicate which choices are disabled. The use of the groupName prop is
|
|
807
923
|
* important to maintain expected keyboard navigation behavior for
|
|
808
924
|
* accessibility.
|
|
925
|
+
*
|
|
926
|
+
* ### Usage
|
|
927
|
+
*
|
|
928
|
+
* ```jsx
|
|
929
|
+
* import {Choice, RadioGroup} from "@khanacademy/wonder-blocks-form";
|
|
930
|
+
*
|
|
931
|
+
* const [selectedValue, setSelectedValue] = React.useState([]);
|
|
932
|
+
*
|
|
933
|
+
* <RadioGroup
|
|
934
|
+
* label="some-label"
|
|
935
|
+
* description="some-description"
|
|
936
|
+
* groupName="some-group-name"
|
|
937
|
+
* onChange={setSelectedValue}
|
|
938
|
+
* selectedValue={selectedValue}
|
|
939
|
+
* />
|
|
940
|
+
* // Add as many choices as necessary
|
|
941
|
+
* <Choice
|
|
942
|
+
* label="Choice 1"
|
|
943
|
+
* value="some-choice-value"
|
|
944
|
+
* />
|
|
945
|
+
* <Choice
|
|
946
|
+
* label="Choice 2"
|
|
947
|
+
* value="some-choice-value-2"
|
|
948
|
+
* description="Some choice description."
|
|
949
|
+
* />
|
|
950
|
+
* </RadioGroup>
|
|
951
|
+
* ```
|
|
809
952
|
*/
|
|
810
953
|
|
|
811
954
|
class RadioGroup extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
|
|
@@ -995,6 +1138,27 @@ LabeledTextFieldInternal.defaultProps = {
|
|
|
995
1138
|
disabled: false,
|
|
996
1139
|
light: false
|
|
997
1140
|
};
|
|
1141
|
+
|
|
1142
|
+
/**
|
|
1143
|
+
* A LabeledTextField is an element used to accept a single line of text
|
|
1144
|
+
* from the user paired with a label, description, and error field elements.
|
|
1145
|
+
*
|
|
1146
|
+
* ### Usage
|
|
1147
|
+
*
|
|
1148
|
+
* ```jsx
|
|
1149
|
+
* import {LabeledTextField} from "@khanacademy/wonder-blocks-form";
|
|
1150
|
+
*
|
|
1151
|
+
* const [value, setValue] = React.useState("");
|
|
1152
|
+
*
|
|
1153
|
+
* <LabeledTextField
|
|
1154
|
+
* label="Label"
|
|
1155
|
+
* description="Hello, this is the description for this field"
|
|
1156
|
+
* placeholder="Placeholder"
|
|
1157
|
+
* value={value}
|
|
1158
|
+
* onChange={setValue}
|
|
1159
|
+
* />
|
|
1160
|
+
* ```
|
|
1161
|
+
*/
|
|
998
1162
|
const LabeledTextField = /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["forwardRef"]((props, ref) => /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["createElement"](LabeledTextFieldInternal, _extends({}, props, {
|
|
999
1163
|
forwardedRef: ref
|
|
1000
1164
|
})));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@khanacademy/wonder-blocks-form",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.6",
|
|
4
4
|
"design": "v1",
|
|
5
5
|
"description": "Form components for Wonder Blocks.",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -15,14 +15,14 @@
|
|
|
15
15
|
"access": "public"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@babel/runtime": "^7.
|
|
19
|
-
"@khanacademy/wonder-blocks-clickable": "^2.
|
|
18
|
+
"@babel/runtime": "^7.18.6",
|
|
19
|
+
"@khanacademy/wonder-blocks-clickable": "^2.3.0",
|
|
20
20
|
"@khanacademy/wonder-blocks-color": "^1.1.20",
|
|
21
|
-
"@khanacademy/wonder-blocks-core": "^4.3.
|
|
22
|
-
"@khanacademy/wonder-blocks-icon": "^1.2.
|
|
23
|
-
"@khanacademy/wonder-blocks-layout": "^1.4.
|
|
21
|
+
"@khanacademy/wonder-blocks-core": "^4.3.2",
|
|
22
|
+
"@khanacademy/wonder-blocks-icon": "^1.2.29",
|
|
23
|
+
"@khanacademy/wonder-blocks-layout": "^1.4.10",
|
|
24
24
|
"@khanacademy/wonder-blocks-spacing": "^3.0.5",
|
|
25
|
-
"@khanacademy/wonder-blocks-typography": "^1.1.
|
|
25
|
+
"@khanacademy/wonder-blocks-typography": "^1.1.32"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
28
|
"aphrodite": "^1.2.5",
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import {Meta, Story, Canvas} from "@storybook/addon-docs";
|
|
2
|
+
import {StyleSheet} from "aphrodite";
|
|
3
|
+
|
|
4
|
+
import Color from "@khanacademy/wonder-blocks-color";
|
|
5
|
+
import {Checkbox} from "@khanacademy/wonder-blocks-form";
|
|
6
|
+
import {LabelSmall} from "@khanacademy/wonder-blocks-typography";
|
|
7
|
+
|
|
8
|
+
<Meta
|
|
9
|
+
title="Form/Checkbox/Accessibility"
|
|
10
|
+
component={Checkbox}
|
|
11
|
+
parameters={{
|
|
12
|
+
previewTabs: {
|
|
13
|
+
canvas: {hidden: true},
|
|
14
|
+
},
|
|
15
|
+
viewMode: "docs",
|
|
16
|
+
chromatic: {
|
|
17
|
+
// Disables chromatic testing for these stories.
|
|
18
|
+
disableSnapshot: true,
|
|
19
|
+
},
|
|
20
|
+
}}
|
|
21
|
+
/>
|
|
22
|
+
|
|
23
|
+
export const ErrorTemplate = (args) => {
|
|
24
|
+
const [checked, setChecked] = React.useState(false);
|
|
25
|
+
const errorState = !checked;
|
|
26
|
+
return (
|
|
27
|
+
<View>
|
|
28
|
+
<Checkbox
|
|
29
|
+
checked={checked}
|
|
30
|
+
onChange={setChecked}
|
|
31
|
+
error={errorState}
|
|
32
|
+
aria-describedby={errorState && "error-message"}
|
|
33
|
+
aria-required={true}
|
|
34
|
+
{...args}
|
|
35
|
+
/>
|
|
36
|
+
{errorState && (
|
|
37
|
+
<LabelSmall style={styles.error} id="error-message">
|
|
38
|
+
You must agree to the terms to continue
|
|
39
|
+
</LabelSmall>
|
|
40
|
+
)}
|
|
41
|
+
</View>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const DisabledTemplate = (args) => {
|
|
46
|
+
const [checked, setChecked] = React.useState(false);
|
|
47
|
+
const errorState = !checked;
|
|
48
|
+
return (
|
|
49
|
+
<Checkbox
|
|
50
|
+
checked={checked}
|
|
51
|
+
onChange={setChecked}
|
|
52
|
+
label="Some setting"
|
|
53
|
+
description="You do not have permission to change this setting"
|
|
54
|
+
disabled={true}
|
|
55
|
+
/>
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
## Accessibility
|
|
60
|
+
|
|
61
|
+
### ARIA
|
|
62
|
+
|
|
63
|
+
`Checkbox` can take in all ARIA props defined in Wonder Blocks Core types.
|
|
64
|
+
|
|
65
|
+
Elements with role `"checkbox"` can have an `aria-checked` property that
|
|
66
|
+
exposes the checked state to assistive technology. The dev does not have
|
|
67
|
+
to worry about this because the Wonder Blocks Checkbox component is an
|
|
68
|
+
`input` element with type `"checkbox"`, as this has built-in semantics and
|
|
69
|
+
does not require ARIA.
|
|
70
|
+
|
|
71
|
+
The current implementation of `Checkbox` uses `aria-describedby` with the
|
|
72
|
+
label and description that may be passed in as props.
|
|
73
|
+
|
|
74
|
+
See the Error section for information about `aria-invalid` and
|
|
75
|
+
`aria-required`.
|
|
76
|
+
|
|
77
|
+
### Error state
|
|
78
|
+
|
|
79
|
+
The Wonder Blocks `Checkbox` component takes an `error` boolean prop. Setting
|
|
80
|
+
this prop to true will set `aria-invalid` to true, and the color of the
|
|
81
|
+
checkbox to red.
|
|
82
|
+
|
|
83
|
+
When a form input is invalid, the user should provide a reason for why
|
|
84
|
+
this is.
|
|
85
|
+
|
|
86
|
+
Generally, it is also suggested this is the validation error message is
|
|
87
|
+
passed to the checkbox's `aria-describedby` prop so assistive tech can
|
|
88
|
+
read it. However, this is not possible with the current implementation of
|
|
89
|
+
the Wonder Blocks Form Checkbox component.
|
|
90
|
+
|
|
91
|
+
The error state can be used to signal that a required checkbox has not been
|
|
92
|
+
checked. In cases where a checkbox is required, the checkbox component should
|
|
93
|
+
set the `aria-required` prop to true for assistive tech.
|
|
94
|
+
There should also be some sort of visual indication that checking
|
|
95
|
+
the box is required, such as a "Required" label or an asterisk.
|
|
96
|
+
|
|
97
|
+
<Canvas>
|
|
98
|
+
<Story
|
|
99
|
+
name="Error state"
|
|
100
|
+
args={{
|
|
101
|
+
label: "I accept the terms and conditions",
|
|
102
|
+
}}
|
|
103
|
+
>
|
|
104
|
+
{ErrorTemplate.bind({})}
|
|
105
|
+
</Story>
|
|
106
|
+
</Canvas>
|
|
107
|
+
|
|
108
|
+
### Disabled state
|
|
109
|
+
|
|
110
|
+
The Wonder Blocks `Checkbox` compoenent takes a `disabled` boolean prop.
|
|
111
|
+
This sets the underlying `input` element's `disabled` prop to `true`.
|
|
112
|
+
This makes is so that the checkbox is not interactable. Also, assistive
|
|
113
|
+
tech will indicated that the checkbox is dimmed.
|
|
114
|
+
|
|
115
|
+
A user will not be able to navigate to the checkbox with a keyboard.
|
|
116
|
+
Screen reader users will be able to navigate to the checkbox with
|
|
117
|
+
screen reader controls.
|
|
118
|
+
|
|
119
|
+
It is suggested that if an element is disabled, an explanation as to why
|
|
120
|
+
should to provided somewhere.
|
|
121
|
+
|
|
122
|
+
<Canvas>
|
|
123
|
+
<Story name="Disabled state">{DisabledTemplate.bind({})}</Story>
|
|
124
|
+
</Canvas>
|
|
125
|
+
|
|
126
|
+
### Keyboard Interaction
|
|
127
|
+
|
|
128
|
+
If a checkbox is not disabled, a user can tab to it using standard
|
|
129
|
+
keyboard navigation. The Space key toggles the checked state of the checkbox.
|
|
130
|
+
|
|
131
|
+
Note the the Space key triggers the `onChange` function of the
|
|
132
|
+
Wonder Blocks Checkbox component. If the user does not specify an `onChange`
|
|
133
|
+
funciton prop that in turn updates the value of `checked`, neither clicking
|
|
134
|
+
nor the Space key will toggle the Checkbox.
|
|
135
|
+
|
|
136
|
+
### References
|
|
137
|
+
|
|
138
|
+
- [Accessible validation of checkbox and radiobutton groups](https://blog.tenon.io/accessible-validation-of-checkbox-and-radiobutton-groups/)
|
|
139
|
+
- [HTML: Validating a checkbox with HTML5](https://www.the-art-of-web.com/html/html5-checkbox-required/#example1)
|
|
140
|
+
- [aria-checked MDN Docs](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-checked)
|
|
141
|
+
- [ARIA: checkbox role MDN Docs](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/checkbox_role)
|
|
142
|
+
|
|
143
|
+
export const styles = StyleSheet.create({
|
|
144
|
+
error: {
|
|
145
|
+
color: Color.red,
|
|
146
|
+
},
|
|
147
|
+
});
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import {StyleSheet} from "aphrodite";
|
|
4
|
+
|
|
5
|
+
import {View} from "@khanacademy/wonder-blocks-core";
|
|
6
|
+
import Color from "@khanacademy/wonder-blocks-color";
|
|
7
|
+
import {Choice, CheckboxGroup} from "@khanacademy/wonder-blocks-form";
|
|
8
|
+
import Spacing from "@khanacademy/wonder-blocks-spacing";
|
|
9
|
+
import {LabelLarge, LabelXSmall} from "@khanacademy/wonder-blocks-typography";
|
|
10
|
+
|
|
11
|
+
import type {StoryComponentType} from "@storybook/react";
|
|
12
|
+
|
|
13
|
+
import ComponentInfo from "../../../../../.storybook/components/component-info.js";
|
|
14
|
+
import {name, version} from "../../../package.json";
|
|
15
|
+
|
|
16
|
+
export default {
|
|
17
|
+
title: "Form / CheckboxGroup",
|
|
18
|
+
component: CheckboxGroup,
|
|
19
|
+
parameters: {
|
|
20
|
+
componentSubtitle: ((
|
|
21
|
+
<ComponentInfo name={name} version={version} />
|
|
22
|
+
): any),
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const Default: StoryComponentType = (args) => {
|
|
27
|
+
return (
|
|
28
|
+
<CheckboxGroup {...args}>
|
|
29
|
+
<Choice label="Pepperoni" value="pepperoni" />
|
|
30
|
+
<Choice
|
|
31
|
+
label="Sausage"
|
|
32
|
+
value="sausage"
|
|
33
|
+
description="Imported from Italy"
|
|
34
|
+
/>
|
|
35
|
+
<Choice label="Extra cheese" value="cheese" />
|
|
36
|
+
<Choice label="Green pepper" value="pepper" />
|
|
37
|
+
<Choice label="Mushroom" value="mushroom" />
|
|
38
|
+
</CheckboxGroup>
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
Default.args = {
|
|
43
|
+
// Required
|
|
44
|
+
groupName: "toppings",
|
|
45
|
+
selectedValues: ["pepperoni", "sausage"],
|
|
46
|
+
onChange: () => {},
|
|
47
|
+
// Optional
|
|
48
|
+
label: "Pizza toppings",
|
|
49
|
+
description: "Choose as many toppings as you would like.",
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const Basic: StoryComponentType = () => {
|
|
53
|
+
const [selectedValues, setSelectedValues] = React.useState([]);
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<CheckboxGroup
|
|
57
|
+
groupName="toppings"
|
|
58
|
+
onChange={setSelectedValues}
|
|
59
|
+
selectedValues={selectedValues}
|
|
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
|
+
</CheckboxGroup>
|
|
71
|
+
);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
Basic.parameters = {
|
|
75
|
+
docs: {
|
|
76
|
+
storyDescription: `This is a basic example of a checkbox group.
|
|
77
|
+
The Wonder Blocks \`CheckboxGroup\` component takes \`Choice\`
|
|
78
|
+
components as children. One of the \`Choice\` components here
|
|
79
|
+
includes a description.`,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export const Error: StoryComponentType = () => {
|
|
84
|
+
const toppingsError = "You have selected too many toppings";
|
|
85
|
+
const [selectedValues, setSelectedValues] = React.useState([
|
|
86
|
+
"pepperoni",
|
|
87
|
+
"sausage",
|
|
88
|
+
"cheese",
|
|
89
|
+
"pepper",
|
|
90
|
+
]);
|
|
91
|
+
const [error, setError] = React.useState(toppingsError);
|
|
92
|
+
|
|
93
|
+
// Returns an error message if more than 3 items are selected,
|
|
94
|
+
// and it returns undefined otherwise. We use undefined instead of
|
|
95
|
+
// null here because null would result in a flow error, whereas
|
|
96
|
+
// undefined would be the same as not passing in anything to the
|
|
97
|
+
// checkbox group's `errorMessage` prop.
|
|
98
|
+
const checkForError = (input) => {
|
|
99
|
+
if (input.length > 3) {
|
|
100
|
+
return toppingsError;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const handleChange = (input) => {
|
|
105
|
+
const errorMessage = checkForError(input);
|
|
106
|
+
setSelectedValues(input);
|
|
107
|
+
setError(errorMessage);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<CheckboxGroup
|
|
112
|
+
label="Pizza order"
|
|
113
|
+
groupName="toppings"
|
|
114
|
+
description="You may choose at most three toppings"
|
|
115
|
+
onChange={handleChange}
|
|
116
|
+
errorMessage={error}
|
|
117
|
+
selectedValues={selectedValues}
|
|
118
|
+
>
|
|
119
|
+
<Choice label="Pepperoni" value="pepperoni" />
|
|
120
|
+
<Choice
|
|
121
|
+
label="Sausage"
|
|
122
|
+
value="sausage"
|
|
123
|
+
description="Imported from Italy"
|
|
124
|
+
/>
|
|
125
|
+
<Choice label="Extra cheese" value="cheese" />
|
|
126
|
+
<Choice label="Green pepper" value="pepper" />
|
|
127
|
+
<Choice label="Mushroom" value="mushroom" />
|
|
128
|
+
</CheckboxGroup>
|
|
129
|
+
);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
Error.parameters = {
|
|
133
|
+
docs: {
|
|
134
|
+
storyDescription: `This is what a checkbox group looks like
|
|
135
|
+
if it has an error. It displays the error that is passed into the
|
|
136
|
+
\`errorMessage\` prop, provided the error is not null. It also
|
|
137
|
+
uses the error styling for all the checkboxes. Here, the error
|
|
138
|
+
message is saved as a state, updated in the change handler, and then
|
|
139
|
+
passed in as the \`errorMessage\` prop.`,
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export const RowStyling: StoryComponentType = () => {
|
|
144
|
+
const [selectedValues, setSelectedValues] = React.useState([]);
|
|
145
|
+
|
|
146
|
+
return (
|
|
147
|
+
<View style={styles.wrapper}>
|
|
148
|
+
<LabelLarge style={styles.title}>Science</LabelLarge>
|
|
149
|
+
<CheckboxGroup
|
|
150
|
+
groupName="science-classes"
|
|
151
|
+
onChange={setSelectedValues}
|
|
152
|
+
selectedValues={selectedValues}
|
|
153
|
+
style={styles.group}
|
|
154
|
+
>
|
|
155
|
+
<Choice label="Biology" value="1" style={styles.choice} />
|
|
156
|
+
<Choice label="AP®︎ Biology" value="2" style={styles.choice} />
|
|
157
|
+
<Choice
|
|
158
|
+
label="High school biology"
|
|
159
|
+
value="3"
|
|
160
|
+
style={styles.choice}
|
|
161
|
+
/>
|
|
162
|
+
<Choice
|
|
163
|
+
label="Cosmology and astronomy"
|
|
164
|
+
value="4"
|
|
165
|
+
style={styles.choice}
|
|
166
|
+
/>
|
|
167
|
+
<Choice
|
|
168
|
+
label="Electrical engineering"
|
|
169
|
+
value="5"
|
|
170
|
+
style={styles.choice}
|
|
171
|
+
/>
|
|
172
|
+
<Choice
|
|
173
|
+
label="Health and medicine"
|
|
174
|
+
value="6"
|
|
175
|
+
style={styles.choice}
|
|
176
|
+
/>
|
|
177
|
+
</CheckboxGroup>
|
|
178
|
+
</View>
|
|
179
|
+
);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
RowStyling.parameters = {
|
|
183
|
+
docs: {
|
|
184
|
+
storyDescription: `This example shows how one can add custom styles
|
|
185
|
+
to the checkbox group and to each component to achieve desired custom
|
|
186
|
+
layouts. The context in this example is inspired by the class selector
|
|
187
|
+
modal. The label is created separately because we are reflowing all
|
|
188
|
+
the elements in the group to row. The checkboxes render
|
|
189
|
+
horizontally when the style on the \`CheckboxGroup\` is set to
|
|
190
|
+
\`{flexDirection: "row"}\`. Here, \`{flexWrap: "wrap"}\` is also
|
|
191
|
+
used so the options continue on the next line.`,
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
export const MultipleChoiceStyling: StoryComponentType = () => {
|
|
196
|
+
const [selectedValues, setSelectedValues] = React.useState([]);
|
|
197
|
+
|
|
198
|
+
return (
|
|
199
|
+
<CheckboxGroup
|
|
200
|
+
label={<LabelLarge>Select all prime numbers</LabelLarge>}
|
|
201
|
+
description={
|
|
202
|
+
<LabelXSmall style={styles.description}>
|
|
203
|
+
Hint: There is at least one prime number
|
|
204
|
+
</LabelXSmall>
|
|
205
|
+
}
|
|
206
|
+
groupName="science-classes"
|
|
207
|
+
onChange={setSelectedValues}
|
|
208
|
+
selectedValues={selectedValues}
|
|
209
|
+
>
|
|
210
|
+
<Choice label="1" value="1" style={styles.multipleChoice} />
|
|
211
|
+
<Choice label="2" value="2" style={styles.multipleChoice} />
|
|
212
|
+
<Choice label="3" value="3" style={styles.multipleChoice} />
|
|
213
|
+
<Choice label="4" value="4" style={styles.multipleChoice} />
|
|
214
|
+
<Choice
|
|
215
|
+
label="5"
|
|
216
|
+
value="5"
|
|
217
|
+
style={[styles.multipleChoice, styles.last]}
|
|
218
|
+
/>
|
|
219
|
+
</CheckboxGroup>
|
|
220
|
+
);
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
MultipleChoiceStyling.parameters = {
|
|
224
|
+
docs: {
|
|
225
|
+
storyDescription: `This example shows how to use custom styling
|
|
226
|
+
to change the appearance of the checkbox group to look more like
|
|
227
|
+
a multiple choice question. You may also provide custom typography
|
|
228
|
+
to the label and description. Here, there is a line in
|
|
229
|
+
between each question, which is achieved using the
|
|
230
|
+
\`{borderTop: "solid 1px #CCC"}\` style on each \`Choice\`
|
|
231
|
+
component.`,
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const styles = StyleSheet.create({
|
|
236
|
+
// Row styling
|
|
237
|
+
wrapper: {
|
|
238
|
+
width: 650,
|
|
239
|
+
},
|
|
240
|
+
group: {
|
|
241
|
+
flexDirection: "row",
|
|
242
|
+
flexWrap: "wrap",
|
|
243
|
+
},
|
|
244
|
+
choice: {
|
|
245
|
+
marginTop: Spacing.xSmall_8,
|
|
246
|
+
width: 200,
|
|
247
|
+
},
|
|
248
|
+
title: {
|
|
249
|
+
paddingBottom: Spacing.xSmall_8,
|
|
250
|
+
borderBottom: `1px solid ${Color.offBlack64}`,
|
|
251
|
+
},
|
|
252
|
+
// Multiple choice styling
|
|
253
|
+
multipleChoice: {
|
|
254
|
+
margin: 0,
|
|
255
|
+
height: 48,
|
|
256
|
+
borderTop: "solid 1px #CCC",
|
|
257
|
+
justifyContent: "center",
|
|
258
|
+
},
|
|
259
|
+
description: {
|
|
260
|
+
marginTop: 5,
|
|
261
|
+
color: Color.offBlack64,
|
|
262
|
+
},
|
|
263
|
+
last: {
|
|
264
|
+
borderBottom: "solid 1px #CCC",
|
|
265
|
+
},
|
|
266
|
+
});
|