@khanacademy/wonder-blocks-form 4.2.3 → 4.3.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.
- package/CHANGELOG.md +36 -0
- package/dist/components/checkbox-core.d.ts +13 -8
- package/dist/components/checkbox-core.js.flow +19 -10
- package/dist/components/checkbox-group.d.ts +2 -5
- package/dist/components/checkbox-group.js.flow +5 -6
- package/dist/components/checkbox.d.ts +33 -39
- package/dist/components/checkbox.js.flow +38 -41
- package/dist/components/choice-internal.d.ts +19 -31
- package/dist/components/choice-internal.js.flow +25 -32
- package/dist/components/choice.d.ts +50 -60
- package/dist/components/choice.js.flow +79 -84
- package/dist/components/radio-core.d.ts +13 -5
- package/dist/components/radio-core.js.flow +19 -7
- package/dist/components/radio-group.d.ts +2 -5
- package/dist/components/radio-group.js.flow +5 -6
- package/dist/components/radio.d.ts +18 -24
- package/dist/components/radio.js.flow +24 -27
- package/dist/es/index.js +262 -294
- package/dist/index.js +262 -294
- package/dist/util/types.d.ts +1 -1
- package/dist/util/types.js.flow +1 -1
- package/package.json +6 -6
- package/src/components/__tests__/{checkbox.test.js → checkbox.test.tsx} +55 -1
- package/src/components/checkbox-core.tsx +66 -71
- package/src/components/checkbox-group.tsx +62 -59
- package/src/components/checkbox.tsx +19 -16
- package/src/components/choice-internal.tsx +80 -77
- package/src/components/choice.tsx +34 -26
- package/src/components/radio-core.tsx +40 -44
- package/src/components/radio-group.tsx +57 -59
- package/src/components/radio.tsx +19 -16
- package/src/util/types.ts +1 -1
- package/tsconfig-build.tsbuildinfo +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@khanacademy/wonder-blocks-form",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.3.1",
|
|
4
4
|
"design": "v1",
|
|
5
5
|
"description": "Form components for Wonder Blocks.",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -16,13 +16,13 @@
|
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@babel/runtime": "^7.18.6",
|
|
19
|
-
"@khanacademy/wonder-blocks-clickable": "^3.1.
|
|
19
|
+
"@khanacademy/wonder-blocks-clickable": "^3.1.2",
|
|
20
20
|
"@khanacademy/wonder-blocks-color": "^2.0.1",
|
|
21
|
-
"@khanacademy/wonder-blocks-core": "^5.
|
|
22
|
-
"@khanacademy/wonder-blocks-icon": "^2.0.
|
|
23
|
-
"@khanacademy/wonder-blocks-layout": "^2.0.
|
|
21
|
+
"@khanacademy/wonder-blocks-core": "^5.3.1",
|
|
22
|
+
"@khanacademy/wonder-blocks-icon": "^2.0.15",
|
|
23
|
+
"@khanacademy/wonder-blocks-layout": "^2.0.15",
|
|
24
24
|
"@khanacademy/wonder-blocks-spacing": "^4.0.1",
|
|
25
|
-
"@khanacademy/wonder-blocks-typography": "^2.
|
|
25
|
+
"@khanacademy/wonder-blocks-typography": "^2.1.1"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
28
|
"aphrodite": "^1.2.5",
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
//@flow
|
|
2
1
|
import * as React from "react";
|
|
3
2
|
import {render, screen} from "@testing-library/react";
|
|
4
3
|
|
|
@@ -81,4 +80,59 @@ describe("Checkbox", () => {
|
|
|
81
80
|
// Assert
|
|
82
81
|
expect(onChangeSpy).toHaveBeenCalled();
|
|
83
82
|
});
|
|
83
|
+
|
|
84
|
+
test.each`
|
|
85
|
+
indeterminateValue | checkedValue
|
|
86
|
+
${true} | ${null}
|
|
87
|
+
${false} | ${true}
|
|
88
|
+
${false} | ${false}
|
|
89
|
+
`(
|
|
90
|
+
"sets the indeterminate property to $indeterminateValue when checked is $checkedValue (with ref)",
|
|
91
|
+
({indeterminateValue, checkedValue}) => {
|
|
92
|
+
// Arrange
|
|
93
|
+
const ref = React.createRef<HTMLInputElement>();
|
|
94
|
+
|
|
95
|
+
// Act
|
|
96
|
+
render(
|
|
97
|
+
<Checkbox
|
|
98
|
+
label="Some label"
|
|
99
|
+
description="Some description"
|
|
100
|
+
checked={checkedValue}
|
|
101
|
+
onChange={() => {}}
|
|
102
|
+
ref={ref}
|
|
103
|
+
/>,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
// Assert
|
|
107
|
+
expect(ref?.current?.indeterminate).toBe(indeterminateValue);
|
|
108
|
+
},
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
test.each`
|
|
112
|
+
indeterminateValue | checkedValue
|
|
113
|
+
${true} | ${null}
|
|
114
|
+
${false} | ${true}
|
|
115
|
+
${false} | ${false}
|
|
116
|
+
`(
|
|
117
|
+
"sets the indeterminate property to $indeterminateValue when checked is $checkedValue (innerRef)",
|
|
118
|
+
({indeterminateValue, checkedValue}) => {
|
|
119
|
+
// Arrange
|
|
120
|
+
render(
|
|
121
|
+
<Checkbox
|
|
122
|
+
label="Some label"
|
|
123
|
+
description="Some description"
|
|
124
|
+
checked={checkedValue}
|
|
125
|
+
onChange={() => {}}
|
|
126
|
+
/>,
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
// Act
|
|
130
|
+
const inputElement = screen.getByRole(
|
|
131
|
+
"checkbox",
|
|
132
|
+
) as HTMLInputElement;
|
|
133
|
+
|
|
134
|
+
// Assert
|
|
135
|
+
expect(inputElement.indeterminate).toBe(indeterminateValue);
|
|
136
|
+
},
|
|
137
|
+
);
|
|
84
138
|
});
|
|
@@ -39,84 +39,77 @@ const indeterminatePath: IconAsset = {
|
|
|
39
39
|
/**
|
|
40
40
|
* The internal stateless ☑️ Checkbox
|
|
41
41
|
*/
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
const CheckboxCore = React.forwardRef(function CheckboxCore(
|
|
43
|
+
props: ChoiceCoreProps,
|
|
44
|
+
ref: React.ForwardedRef<HTMLInputElement>,
|
|
45
|
+
) {
|
|
46
|
+
const {checked, disabled, error, groupName, id, testId, ...sharedProps} =
|
|
47
|
+
props;
|
|
48
|
+
|
|
49
|
+
const innerRef = React.useRef<HTMLInputElement>(null);
|
|
50
|
+
|
|
51
|
+
React.useEffect(() => {
|
|
52
|
+
// Keep the indeterminate state in sync with the checked prop
|
|
53
|
+
if (innerRef.current != null) {
|
|
54
|
+
innerRef.current.indeterminate = checked == null;
|
|
46
55
|
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
componentDidUpdate(prevProps: Readonly<ChoiceCoreProps>): void {
|
|
50
|
-
if (this.inputRef.current != null) {
|
|
51
|
-
this.inputRef.current.indeterminate = this.props.checked == null;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
inputRef: React.RefObject<HTMLInputElement> = React.createRef();
|
|
56
|
+
}, [checked, innerRef]);
|
|
56
57
|
|
|
57
|
-
handleChange: () => void = () => {
|
|
58
|
+
const handleChange: () => void = () => {
|
|
58
59
|
// Empty because change is handled by ClickableBehavior
|
|
59
60
|
return;
|
|
60
61
|
};
|
|
61
62
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
sharedStyles.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
63
|
+
const stateStyles = _generateStyles(checked, error);
|
|
64
|
+
|
|
65
|
+
const defaultStyle = [
|
|
66
|
+
sharedStyles.inputReset,
|
|
67
|
+
sharedStyles.default,
|
|
68
|
+
!disabled && stateStyles.default,
|
|
69
|
+
disabled && sharedStyles.disabled,
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
const checkboxIcon = (
|
|
73
|
+
<Icon
|
|
74
|
+
color={disabled ? offBlack32 : white}
|
|
75
|
+
icon={checked ? checkPath : indeterminatePath}
|
|
76
|
+
size="small"
|
|
77
|
+
style={sharedStyles.checkboxIcon}
|
|
78
|
+
/>
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const ariaChecked = mapCheckedToAriaChecked(checked);
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<React.Fragment>
|
|
85
|
+
<StyledInput
|
|
86
|
+
{...sharedProps}
|
|
87
|
+
ref={(node) => {
|
|
88
|
+
// @ts-expect-error: current is not actually read-only
|
|
89
|
+
innerRef.current = node;
|
|
90
|
+
if (typeof ref === "function") {
|
|
91
|
+
ref(node);
|
|
92
|
+
} else if (ref != null) {
|
|
93
|
+
ref.current = node;
|
|
94
|
+
}
|
|
95
|
+
}}
|
|
96
|
+
type="checkbox"
|
|
97
|
+
aria-checked={ariaChecked}
|
|
98
|
+
aria-invalid={error}
|
|
99
|
+
checked={checked ?? undefined}
|
|
100
|
+
disabled={disabled}
|
|
101
|
+
id={id}
|
|
102
|
+
name={groupName}
|
|
103
|
+
// Need to specify because this is a controlled React form
|
|
104
|
+
// component, but we handle the click via ClickableBehavior
|
|
105
|
+
onChange={handleChange}
|
|
106
|
+
style={defaultStyle}
|
|
107
|
+
data-test-id={testId}
|
|
92
108
|
/>
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
return (
|
|
98
|
-
<React.Fragment>
|
|
99
|
-
<StyledInput
|
|
100
|
-
{...sharedProps}
|
|
101
|
-
ref={this.inputRef}
|
|
102
|
-
type="checkbox"
|
|
103
|
-
aria-checked={ariaChecked}
|
|
104
|
-
aria-invalid={error}
|
|
105
|
-
checked={checked ?? undefined}
|
|
106
|
-
disabled={disabled}
|
|
107
|
-
id={id}
|
|
108
|
-
name={groupName}
|
|
109
|
-
// Need to specify because this is a controlled React form
|
|
110
|
-
// component, but we handle the click via ClickableBehavior
|
|
111
|
-
onChange={this.handleChange}
|
|
112
|
-
style={defaultStyle}
|
|
113
|
-
{...props}
|
|
114
|
-
/>
|
|
115
|
-
{checked || checked == null ? checkboxIcon : <></>}
|
|
116
|
-
</React.Fragment>
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
109
|
+
{checked || checked == null ? checkboxIcon : <></>}
|
|
110
|
+
</React.Fragment>
|
|
111
|
+
);
|
|
112
|
+
});
|
|
120
113
|
|
|
121
114
|
const size = 16;
|
|
122
115
|
|
|
@@ -237,3 +230,5 @@ const _generateStyles = (checked: Checked, error: boolean) => {
|
|
|
237
230
|
styles[styleKey] = StyleSheet.create(newStyles);
|
|
238
231
|
return styles[styleKey];
|
|
239
232
|
};
|
|
233
|
+
|
|
234
|
+
export default CheckboxCore;
|
|
@@ -95,10 +95,26 @@ const StyledLegend = addStyle("legend");
|
|
|
95
95
|
* </CheckboxGroup>
|
|
96
96
|
* ```
|
|
97
97
|
*/
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
const CheckboxGroup = React.forwardRef(function CheckboxGroup(
|
|
99
|
+
props: CheckboxGroupProps,
|
|
100
|
+
ref: React.ForwardedRef<HTMLFieldSetElement>,
|
|
101
|
+
) {
|
|
102
|
+
const {
|
|
103
|
+
children,
|
|
104
|
+
label,
|
|
105
|
+
description,
|
|
106
|
+
errorMessage,
|
|
107
|
+
groupName,
|
|
108
|
+
onChange,
|
|
109
|
+
selectedValues,
|
|
110
|
+
style,
|
|
111
|
+
testId,
|
|
112
|
+
} = props;
|
|
101
113
|
|
|
114
|
+
const handleChange = (
|
|
115
|
+
changedValue: string,
|
|
116
|
+
originalCheckedState: boolean,
|
|
117
|
+
) => {
|
|
102
118
|
if (originalCheckedState) {
|
|
103
119
|
const index = selectedValues.indexOf(changedValue);
|
|
104
120
|
const updatedSelection = [
|
|
@@ -109,63 +125,50 @@ export default class CheckboxGroup extends React.Component<CheckboxGroupProps> {
|
|
|
109
125
|
} else {
|
|
110
126
|
onChange([...selectedValues, changedValue]);
|
|
111
127
|
}
|
|
112
|
-
}
|
|
128
|
+
};
|
|
113
129
|
|
|
114
|
-
|
|
115
|
-
const {
|
|
116
|
-
children,
|
|
117
|
-
label,
|
|
118
|
-
description,
|
|
119
|
-
errorMessage,
|
|
120
|
-
groupName,
|
|
121
|
-
selectedValues,
|
|
122
|
-
style,
|
|
123
|
-
testId,
|
|
124
|
-
} = this.props;
|
|
130
|
+
const allChildren = React.Children.toArray(children).filter(Boolean);
|
|
125
131
|
|
|
126
|
-
|
|
132
|
+
return (
|
|
133
|
+
<StyledFieldset data-test-id={testId} style={styles.fieldset} ref={ref}>
|
|
134
|
+
{/* We have a View here because fieldset cannot be used with flexbox*/}
|
|
135
|
+
<View style={style}>
|
|
136
|
+
{label && (
|
|
137
|
+
<StyledLegend style={styles.legend}>
|
|
138
|
+
<LabelMedium>{label}</LabelMedium>
|
|
139
|
+
</StyledLegend>
|
|
140
|
+
)}
|
|
141
|
+
{description && (
|
|
142
|
+
<LabelSmall style={styles.description}>
|
|
143
|
+
{description}
|
|
144
|
+
</LabelSmall>
|
|
145
|
+
)}
|
|
146
|
+
{errorMessage && (
|
|
147
|
+
<LabelSmall style={styles.error}>{errorMessage}</LabelSmall>
|
|
148
|
+
)}
|
|
149
|
+
{(label || description || errorMessage) && (
|
|
150
|
+
<Strut size={Spacing.small_12} />
|
|
151
|
+
)}
|
|
127
152
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
<Strut size={Spacing.small_12} />
|
|
149
|
-
)}
|
|
153
|
+
{allChildren.map((child, index) => {
|
|
154
|
+
// @ts-expect-error [FEI-5019] - TS2339 - Property 'props' does not exist on type 'ReactChild | ReactFragment | ReactPortal'.
|
|
155
|
+
const {style, value} = child.props;
|
|
156
|
+
const checked = selectedValues.includes(value);
|
|
157
|
+
// @ts-expect-error [FEI-5019] - TS2769 - No overload matches this call.
|
|
158
|
+
return React.cloneElement(child, {
|
|
159
|
+
checked: checked,
|
|
160
|
+
error: !!errorMessage,
|
|
161
|
+
groupName: groupName,
|
|
162
|
+
id: `${groupName}-${value}`,
|
|
163
|
+
key: value,
|
|
164
|
+
onChange: () => handleChange(value, checked),
|
|
165
|
+
style: [index > 0 && styles.defaultLineGap, style],
|
|
166
|
+
variant: "checkbox",
|
|
167
|
+
});
|
|
168
|
+
})}
|
|
169
|
+
</View>
|
|
170
|
+
</StyledFieldset>
|
|
171
|
+
);
|
|
172
|
+
});
|
|
150
173
|
|
|
151
|
-
|
|
152
|
-
// @ts-expect-error [FEI-5019] - TS2339 - Property 'props' does not exist on type 'ReactChild | ReactFragment | ReactPortal'.
|
|
153
|
-
const {style, value} = child.props;
|
|
154
|
-
const checked = selectedValues.includes(value);
|
|
155
|
-
// @ts-expect-error [FEI-5019] - TS2769 - No overload matches this call.
|
|
156
|
-
return React.cloneElement(child, {
|
|
157
|
-
checked: checked,
|
|
158
|
-
error: !!errorMessage,
|
|
159
|
-
groupName: groupName,
|
|
160
|
-
id: `${groupName}-${value}`,
|
|
161
|
-
key: value,
|
|
162
|
-
onChange: () => this.handleChange(value, checked),
|
|
163
|
-
style: [index > 0 && styles.defaultLineGap, style],
|
|
164
|
-
variant: "checkbox",
|
|
165
|
-
});
|
|
166
|
-
})}
|
|
167
|
-
</View>
|
|
168
|
-
</StyledFieldset>
|
|
169
|
-
);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
174
|
+
export default CheckboxGroup;
|
|
@@ -14,11 +14,11 @@ type ChoiceComponentProps = AriaProps & {
|
|
|
14
14
|
/**
|
|
15
15
|
* Whether this component is disabled
|
|
16
16
|
*/
|
|
17
|
-
disabled
|
|
17
|
+
disabled?: boolean;
|
|
18
18
|
/**
|
|
19
19
|
* Whether this component should show an error state
|
|
20
20
|
*/
|
|
21
|
-
error
|
|
21
|
+
error?: boolean;
|
|
22
22
|
/**
|
|
23
23
|
* Callback when this component is selected. The newCheckedState is the
|
|
24
24
|
* new checked state of the component.
|
|
@@ -58,11 +58,6 @@ type ChoiceComponentProps = AriaProps & {
|
|
|
58
58
|
groupName?: string;
|
|
59
59
|
};
|
|
60
60
|
|
|
61
|
-
type DefaultProps = {
|
|
62
|
-
disabled: ChoiceComponentProps["disabled"];
|
|
63
|
-
error: ChoiceComponentProps["error"];
|
|
64
|
-
};
|
|
65
|
-
|
|
66
61
|
/**
|
|
67
62
|
* ☑️ A nicely styled checkbox for all your checking needs. Can optionally take
|
|
68
63
|
* label and description props.
|
|
@@ -84,13 +79,21 @@ type DefaultProps = {
|
|
|
84
79
|
* <Checkbox checked={checked} onChange={setChecked} />
|
|
85
80
|
* ```
|
|
86
81
|
*/
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
};
|
|
82
|
+
const Checkbox = React.forwardRef(function Checkbox(
|
|
83
|
+
props: ChoiceComponentProps,
|
|
84
|
+
ref: React.ForwardedRef<HTMLInputElement>,
|
|
85
|
+
) {
|
|
86
|
+
const {disabled = false, error = false} = props;
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<ChoiceInternal
|
|
90
|
+
{...props}
|
|
91
|
+
variant="checkbox"
|
|
92
|
+
disabled={disabled}
|
|
93
|
+
error={error}
|
|
94
|
+
ref={ref}
|
|
95
|
+
/>
|
|
96
|
+
);
|
|
97
|
+
});
|
|
92
98
|
|
|
93
|
-
|
|
94
|
-
return <ChoiceInternal variant="checkbox" {...this.props} />;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
99
|
+
export default Checkbox;
|
|
@@ -14,9 +14,9 @@ type Props = AriaProps & {
|
|
|
14
14
|
/** Whether this choice is checked. */
|
|
15
15
|
checked: boolean | null | undefined;
|
|
16
16
|
/** Whether this choice option is disabled. */
|
|
17
|
-
disabled
|
|
17
|
+
disabled?: boolean;
|
|
18
18
|
/** Whether this choice is in error mode. */
|
|
19
|
-
error
|
|
19
|
+
error?: boolean;
|
|
20
20
|
/** Returns the new checked state of the component. */
|
|
21
21
|
onChange: (newCheckedState: boolean) => unknown;
|
|
22
22
|
/**
|
|
@@ -48,11 +48,6 @@ type Props = AriaProps & {
|
|
|
48
48
|
variant: "radio" | "checkbox";
|
|
49
49
|
};
|
|
50
50
|
|
|
51
|
-
type DefaultProps = {
|
|
52
|
-
disabled: Props["disabled"];
|
|
53
|
-
error: Props["error"];
|
|
54
|
-
};
|
|
55
|
-
|
|
56
51
|
/**
|
|
57
52
|
* This is a potentially labeled 🔘 or ☑️ item. This is an internal component
|
|
58
53
|
* that's wrapped by Checkbox and Radio. Choice is a wrapper for Checkbox and
|
|
@@ -60,14 +55,27 @@ type DefaultProps = {
|
|
|
60
55
|
* and RadioGroup. This design allows for more explicit prop typing. For
|
|
61
56
|
* example, we can make onChange a required prop on Checkbox but not on Choice
|
|
62
57
|
* (because for Choice, that prop would be auto-populated by CheckboxGroup).
|
|
63
|
-
*/
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
58
|
+
*/ const ChoiceInternal = React.forwardRef(function ChoiceInternal(
|
|
59
|
+
props: Props,
|
|
60
|
+
ref: React.ForwardedRef<HTMLInputElement>,
|
|
61
|
+
) {
|
|
62
|
+
const {
|
|
63
|
+
checked,
|
|
64
|
+
description,
|
|
65
|
+
disabled = false,
|
|
66
|
+
error = false,
|
|
67
|
+
id,
|
|
68
|
+
label,
|
|
69
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
70
|
+
onChange,
|
|
71
|
+
style,
|
|
72
|
+
className,
|
|
73
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
74
|
+
variant,
|
|
75
|
+
// ...coreProps
|
|
76
|
+
} = props;
|
|
68
77
|
|
|
69
|
-
handleClick: () => void = () => {
|
|
70
|
-
const {checked, onChange, variant} = this.props;
|
|
78
|
+
const handleClick: () => void = () => {
|
|
71
79
|
// Radio buttons cannot be unchecked
|
|
72
80
|
if (variant === "radio" && checked) {
|
|
73
81
|
return;
|
|
@@ -75,15 +83,17 @@ type DefaultProps = {
|
|
|
75
83
|
onChange(!checked);
|
|
76
84
|
};
|
|
77
85
|
|
|
78
|
-
getChoiceCoreComponent():
|
|
79
|
-
|
|
86
|
+
const getChoiceCoreComponent = ():
|
|
87
|
+
| typeof RadioCore
|
|
88
|
+
| typeof CheckboxCore => {
|
|
89
|
+
if (variant === "radio") {
|
|
80
90
|
return RadioCore;
|
|
81
91
|
} else {
|
|
82
92
|
return CheckboxCore;
|
|
83
93
|
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const getLabel = (id: string): React.ReactNode => {
|
|
87
97
|
return (
|
|
88
98
|
<LabelMedium
|
|
89
99
|
style={[styles.label, disabled && styles.disabledLabel]}
|
|
@@ -91,73 +101,64 @@ type DefaultProps = {
|
|
|
91
101
|
<label htmlFor={id}>{label}</label>
|
|
92
102
|
</LabelMedium>
|
|
93
103
|
);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const getDescription = (id?: string): React.ReactNode => {
|
|
97
107
|
return (
|
|
98
108
|
<LabelSmall style={styles.description} id={id}>
|
|
99
109
|
{description}
|
|
100
110
|
</LabelSmall>
|
|
101
111
|
);
|
|
102
|
-
}
|
|
103
|
-
render(): React.ReactNode {
|
|
104
|
-
const {
|
|
105
|
-
label,
|
|
106
|
-
description,
|
|
107
|
-
id,
|
|
108
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
109
|
-
onChange,
|
|
110
|
-
style,
|
|
111
|
-
className,
|
|
112
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
113
|
-
variant,
|
|
114
|
-
...coreProps
|
|
115
|
-
} = this.props;
|
|
116
|
-
const ChoiceCore = this.getChoiceCoreComponent();
|
|
112
|
+
};
|
|
117
113
|
|
|
118
|
-
|
|
119
|
-
<UniqueIDProvider mockOnFirstRender={true} scope="choice">
|
|
120
|
-
{(ids) => {
|
|
121
|
-
// A choice element should always have a unique ID set
|
|
122
|
-
// so that the label can always refer to this element.
|
|
123
|
-
// This guarantees that clicking on the label will
|
|
124
|
-
// always click on the choice as well. If an ID is
|
|
125
|
-
// passed in as a prop, use that one. Otherwise,
|
|
126
|
-
// create a unique ID using the provider.
|
|
127
|
-
const uniqueId = id || ids.get("main");
|
|
114
|
+
const ChoiceCore = getChoiceCoreComponent();
|
|
128
115
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
116
|
+
return (
|
|
117
|
+
<UniqueIDProvider mockOnFirstRender={true} scope="choice">
|
|
118
|
+
{(ids) => {
|
|
119
|
+
// A choice element should always have a unique ID set
|
|
120
|
+
// so that the label can always refer to this element.
|
|
121
|
+
// This guarantees that clicking on the label will
|
|
122
|
+
// always click on the choice as well. If an ID is
|
|
123
|
+
// passed in as a prop, use that one. Otherwise,
|
|
124
|
+
// create a unique ID using the provider.
|
|
125
|
+
const uniqueId = id || ids.get("main");
|
|
134
126
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
{
|
|
152
|
-
|
|
153
|
-
|
|
127
|
+
// Create a unique ID for the description section to be
|
|
128
|
+
// used by this element's `aria-describedby`.
|
|
129
|
+
const descriptionId = description
|
|
130
|
+
? ids.get("description")
|
|
131
|
+
: undefined;
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<View style={style} className={className}>
|
|
135
|
+
<View
|
|
136
|
+
style={styles.wrapper}
|
|
137
|
+
// We are resetting the tabIndex=0 from handlers
|
|
138
|
+
// because the ChoiceCore component will receive
|
|
139
|
+
// focus on basis of it being an input element.
|
|
140
|
+
tabIndex={-1}
|
|
141
|
+
>
|
|
142
|
+
<ChoiceCore
|
|
143
|
+
{...props}
|
|
144
|
+
id={uniqueId}
|
|
145
|
+
aria-describedby={descriptionId}
|
|
146
|
+
onClick={handleClick}
|
|
147
|
+
disabled={disabled}
|
|
148
|
+
error={error}
|
|
149
|
+
ref={ref}
|
|
150
|
+
/>
|
|
151
|
+
<Strut size={Spacing.xSmall_8} />
|
|
152
|
+
{label && getLabel(uniqueId)}
|
|
154
153
|
</View>
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
154
|
+
{description && getDescription(descriptionId)}
|
|
155
|
+
</View>
|
|
156
|
+
);
|
|
157
|
+
}}
|
|
158
|
+
</UniqueIDProvider>
|
|
159
|
+
);
|
|
160
|
+
});
|
|
161
|
+
|
|
161
162
|
const styles = StyleSheet.create({
|
|
162
163
|
wrapper: {
|
|
163
164
|
flexDirection: "row",
|
|
@@ -181,3 +182,5 @@ const styles = StyleSheet.create({
|
|
|
181
182
|
color: Color.offBlack64,
|
|
182
183
|
},
|
|
183
184
|
});
|
|
185
|
+
|
|
186
|
+
export default ChoiceInternal;
|