@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.
Files changed (33) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/components/checkbox-core.d.ts +13 -8
  3. package/dist/components/checkbox-core.js.flow +19 -10
  4. package/dist/components/checkbox-group.d.ts +2 -5
  5. package/dist/components/checkbox-group.js.flow +5 -6
  6. package/dist/components/checkbox.d.ts +33 -39
  7. package/dist/components/checkbox.js.flow +38 -41
  8. package/dist/components/choice-internal.d.ts +19 -31
  9. package/dist/components/choice-internal.js.flow +25 -32
  10. package/dist/components/choice.d.ts +50 -60
  11. package/dist/components/choice.js.flow +79 -84
  12. package/dist/components/radio-core.d.ts +13 -5
  13. package/dist/components/radio-core.js.flow +19 -7
  14. package/dist/components/radio-group.d.ts +2 -5
  15. package/dist/components/radio-group.js.flow +5 -6
  16. package/dist/components/radio.d.ts +18 -24
  17. package/dist/components/radio.js.flow +24 -27
  18. package/dist/es/index.js +262 -294
  19. package/dist/index.js +262 -294
  20. package/dist/util/types.d.ts +1 -1
  21. package/dist/util/types.js.flow +1 -1
  22. package/package.json +6 -6
  23. package/src/components/__tests__/{checkbox.test.js → checkbox.test.tsx} +55 -1
  24. package/src/components/checkbox-core.tsx +66 -71
  25. package/src/components/checkbox-group.tsx +62 -59
  26. package/src/components/checkbox.tsx +19 -16
  27. package/src/components/choice-internal.tsx +80 -77
  28. package/src/components/choice.tsx +34 -26
  29. package/src/components/radio-core.tsx +40 -44
  30. package/src/components/radio-group.tsx +57 -59
  31. package/src/components/radio.tsx +19 -16
  32. package/src/util/types.ts +1 -1
  33. package/tsconfig-build.tsbuildinfo +1 -1
@@ -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: boolean;
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: boolean;
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: (newCheckedState: boolean) => unknown;
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,41 @@ type DefaultProps = {
123
117
  * </RadioGroup>
124
118
  * ```
125
119
  */
126
- export default class Choice extends React.Component<Props> {
127
- static defaultProps: DefaultProps = {
128
- checked: false,
129
- disabled: false,
130
- onChange: () => {},
131
- };
120
+ const Choice = React.forwardRef(function Choice(
121
+ props: Props,
122
+ ref: React.ForwardedRef<HTMLInputElement>,
123
+ ) {
124
+ const {
125
+ checked = false,
126
+ disabled = false,
127
+ onChange = () => {},
128
+ // we don't need this going into the ChoiceComponent
129
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
130
+ value,
131
+ variant,
132
+ ...remainingProps
133
+ } = props;
132
134
 
133
- getChoiceComponent(
135
+ const getChoiceComponent = (
134
136
  variant?: string | null,
135
- ): typeof Radio | typeof Checkbox {
137
+ ): typeof Radio | typeof Checkbox => {
136
138
  if (variant === "checkbox") {
137
139
  return Checkbox;
138
140
  } else {
139
141
  return Radio;
140
142
  }
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
- }
143
+ };
144
+
145
+ const ChoiceComponent = getChoiceComponent(variant);
146
+ return (
147
+ <ChoiceComponent
148
+ {...remainingProps}
149
+ checked={checked}
150
+ disabled={disabled}
151
+ onChange={onChange}
152
+ ref={ref}
153
+ />
154
+ );
155
+ });
156
+
157
+ export default Choice;
@@ -12,54 +12,48 @@ const StyledInput = addStyle("input");
12
12
 
13
13
  /**
14
14
  * The internal stateless 🔘 Radio button
15
- */ export default class RadioCore extends React.Component<ChoiceCoreProps> {
16
- handleChange: () => void = () => {
15
+ */ const RadioCore = React.forwardRef(function RadioCore(
16
+ props: ChoiceCoreProps,
17
+ ref: React.ForwardedRef<HTMLInputElement>,
18
+ ) {
19
+ const handleChange = () => {
17
20
  // Empty because change is handled by ClickableBehavior
18
21
  return;
19
22
  };
20
23
 
21
- render(): React.ReactNode {
22
- const {
23
- checked,
24
- disabled,
25
- error,
26
- groupName,
27
- id,
28
- testId,
29
- ...sharedProps
30
- } = this.props;
31
-
32
- const stateStyles = _generateStyles(checked, error);
33
- const defaultStyle = [
34
- sharedStyles.inputReset,
35
- sharedStyles.default,
36
- !disabled && stateStyles.default,
37
- disabled && sharedStyles.disabled,
38
- ];
39
- const props = {
40
- "data-test-id": testId,
41
- } as const;
42
- return (
43
- <React.Fragment>
44
- <StyledInput
45
- {...sharedProps}
46
- type="radio"
47
- aria-invalid={error}
48
- checked={checked ?? undefined}
49
- disabled={disabled}
50
- id={id}
51
- name={groupName}
52
- // Need to specify because this is a controlled React form
53
- // component, but we handle the click via ClickableBehavior
54
- onChange={this.handleChange}
55
- style={defaultStyle}
56
- {...props}
57
- />
58
- {disabled && checked && <span style={disabledChecked} />}
59
- </React.Fragment>
60
- );
61
- }
62
- }
24
+ const {checked, disabled, error, groupName, id, testId, ...sharedProps} =
25
+ props;
26
+
27
+ const stateStyles = _generateStyles(checked, error);
28
+ const defaultStyle = [
29
+ sharedStyles.inputReset,
30
+ sharedStyles.default,
31
+ !disabled && stateStyles.default,
32
+ disabled && sharedStyles.disabled,
33
+ ];
34
+
35
+ return (
36
+ <React.Fragment>
37
+ <StyledInput
38
+ {...sharedProps}
39
+ type="radio"
40
+ aria-invalid={error}
41
+ checked={checked ?? undefined}
42
+ disabled={disabled}
43
+ id={id}
44
+ name={groupName}
45
+ // Need to specify because this is a controlled React form
46
+ // component, but we handle the click via ClickableBehavior
47
+ onChange={handleChange}
48
+ style={defaultStyle}
49
+ data-test-id={testId}
50
+ ref={ref}
51
+ />
52
+ {disabled && checked && <span style={disabledChecked} />}
53
+ </React.Fragment>
54
+ );
55
+ });
56
+
63
57
  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
58
  const disabledChecked = {
65
59
  position: "absolute",
@@ -175,3 +169,5 @@ const _generateStyles = (checked: Checked, error: boolean) => {
175
169
  styles[styleKey] = StyleSheet.create(newStyles);
176
170
  return styles[styleKey];
177
171
  };
172
+
173
+ export default RadioCore;
@@ -96,66 +96,64 @@ const StyledLegend = addStyle("legend");
96
96
  * </RadioGroup>
97
97
  * ```
98
98
  */
99
- export default class RadioGroup extends React.Component<RadioGroupProps> {
100
- handleChange(changedValue: string) {
101
- this.props.onChange(changedValue);
102
- }
99
+ const RadioGroup = React.forwardRef(function RadioGroup(
100
+ props: RadioGroupProps,
101
+ ref: React.ForwardedRef<HTMLFieldSetElement>,
102
+ ) {
103
+ const {
104
+ children,
105
+ label,
106
+ description,
107
+ errorMessage,
108
+ groupName,
109
+ onChange,
110
+ selectedValue,
111
+ style,
112
+ testId,
113
+ } = props;
103
114
 
104
- render(): React.ReactNode {
105
- const {
106
- children,
107
- label,
108
- description,
109
- errorMessage,
110
- groupName,
111
- selectedValue,
112
- style,
113
- testId,
114
- } = this.props;
115
+ const allChildren = React.Children.toArray(children).filter(Boolean);
115
116
 
116
- const allChildren = React.Children.toArray(children).filter(Boolean);
117
+ return (
118
+ <StyledFieldset data-test-id={testId} style={styles.fieldset} ref={ref}>
119
+ {/* We have a View here because fieldset cannot be used with flexbox*/}
120
+ <View style={style}>
121
+ {label && (
122
+ <StyledLegend style={styles.legend}>
123
+ <LabelMedium>{label}</LabelMedium>
124
+ </StyledLegend>
125
+ )}
126
+ {description && (
127
+ <LabelSmall style={styles.description}>
128
+ {description}
129
+ </LabelSmall>
130
+ )}
131
+ {errorMessage && (
132
+ <LabelSmall style={styles.error}>{errorMessage}</LabelSmall>
133
+ )}
134
+ {(label || description || errorMessage) && (
135
+ <Strut size={Spacing.small_12} />
136
+ )}
117
137
 
118
- return (
119
- <StyledFieldset data-test-id={testId} style={styles.fieldset}>
120
- {/* We have a View here because fieldset cannot be used with flexbox*/}
121
- <View style={style}>
122
- {label && (
123
- <StyledLegend style={styles.legend}>
124
- <LabelMedium>{label}</LabelMedium>
125
- </StyledLegend>
126
- )}
127
- {description && (
128
- <LabelSmall style={styles.description}>
129
- {description}
130
- </LabelSmall>
131
- )}
132
- {errorMessage && (
133
- <LabelSmall style={styles.error}>
134
- {errorMessage}
135
- </LabelSmall>
136
- )}
137
- {(label || description || errorMessage) && (
138
- <Strut size={Spacing.small_12} />
139
- )}
138
+ {allChildren.map((child, index) => {
139
+ // @ts-expect-error [FEI-5019] - TS2339 - Property 'props' does not exist on type 'ReactChild | ReactFragment | ReactPortal'.
140
+ const {style, value} = child.props;
141
+ const checked = selectedValue === value;
142
+ // @ts-expect-error [FEI-5019] - TS2769 - No overload matches this call.
143
+ return React.cloneElement(child, {
144
+ checked: checked,
145
+ error: !!errorMessage,
146
+ groupName: groupName,
147
+ id: `${groupName}-${value}`,
148
+ key: value,
149
+ onChange: () => onChange(value),
150
+ style: [index > 0 && styles.defaultLineGap, style],
151
+ variant: "radio",
152
+ });
153
+ })}
154
+ </View>
155
+ </StyledFieldset>
156
+ );
157
+ });
140
158
 
141
- {allChildren.map((child, index) => {
142
- // @ts-expect-error [FEI-5019] - TS2339 - Property 'props' does not exist on type 'ReactChild | ReactFragment | ReactPortal'.
143
- const {style, value} = child.props;
144
- const checked = selectedValue === value;
145
- // @ts-expect-error [FEI-5019] - TS2769 - No overload matches this call.
146
- return React.cloneElement(child, {
147
- checked: checked,
148
- error: !!errorMessage,
149
- groupName: groupName,
150
- id: `${groupName}-${value}`,
151
- key: value,
152
- onChange: () => this.handleChange(value),
153
- style: [index > 0 && styles.defaultLineGap, style],
154
- variant: "radio",
155
- });
156
- })}
157
- </View>
158
- </StyledFieldset>
159
- );
160
- }
161
- }
159
+ export default RadioGroup;
@@ -12,11 +12,11 @@ type ChoiceComponentProps = AriaProps & {
12
12
  /**
13
13
  * Whether this component is disabled
14
14
  */
15
- disabled: boolean;
15
+ disabled?: boolean;
16
16
  /**
17
17
  * Whether this component should show an error state
18
18
  */
19
- error: boolean;
19
+ error?: boolean;
20
20
  /**
21
21
  * Callback when this component is selected. The newCheckedState is the
22
22
  * new checked state of the component.
@@ -56,24 +56,27 @@ type ChoiceComponentProps = AriaProps & {
56
56
  groupName?: string;
57
57
  };
58
58
 
59
- type DefaultProps = {
60
- disabled: ChoiceComponentProps["disabled"];
61
- error: ChoiceComponentProps["error"];
62
- };
63
-
64
59
  /**
65
60
  * 🔘 A nicely styled radio button for all your non-AMFM radio button needs. Can
66
61
  * optionally take label and description props.
67
62
  *
68
63
  * This component should not really be used by itself because radio buttons are
69
64
  * often grouped together. See RadioGroup.
70
- */ export default class Radio extends React.Component<ChoiceComponentProps> {
71
- static defaultProps: DefaultProps = {
72
- disabled: false,
73
- error: false,
74
- };
65
+ */ const Radio = React.forwardRef(function Radio(
66
+ props: ChoiceComponentProps,
67
+ ref: React.ForwardedRef<HTMLInputElement>,
68
+ ) {
69
+ const {disabled = false, error = false, ...otherProps} = props;
70
+
71
+ return (
72
+ <ChoiceInternal
73
+ {...otherProps}
74
+ variant="radio"
75
+ disabled={disabled}
76
+ error={error}
77
+ ref={ref}
78
+ />
79
+ );
80
+ });
75
81
 
76
- render(): React.ReactNode {
77
- return <ChoiceInternal variant="radio" {...this.props} />;
78
- }
79
- }
82
+ export default Radio;
package/src/util/types.ts CHANGED
@@ -46,7 +46,7 @@ export type ChoiceComponentProps = ChoiceCoreProps & {
46
46
 
47
47
  export type SharedGroupProps = {
48
48
  /** Children should be Choice components. */
49
- children: Choice;
49
+ children: typeof Choice;
50
50
  /** Group name for this checkbox or radio group. Should be unique for all
51
51
  * such groups displayed on a page. */
52
52
  groupName: string;