@khanacademy/wonder-blocks-form 4.2.2 → 4.3.0
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 +31 -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 +32 -31
- package/src/components/checkbox-group.tsx +33 -22
- package/src/components/checkbox.tsx +21 -16
- package/src/components/choice-internal.tsx +60 -58
- package/src/components/choice.tsx +39 -32
- package/src/components/radio-core.tsx +16 -14
- package/src/components/radio-group.tsx +14 -11
- package/src/components/radio.tsx +21 -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.0",
|
|
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.
|
|
19
|
+
"@khanacademy/wonder-blocks-clickable": "^3.1.1",
|
|
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.0",
|
|
22
|
+
"@khanacademy/wonder-blocks-icon": "^2.0.14",
|
|
23
|
+
"@khanacademy/wonder-blocks-layout": "^2.0.14",
|
|
24
24
|
"@khanacademy/wonder-blocks-spacing": "^4.0.1",
|
|
25
|
-
"@khanacademy/wonder-blocks-typography": "^2.0
|
|
25
|
+
"@khanacademy/wonder-blocks-typography": "^2.1.0"
|
|
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,27 +39,8 @@ const indeterminatePath: IconAsset = {
|
|
|
39
39
|
/**
|
|
40
40
|
* The internal stateless ☑️ Checkbox
|
|
41
41
|
*/
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (this.props.checked == null && this.inputRef.current != null) {
|
|
45
|
-
this.inputRef.current.indeterminate = true;
|
|
46
|
-
}
|
|
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
|
-
|
|
57
|
-
handleChange: () => void = () => {
|
|
58
|
-
// Empty because change is handled by ClickableBehavior
|
|
59
|
-
return;
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
render(): React.ReactNode {
|
|
42
|
+
const CheckboxCore = React.forwardRef(
|
|
43
|
+
(props: ChoiceCoreProps, ref: React.ForwardedRef<HTMLInputElement>) => {
|
|
63
44
|
const {
|
|
64
45
|
checked,
|
|
65
46
|
disabled,
|
|
@@ -68,7 +49,21 @@ export default class CheckboxCore extends React.Component<ChoiceCoreProps> {
|
|
|
68
49
|
id,
|
|
69
50
|
testId,
|
|
70
51
|
...sharedProps
|
|
71
|
-
} =
|
|
52
|
+
} = props;
|
|
53
|
+
|
|
54
|
+
const innerRef = React.useRef<HTMLInputElement>(null);
|
|
55
|
+
|
|
56
|
+
React.useEffect(() => {
|
|
57
|
+
// Keep the indeterminate state in sync with the checked prop
|
|
58
|
+
if (innerRef.current != null) {
|
|
59
|
+
innerRef.current.indeterminate = checked == null;
|
|
60
|
+
}
|
|
61
|
+
}, [checked, innerRef]);
|
|
62
|
+
|
|
63
|
+
const handleChange: () => void = () => {
|
|
64
|
+
// Empty because change is handled by ClickableBehavior
|
|
65
|
+
return;
|
|
66
|
+
};
|
|
72
67
|
|
|
73
68
|
const stateStyles = _generateStyles(checked, error);
|
|
74
69
|
|
|
@@ -79,10 +74,6 @@ export default class CheckboxCore extends React.Component<ChoiceCoreProps> {
|
|
|
79
74
|
disabled && sharedStyles.disabled,
|
|
80
75
|
];
|
|
81
76
|
|
|
82
|
-
const props = {
|
|
83
|
-
"data-test-id": testId,
|
|
84
|
-
} as const;
|
|
85
|
-
|
|
86
77
|
const checkboxIcon = (
|
|
87
78
|
<Icon
|
|
88
79
|
color={disabled ? offBlack32 : white}
|
|
@@ -98,7 +89,15 @@ export default class CheckboxCore extends React.Component<ChoiceCoreProps> {
|
|
|
98
89
|
<React.Fragment>
|
|
99
90
|
<StyledInput
|
|
100
91
|
{...sharedProps}
|
|
101
|
-
ref={
|
|
92
|
+
ref={(node) => {
|
|
93
|
+
// @ts-expect-error: current is not actually read-only
|
|
94
|
+
innerRef.current = node;
|
|
95
|
+
if (typeof ref === "function") {
|
|
96
|
+
ref(node);
|
|
97
|
+
} else if (ref != null) {
|
|
98
|
+
ref.current = node;
|
|
99
|
+
}
|
|
100
|
+
}}
|
|
102
101
|
type="checkbox"
|
|
103
102
|
aria-checked={ariaChecked}
|
|
104
103
|
aria-invalid={error}
|
|
@@ -108,15 +107,15 @@ export default class CheckboxCore extends React.Component<ChoiceCoreProps> {
|
|
|
108
107
|
name={groupName}
|
|
109
108
|
// Need to specify because this is a controlled React form
|
|
110
109
|
// component, but we handle the click via ClickableBehavior
|
|
111
|
-
onChange={
|
|
110
|
+
onChange={handleChange}
|
|
112
111
|
style={defaultStyle}
|
|
113
|
-
{
|
|
112
|
+
data-test-id={testId}
|
|
114
113
|
/>
|
|
115
114
|
{checked || checked == null ? checkboxIcon : <></>}
|
|
116
115
|
</React.Fragment>
|
|
117
116
|
);
|
|
118
|
-
}
|
|
119
|
-
|
|
117
|
+
},
|
|
118
|
+
);
|
|
120
119
|
|
|
121
120
|
const size = 16;
|
|
122
121
|
|
|
@@ -237,3 +236,5 @@ const _generateStyles = (checked: Checked, error: boolean) => {
|
|
|
237
236
|
styles[styleKey] = StyleSheet.create(newStyles);
|
|
238
237
|
return styles[styleKey];
|
|
239
238
|
};
|
|
239
|
+
|
|
240
|
+
export default CheckboxCore;
|
|
@@ -95,38 +95,47 @@ const StyledLegend = addStyle("legend");
|
|
|
95
95
|
* </CheckboxGroup>
|
|
96
96
|
* ```
|
|
97
97
|
*/
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const index = selectedValues.indexOf(changedValue);
|
|
104
|
-
const updatedSelection = [
|
|
105
|
-
...selectedValues.slice(0, index),
|
|
106
|
-
...selectedValues.slice(index + 1),
|
|
107
|
-
];
|
|
108
|
-
onChange(updatedSelection);
|
|
109
|
-
} else {
|
|
110
|
-
onChange([...selectedValues, changedValue]);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
render(): React.ReactNode {
|
|
98
|
+
const CheckboxGroup = React.forwardRef(
|
|
99
|
+
(
|
|
100
|
+
props: CheckboxGroupProps,
|
|
101
|
+
ref: React.ForwardedRef<HTMLFieldSetElement>,
|
|
102
|
+
) => {
|
|
115
103
|
const {
|
|
116
104
|
children,
|
|
117
105
|
label,
|
|
118
106
|
description,
|
|
119
107
|
errorMessage,
|
|
120
108
|
groupName,
|
|
109
|
+
onChange,
|
|
121
110
|
selectedValues,
|
|
122
111
|
style,
|
|
123
112
|
testId,
|
|
124
|
-
} =
|
|
113
|
+
} = props;
|
|
114
|
+
|
|
115
|
+
const handleChange = (
|
|
116
|
+
changedValue: string,
|
|
117
|
+
originalCheckedState: boolean,
|
|
118
|
+
) => {
|
|
119
|
+
if (originalCheckedState) {
|
|
120
|
+
const index = selectedValues.indexOf(changedValue);
|
|
121
|
+
const updatedSelection = [
|
|
122
|
+
...selectedValues.slice(0, index),
|
|
123
|
+
...selectedValues.slice(index + 1),
|
|
124
|
+
];
|
|
125
|
+
onChange(updatedSelection);
|
|
126
|
+
} else {
|
|
127
|
+
onChange([...selectedValues, changedValue]);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
125
130
|
|
|
126
131
|
const allChildren = React.Children.toArray(children).filter(Boolean);
|
|
127
132
|
|
|
128
133
|
return (
|
|
129
|
-
<StyledFieldset
|
|
134
|
+
<StyledFieldset
|
|
135
|
+
data-test-id={testId}
|
|
136
|
+
style={styles.fieldset}
|
|
137
|
+
ref={ref}
|
|
138
|
+
>
|
|
130
139
|
{/* We have a View here because fieldset cannot be used with flexbox*/}
|
|
131
140
|
<View style={style}>
|
|
132
141
|
{label && (
|
|
@@ -159,7 +168,7 @@ export default class CheckboxGroup extends React.Component<CheckboxGroupProps> {
|
|
|
159
168
|
groupName: groupName,
|
|
160
169
|
id: `${groupName}-${value}`,
|
|
161
170
|
key: value,
|
|
162
|
-
onChange: () =>
|
|
171
|
+
onChange: () => handleChange(value, checked),
|
|
163
172
|
style: [index > 0 && styles.defaultLineGap, style],
|
|
164
173
|
variant: "checkbox",
|
|
165
174
|
});
|
|
@@ -167,5 +176,7 @@ export default class CheckboxGroup extends React.Component<CheckboxGroupProps> {
|
|
|
167
176
|
</View>
|
|
168
177
|
</StyledFieldset>
|
|
169
178
|
);
|
|
170
|
-
}
|
|
171
|
-
|
|
179
|
+
},
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
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,23 @@ type DefaultProps = {
|
|
|
84
79
|
* <Checkbox checked={checked} onChange={setChecked} />
|
|
85
80
|
* ```
|
|
86
81
|
*/
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
82
|
+
const Checkbox = React.forwardRef(
|
|
83
|
+
(
|
|
84
|
+
props: ChoiceComponentProps,
|
|
85
|
+
ref: React.ForwardedRef<HTMLInputElement>,
|
|
86
|
+
) => {
|
|
87
|
+
const {disabled = false, error = false} = props;
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<ChoiceInternal
|
|
91
|
+
{...props}
|
|
92
|
+
variant="checkbox"
|
|
93
|
+
disabled={disabled}
|
|
94
|
+
error={error}
|
|
95
|
+
ref={ref}
|
|
96
|
+
/>
|
|
97
|
+
);
|
|
98
|
+
},
|
|
99
|
+
);
|
|
92
100
|
|
|
93
|
-
|
|
94
|
-
return <ChoiceInternal variant="checkbox" {...this.props} />;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
101
|
+
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,60 +55,61 @@ 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
|
-
disabled: false,
|
|
66
|
-
error: false,
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
handleClick: () => void = () => {
|
|
70
|
-
const {checked, onChange, variant} = this.props;
|
|
71
|
-
// Radio buttons cannot be unchecked
|
|
72
|
-
if (variant === "radio" && checked) {
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
onChange(!checked);
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
getChoiceCoreComponent(): typeof RadioCore | typeof CheckboxCore {
|
|
79
|
-
if (this.props.variant === "radio") {
|
|
80
|
-
return RadioCore;
|
|
81
|
-
} else {
|
|
82
|
-
return CheckboxCore;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
getLabel(id: string): React.ReactNode {
|
|
86
|
-
const {disabled, label} = this.props;
|
|
87
|
-
return (
|
|
88
|
-
<LabelMedium
|
|
89
|
-
style={[styles.label, disabled && styles.disabledLabel]}
|
|
90
|
-
>
|
|
91
|
-
<label htmlFor={id}>{label}</label>
|
|
92
|
-
</LabelMedium>
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
getDescription(id?: string): React.ReactNode {
|
|
96
|
-
const {description} = this.props;
|
|
97
|
-
return (
|
|
98
|
-
<LabelSmall style={styles.description} id={id}>
|
|
99
|
-
{description}
|
|
100
|
-
</LabelSmall>
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
render(): React.ReactNode {
|
|
58
|
+
*/ const ChoiceInternal = React.forwardRef(
|
|
59
|
+
(props: Props, ref: React.ForwardedRef<HTMLInputElement>) => {
|
|
104
60
|
const {
|
|
105
|
-
|
|
61
|
+
checked,
|
|
106
62
|
description,
|
|
63
|
+
disabled = false,
|
|
64
|
+
error = false,
|
|
107
65
|
id,
|
|
66
|
+
label,
|
|
108
67
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
109
68
|
onChange,
|
|
110
69
|
style,
|
|
111
70
|
className,
|
|
112
71
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
113
72
|
variant,
|
|
114
|
-
...coreProps
|
|
115
|
-
} =
|
|
116
|
-
|
|
73
|
+
// ...coreProps
|
|
74
|
+
} = props;
|
|
75
|
+
|
|
76
|
+
const handleClick: () => void = () => {
|
|
77
|
+
// Radio buttons cannot be unchecked
|
|
78
|
+
if (variant === "radio" && checked) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
onChange(!checked);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const getChoiceCoreComponent = ():
|
|
85
|
+
| typeof RadioCore
|
|
86
|
+
| typeof CheckboxCore => {
|
|
87
|
+
if (variant === "radio") {
|
|
88
|
+
return RadioCore;
|
|
89
|
+
} else {
|
|
90
|
+
return CheckboxCore;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const getLabel = (id: string): React.ReactNode => {
|
|
95
|
+
return (
|
|
96
|
+
<LabelMedium
|
|
97
|
+
style={[styles.label, disabled && styles.disabledLabel]}
|
|
98
|
+
>
|
|
99
|
+
<label htmlFor={id}>{label}</label>
|
|
100
|
+
</LabelMedium>
|
|
101
|
+
);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const getDescription = (id?: string): React.ReactNode => {
|
|
105
|
+
return (
|
|
106
|
+
<LabelSmall style={styles.description} id={id}>
|
|
107
|
+
{description}
|
|
108
|
+
</LabelSmall>
|
|
109
|
+
);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const ChoiceCore = getChoiceCoreComponent();
|
|
117
113
|
|
|
118
114
|
return (
|
|
119
115
|
<UniqueIDProvider mockOnFirstRender={true} scope="choice">
|
|
@@ -142,22 +138,26 @@ type DefaultProps = {
|
|
|
142
138
|
tabIndex={-1}
|
|
143
139
|
>
|
|
144
140
|
<ChoiceCore
|
|
145
|
-
{...
|
|
141
|
+
{...props}
|
|
146
142
|
id={uniqueId}
|
|
147
143
|
aria-describedby={descriptionId}
|
|
148
|
-
onClick={
|
|
144
|
+
onClick={handleClick}
|
|
145
|
+
disabled={disabled}
|
|
146
|
+
error={error}
|
|
147
|
+
ref={ref}
|
|
149
148
|
/>
|
|
150
149
|
<Strut size={Spacing.xSmall_8} />
|
|
151
|
-
{label &&
|
|
150
|
+
{label && getLabel(uniqueId)}
|
|
152
151
|
</View>
|
|
153
|
-
{description &&
|
|
152
|
+
{description && getDescription(descriptionId)}
|
|
154
153
|
</View>
|
|
155
154
|
);
|
|
156
155
|
}}
|
|
157
156
|
</UniqueIDProvider>
|
|
158
157
|
);
|
|
159
|
-
}
|
|
160
|
-
|
|
158
|
+
},
|
|
159
|
+
);
|
|
160
|
+
|
|
161
161
|
const styles = StyleSheet.create({
|
|
162
162
|
wrapper: {
|
|
163
163
|
flexDirection: "row",
|
|
@@ -181,3 +181,5 @@ const styles = StyleSheet.create({
|
|
|
181
181
|
color: Color.offBlack64,
|
|
182
182
|
},
|
|
183
183
|
});
|
|
184
|
+
|
|
185
|
+
export default ChoiceInternal;
|
|
@@ -12,7 +12,7 @@ type Props = AriaProps & {
|
|
|
12
12
|
/** User-defined. Should be distinct for each item in the group. */
|
|
13
13
|
value: string;
|
|
14
14
|
/** User-defined. Whether this choice option is disabled. Default false. */
|
|
15
|
-
disabled
|
|
15
|
+
disabled?: boolean;
|
|
16
16
|
/** User-defined. Optional id for testing purposes. */
|
|
17
17
|
testId?: string;
|
|
18
18
|
/** User-defined. Optional additional styling. */
|
|
@@ -21,7 +21,7 @@ type Props = AriaProps & {
|
|
|
21
21
|
* Auto-populated by parent. Whether this choice is checked.
|
|
22
22
|
* @ignore
|
|
23
23
|
*/
|
|
24
|
-
checked
|
|
24
|
+
checked?: boolean;
|
|
25
25
|
/**
|
|
26
26
|
* Auto-populated by parent. Whether this choice is in error mode (everything
|
|
27
27
|
* in a choice group would be in error mode at the same time).
|
|
@@ -43,7 +43,7 @@ type Props = AriaProps & {
|
|
|
43
43
|
* Auto-populated by parent. Returns the new checked state of the component.
|
|
44
44
|
* @ignore
|
|
45
45
|
*/
|
|
46
|
-
onChange
|
|
46
|
+
onChange?: (newCheckedState: boolean) => unknown;
|
|
47
47
|
/**
|
|
48
48
|
* Auto-populated by parent.
|
|
49
49
|
* @ignore
|
|
@@ -51,12 +51,6 @@ type Props = AriaProps & {
|
|
|
51
51
|
variant?: "radio" | "checkbox";
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
-
type DefaultProps = {
|
|
55
|
-
checked: Props["checked"];
|
|
56
|
-
disabled: Props["disabled"];
|
|
57
|
-
onChange: Props["onChange"];
|
|
58
|
-
};
|
|
59
|
-
|
|
60
54
|
/**
|
|
61
55
|
* This is a labeled 🔘 or ☑️ item. Choice is meant to be used as children of
|
|
62
56
|
* CheckboxGroup and RadioGroup because many of its props are auto-populated
|
|
@@ -123,27 +117,40 @@ type DefaultProps = {
|
|
|
123
117
|
* </RadioGroup>
|
|
124
118
|
* ```
|
|
125
119
|
*/
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
120
|
+
const Choice = React.forwardRef(
|
|
121
|
+
(props: Props, ref: React.ForwardedRef<HTMLInputElement>) => {
|
|
122
|
+
const {
|
|
123
|
+
checked = false,
|
|
124
|
+
disabled = false,
|
|
125
|
+
onChange = () => {},
|
|
126
|
+
// we don't need this going into the ChoiceComponent
|
|
127
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
128
|
+
value,
|
|
129
|
+
variant,
|
|
130
|
+
...remainingProps
|
|
131
|
+
} = props;
|
|
132
|
+
|
|
133
|
+
const getChoiceComponent = (
|
|
134
|
+
variant?: string | null,
|
|
135
|
+
): typeof Radio | typeof Checkbox => {
|
|
136
|
+
if (variant === "checkbox") {
|
|
137
|
+
return Checkbox;
|
|
138
|
+
} else {
|
|
139
|
+
return Radio;
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const ChoiceComponent = getChoiceComponent(variant);
|
|
144
|
+
return (
|
|
145
|
+
<ChoiceComponent
|
|
146
|
+
{...remainingProps}
|
|
147
|
+
checked={checked}
|
|
148
|
+
disabled={disabled}
|
|
149
|
+
onChange={onChange}
|
|
150
|
+
ref={ref}
|
|
151
|
+
/>
|
|
152
|
+
);
|
|
153
|
+
},
|
|
154
|
+
);
|
|
132
155
|
|
|
133
|
-
|
|
134
|
-
variant?: string | null,
|
|
135
|
-
): typeof Radio | typeof Checkbox {
|
|
136
|
-
if (variant === "checkbox") {
|
|
137
|
-
return Checkbox;
|
|
138
|
-
} else {
|
|
139
|
-
return Radio;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
render(): React.ReactNode {
|
|
143
|
-
// we don't need this going into the ChoiceComponent
|
|
144
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
145
|
-
const {value, variant, ...remainingProps} = this.props;
|
|
146
|
-
const ChoiceComponent = this.getChoiceComponent(variant);
|
|
147
|
-
return <ChoiceComponent {...remainingProps} />;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
156
|
+
export default Choice;
|
|
@@ -12,13 +12,13 @@ const StyledInput = addStyle("input");
|
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* The internal stateless 🔘 Radio button
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
*/ const RadioCore = React.forwardRef(
|
|
16
|
+
(props: ChoiceCoreProps, ref: React.ForwardedRef<HTMLInputElement>) => {
|
|
17
|
+
const handleChange = () => {
|
|
18
|
+
// Empty because change is handled by ClickableBehavior
|
|
19
|
+
return;
|
|
20
|
+
};
|
|
20
21
|
|
|
21
|
-
render(): React.ReactNode {
|
|
22
22
|
const {
|
|
23
23
|
checked,
|
|
24
24
|
disabled,
|
|
@@ -27,7 +27,7 @@ const StyledInput = addStyle("input");
|
|
|
27
27
|
id,
|
|
28
28
|
testId,
|
|
29
29
|
...sharedProps
|
|
30
|
-
} =
|
|
30
|
+
} = props;
|
|
31
31
|
|
|
32
32
|
const stateStyles = _generateStyles(checked, error);
|
|
33
33
|
const defaultStyle = [
|
|
@@ -36,9 +36,7 @@ const StyledInput = addStyle("input");
|
|
|
36
36
|
!disabled && stateStyles.default,
|
|
37
37
|
disabled && sharedStyles.disabled,
|
|
38
38
|
];
|
|
39
|
-
|
|
40
|
-
"data-test-id": testId,
|
|
41
|
-
} as const;
|
|
39
|
+
|
|
42
40
|
return (
|
|
43
41
|
<React.Fragment>
|
|
44
42
|
<StyledInput
|
|
@@ -51,15 +49,17 @@ const StyledInput = addStyle("input");
|
|
|
51
49
|
name={groupName}
|
|
52
50
|
// Need to specify because this is a controlled React form
|
|
53
51
|
// component, but we handle the click via ClickableBehavior
|
|
54
|
-
onChange={
|
|
52
|
+
onChange={handleChange}
|
|
55
53
|
style={defaultStyle}
|
|
56
|
-
{
|
|
54
|
+
data-test-id={testId}
|
|
55
|
+
ref={ref}
|
|
57
56
|
/>
|
|
58
57
|
{disabled && checked && <span style={disabledChecked} />}
|
|
59
58
|
</React.Fragment>
|
|
60
59
|
);
|
|
61
|
-
}
|
|
62
|
-
|
|
60
|
+
},
|
|
61
|
+
);
|
|
62
|
+
|
|
63
63
|
const size = 16; // circle with a different color. Here, we add that center circle. // If the checkbox is disabled and selected, it has a border but also an inner
|
|
64
64
|
const disabledChecked = {
|
|
65
65
|
position: "absolute",
|
|
@@ -175,3 +175,5 @@ const _generateStyles = (checked: Checked, error: boolean) => {
|
|
|
175
175
|
styles[styleKey] = StyleSheet.create(newStyles);
|
|
176
176
|
return styles[styleKey];
|
|
177
177
|
};
|
|
178
|
+
|
|
179
|
+
export default RadioCore;
|