@khanacademy/wonder-blocks-form 4.9.1 → 4.9.3

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 (38) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/components/checkbox-core.d.ts +2 -2
  3. package/dist/components/checkbox.d.ts +2 -2
  4. package/dist/components/choice-internal.d.ts +2 -2
  5. package/dist/components/choice.d.ts +2 -2
  6. package/dist/components/radio-core.d.ts +2 -2
  7. package/dist/components/radio.d.ts +2 -2
  8. package/dist/components/text-area.d.ts +2 -2
  9. package/dist/components/text-field.d.ts +4 -1
  10. package/dist/es/index.js +31 -78
  11. package/dist/index.js +31 -78
  12. package/package.json +7 -7
  13. package/src/__tests__/__snapshots__/custom-snapshot.test.tsx.snap +0 -247
  14. package/src/__tests__/custom-snapshot.test.tsx +0 -48
  15. package/src/components/__tests__/checkbox-group.test.tsx +0 -162
  16. package/src/components/__tests__/checkbox.test.tsx +0 -138
  17. package/src/components/__tests__/field-heading.test.tsx +0 -225
  18. package/src/components/__tests__/labeled-text-field.test.tsx +0 -727
  19. package/src/components/__tests__/radio-group.test.tsx +0 -182
  20. package/src/components/__tests__/text-area.test.tsx +0 -1286
  21. package/src/components/__tests__/text-field.test.tsx +0 -562
  22. package/src/components/checkbox-core.tsx +0 -239
  23. package/src/components/checkbox-group.tsx +0 -174
  24. package/src/components/checkbox.tsx +0 -99
  25. package/src/components/choice-internal.tsx +0 -184
  26. package/src/components/choice.tsx +0 -157
  27. package/src/components/field-heading.tsx +0 -169
  28. package/src/components/group-styles.ts +0 -33
  29. package/src/components/labeled-text-field.tsx +0 -317
  30. package/src/components/radio-core.tsx +0 -171
  31. package/src/components/radio-group.tsx +0 -159
  32. package/src/components/radio.tsx +0 -82
  33. package/src/components/text-area.tsx +0 -430
  34. package/src/components/text-field.tsx +0 -437
  35. package/src/index.ts +0 -17
  36. package/src/util/types.ts +0 -85
  37. package/tsconfig-build.json +0 -19
  38. package/tsconfig-build.tsbuildinfo +0 -1
@@ -1,157 +0,0 @@
1
- import * as React from "react";
2
-
3
- import type {AriaProps, StyleType} from "@khanacademy/wonder-blocks-core";
4
- import Checkbox from "./checkbox";
5
- import Radio from "./radio";
6
-
7
- type Props = AriaProps & {
8
- /** User-defined. Label for the field. */
9
- label: React.ReactNode;
10
- /** User-defined. Optional description for the field. */
11
- description?: React.ReactNode;
12
- /** User-defined. Should be distinct for each item in the group. */
13
- value: string;
14
- /** User-defined. Whether this choice option is disabled. Default false. */
15
- disabled?: boolean;
16
- /** User-defined. Optional id for testing purposes. */
17
- testId?: string;
18
- /** User-defined. Optional additional styling. */
19
- style?: StyleType;
20
- /**
21
- * Auto-populated by parent. Whether this choice is checked.
22
- * @ignore
23
- */
24
- checked?: boolean;
25
- /**
26
- * Auto-populated by parent. Whether this choice is in error mode (everything
27
- * in a choice group would be in error mode at the same time).
28
- * @ignore
29
- */
30
- error?: boolean;
31
- /**
32
- * Auto-populated by parent. Used for accessibility purposes, where the label
33
- * id should match the input id.
34
- * @ignore
35
- */
36
- id?: string;
37
- /**
38
- * Auto-populated by parent's groupName prop.
39
- * @ignore
40
- */
41
- groupName?: string;
42
- /**
43
- * Auto-populated by parent. Returns the new checked state of the component.
44
- * @ignore
45
- */
46
- onChange?: (newCheckedState: boolean) => unknown;
47
- /**
48
- * Auto-populated by parent.
49
- * @ignore
50
- */
51
- variant?: "radio" | "checkbox";
52
- };
53
-
54
- /**
55
- * This is a labeled 🔘 or ☑️ item. Choice is meant to be used as children of
56
- * CheckboxGroup and RadioGroup because many of its props are auto-populated
57
- * and not shown in the documentation here. See those components for usage
58
- * examples.
59
- *
60
- * If you wish to use just a single field, use Checkbox or Radio with the
61
- * optional label and description props.
62
- *
63
- * ### Checkbox Usage
64
- *
65
- * ```jsx
66
- * import {Choice, CheckboxGroup} from "@khanacademy/wonder-blocks-form";
67
- *
68
- * const [selectedValues, setSelectedValues] = React.useState([]);
69
- *
70
- * // Checkbox usage
71
- * <CheckboxGroup
72
- * label="some-label"
73
- * description="some-description"
74
- * groupName="some-group-name"
75
- * onChange={setSelectedValues}
76
- * selectedValues={selectedValues}
77
- * />
78
- * // Add as many choices as necessary
79
- * <Choice
80
- * label="Choice 1"
81
- * value="some-choice-value"
82
- * description="Some choice description."
83
- * />
84
- * <Choice
85
- * label="Choice 2"
86
- * value="some-choice-value-2"
87
- * description="Some choice description."
88
- * />
89
- * </CheckboxGroup>
90
- * ```
91
- *
92
- * ### Radio Usage
93
- *
94
- * ```jsx
95
- * import {Choice, RadioGroup} from "@khanacademy/wonder-blocks-form";
96
- *
97
- * const [selectedValue, setSelectedValue] = React.useState("");
98
- *
99
- * <RadioGroup
100
- * label="some-label"
101
- * description="some-description"
102
- * groupName="some-group-name"
103
- * onChange={setSelectedValue}>
104
- * selectedValues={selectedValue}
105
- * />
106
- * // Add as many choices as necessary
107
- * <Choice
108
- * label="Choice 1"
109
- * value="some-choice-value"
110
- * description="Some choice description."
111
- * />
112
- * <Choice
113
- * label="Choice 2"
114
- * value="some-choice-value-2"
115
- * description="Some choice description."
116
- * />
117
- * </RadioGroup>
118
- * ```
119
- */
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;
134
-
135
- const getChoiceComponent = (
136
- variant?: string | null,
137
- ): typeof Radio | typeof Checkbox => {
138
- if (variant === "checkbox") {
139
- return Checkbox;
140
- } else {
141
- return Radio;
142
- }
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;
@@ -1,169 +0,0 @@
1
- import * as React from "react";
2
- import {StyleSheet} from "aphrodite";
3
-
4
- import {View, addStyle, StyleType} from "@khanacademy/wonder-blocks-core";
5
- import {Strut} from "@khanacademy/wonder-blocks-layout";
6
- import {color, spacing} from "@khanacademy/wonder-blocks-tokens";
7
- import {LabelMedium, LabelSmall} from "@khanacademy/wonder-blocks-typography";
8
-
9
- type Props = {
10
- /**
11
- * The form field component.
12
- */
13
- field: React.ReactNode;
14
- /**
15
- * The title for the label element.
16
- */
17
- label: React.ReactNode;
18
- /**
19
- * The text for the description element.
20
- */
21
- description?: React.ReactNode;
22
- /**
23
- * Whether this field is required to continue.
24
- */
25
- required?: boolean;
26
- /**
27
- * The message for the error element.
28
- */
29
- error?: React.ReactNode;
30
- /**
31
- * Custom styles for the field heading container.
32
- */
33
- style?: StyleType;
34
- /**
35
- * A unique id to link the label (and optional error) to the field.
36
- *
37
- * The label will assume that the field will have its id formatted as `${id}-field`.
38
- * The field can assume that the error will have its id formatted as `${id}-error`.
39
- */
40
- id?: string;
41
- /**
42
- * Optional test ID for e2e testing.
43
- */
44
- testId?: string;
45
- /**
46
- * Change the field’s sub-components to fit a dark background.
47
- */
48
- light?: boolean;
49
- };
50
-
51
- const StyledSpan = addStyle("span");
52
-
53
- /**
54
- * A FieldHeading is an element that provides a label, description, and error element
55
- * to present better context and hints to any type of form field component.
56
- */
57
- export default class FieldHeading extends React.Component<Props> {
58
- renderLabel(): React.ReactNode {
59
- const {label, id, required, testId, light} = this.props;
60
-
61
- const requiredIcon = (
62
- <StyledSpan
63
- style={light ? styles.lightRequired : styles.required}
64
- aria-hidden={true}
65
- >
66
- {" "}
67
- *
68
- </StyledSpan>
69
- );
70
-
71
- return (
72
- <React.Fragment>
73
- <LabelMedium
74
- style={light ? styles.lightLabel : styles.label}
75
- tag="label"
76
- htmlFor={id && `${id}-field`}
77
- testId={testId && `${testId}-label`}
78
- >
79
- {label}
80
- {required && requiredIcon}
81
- </LabelMedium>
82
- <Strut size={spacing.xxxSmall_4} />
83
- </React.Fragment>
84
- );
85
- }
86
-
87
- maybeRenderDescription(): React.ReactNode | null | undefined {
88
- const {description, testId, light} = this.props;
89
-
90
- if (!description) {
91
- return null;
92
- }
93
-
94
- return (
95
- <React.Fragment>
96
- <LabelSmall
97
- style={light ? styles.lightDescription : styles.description}
98
- testId={testId && `${testId}-description`}
99
- >
100
- {description}
101
- </LabelSmall>
102
- <Strut size={spacing.xxxSmall_4} />
103
- </React.Fragment>
104
- );
105
- }
106
-
107
- maybeRenderError(): React.ReactNode | null | undefined {
108
- const {error, id, testId, light} = this.props;
109
-
110
- if (!error) {
111
- return null;
112
- }
113
-
114
- return (
115
- <React.Fragment>
116
- <Strut size={spacing.small_12} />
117
- <LabelSmall
118
- style={light ? styles.lightError : styles.error}
119
- role="alert"
120
- id={id && `${id}-error`}
121
- testId={testId && `${testId}-error`}
122
- >
123
- {error}
124
- </LabelSmall>
125
- </React.Fragment>
126
- );
127
- }
128
-
129
- render(): React.ReactNode {
130
- const {field, style} = this.props;
131
-
132
- return (
133
- <View style={style}>
134
- {this.renderLabel()}
135
- {this.maybeRenderDescription()}
136
- <Strut size={spacing.xSmall_8} />
137
- {field}
138
- {this.maybeRenderError()}
139
- </View>
140
- );
141
- }
142
- }
143
-
144
- const styles = StyleSheet.create({
145
- label: {
146
- color: color.offBlack,
147
- },
148
- lightLabel: {
149
- color: color.white,
150
- },
151
- description: {
152
- color: color.offBlack64,
153
- },
154
- lightDescription: {
155
- color: color.white64,
156
- },
157
- error: {
158
- color: color.red,
159
- },
160
- lightError: {
161
- color: color.fadedRed,
162
- },
163
- required: {
164
- color: color.red,
165
- },
166
- lightRequired: {
167
- color: color.fadedRed,
168
- },
169
- });
@@ -1,33 +0,0 @@
1
- import {StyleSheet} from "aphrodite";
2
-
3
- import {color, spacing} from "@khanacademy/wonder-blocks-tokens";
4
-
5
- import type {StyleDeclaration} from "aphrodite";
6
-
7
- const styles: StyleDeclaration = StyleSheet.create({
8
- fieldset: {
9
- border: "none",
10
- padding: 0,
11
- margin: 0,
12
- },
13
-
14
- legend: {
15
- padding: 0,
16
- },
17
-
18
- description: {
19
- marginTop: spacing.xxxSmall_4,
20
- color: color.offBlack64,
21
- },
22
-
23
- error: {
24
- marginTop: spacing.xxxSmall_4,
25
- color: color.red,
26
- },
27
-
28
- defaultLineGap: {
29
- marginTop: spacing.xSmall_8,
30
- },
31
- });
32
-
33
- export default styles;
@@ -1,317 +0,0 @@
1
- import * as React from "react";
2
-
3
- import {IDProvider, StyleType} from "@khanacademy/wonder-blocks-core";
4
-
5
- import FieldHeading from "./field-heading";
6
- import TextField from "./text-field";
7
- import type {NumericInputProps} from "./text-field";
8
- import {OmitConstrained} from "../util/types";
9
-
10
- type WithForwardRef = {
11
- forwardedRef: React.ForwardedRef<HTMLInputElement>;
12
- };
13
-
14
- type CommonProps = {
15
- /**
16
- * An optional unique identifier for the TextField.
17
- * If no id is specified, a unique id will be auto-generated.
18
- */
19
- id?: string;
20
- /**
21
- * Provide a label for the TextField.
22
- */
23
- label: React.ReactNode;
24
- /**
25
- * Provide a description for the TextField.
26
- */
27
- description?: React.ReactNode;
28
- /**
29
- * The input value.
30
- */
31
- value: string;
32
- /**
33
- * Makes a read-only input field that cannot be focused. Defaults to false.
34
- */
35
- disabled: boolean;
36
- /**
37
- * Whether this field is required to to continue, or the error message to
38
- * render if this field is left blank.
39
- *
40
- * This can be a boolean or a string.
41
- *
42
- * String:
43
- * Please pass in a translated string to use as the error message that will
44
- * render if the user leaves this field blank. If this field is required,
45
- * and a string is not passed in, a default untranslated string will render
46
- * upon error.
47
- * Note: The string will not be used if a `validate` prop is passed in.
48
- *
49
- * Example message: i18n._("A password is required to log in.")
50
- *
51
- * Boolean:
52
- * True/false indicating whether this field is required. Please do not pass
53
- * in `true` if possible - pass in the error string instead.
54
- * If `true` is passed, and a `validate` prop is not passed, that means
55
- * there is no corresponding message and the default untranlsated message
56
- * will be used.
57
- */
58
- required?: boolean | string;
59
- /**
60
- * Identifies the element or elements that describes this text field.
61
- */
62
- ariaDescribedby?: string | undefined;
63
- /**
64
- * Provide a validation for the input value.
65
- * Return a string error message or null | void for a valid input.
66
- */
67
- validate?: (value: string) => string | null | undefined;
68
- /**
69
- * Called when the TextField input is validated.
70
- */
71
- onValidate?: (errorMessage?: string | null | undefined) => unknown;
72
- /**
73
- * Called when the value has changed.
74
- */
75
- onChange: (newValue: string) => unknown;
76
- /**
77
- * Called when a key is pressed.
78
- */
79
- onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => unknown;
80
- /**
81
- * Called when the element has been focused.
82
- */
83
- onFocus?: (event: React.FocusEvent<HTMLInputElement>) => unknown;
84
- /**
85
- * Called when the element has been blurred.
86
- */
87
- onBlur?: (event: React.FocusEvent<HTMLInputElement>) => unknown;
88
- /**
89
- * Provide hints or examples of what to enter.
90
- */
91
- placeholder?: string;
92
- /**
93
- * Change the field’s sub-components to fit a dark background.
94
- */
95
- light: boolean;
96
- /**
97
- * Custom styles for the container.
98
- *
99
- * Note: This style is passed to the field heading container
100
- * due to scenarios where we would like to set a specific
101
- * width for the text field. If we apply the style directly
102
- * to the text field, the container would not be affected.
103
- * For example, setting text field to "width: 50%" would not
104
- * affect the container since text field is a child of the container.
105
- * In this case, the container would maintain its width ocuppying
106
- * unnecessary space when the text field is smaller.
107
- */
108
- style?: StyleType;
109
- /**
110
- * Optional test ID for e2e testing.
111
- */
112
- testId?: string;
113
- /**
114
- * Specifies if the TextField is read-only.
115
- */
116
- readOnly?: boolean;
117
- /**
118
- * Specifies if the TextField allows autocomplete.
119
- */
120
- autoComplete?: string;
121
- };
122
-
123
- type OtherInputProps = CommonProps & {
124
- /**
125
- * Determines the type of input. Defaults to text.
126
- */
127
- type: "text" | "password" | "email" | "tel";
128
- };
129
-
130
- type FullNumericInputProps = NumericInputProps & CommonProps;
131
- type Props = OtherInputProps | FullNumericInputProps;
132
- type PropsWithForwardRef = Props & WithForwardRef;
133
-
134
- type DefaultProps = {
135
- type: PropsWithForwardRef["type"];
136
- disabled: PropsWithForwardRef["disabled"];
137
- light: PropsWithForwardRef["light"];
138
- };
139
-
140
- type State = {
141
- /**
142
- * Displayed when the validation fails.
143
- */
144
- error: string | null | undefined;
145
- /**
146
- * The user focuses on the textfield.
147
- */
148
- focused: boolean;
149
- };
150
-
151
- /**
152
- * A LabeledTextField is an element used to accept a single line of text
153
- * from the user paired with a label, description, and error field elements.
154
- */
155
- class LabeledTextField extends React.Component<PropsWithForwardRef, State> {
156
- static defaultProps: DefaultProps = {
157
- type: "text",
158
- disabled: false,
159
- light: false,
160
- };
161
-
162
- constructor(props: PropsWithForwardRef) {
163
- super(props);
164
- this.state = {
165
- error: null,
166
- focused: false,
167
- };
168
- }
169
-
170
- handleValidate: (errorMessage?: string | null | undefined) => unknown = (
171
- errorMessage,
172
- ) => {
173
- const {onValidate} = this.props;
174
- this.setState({error: errorMessage}, () => {
175
- if (onValidate) {
176
- onValidate(errorMessage);
177
- }
178
- });
179
- };
180
-
181
- handleFocus: (event: React.FocusEvent<HTMLInputElement>) => unknown = (
182
- event,
183
- ) => {
184
- const {onFocus} = this.props;
185
- this.setState({focused: true}, () => {
186
- if (onFocus) {
187
- onFocus(event);
188
- }
189
- });
190
- };
191
-
192
- handleBlur: (event: React.FocusEvent<HTMLInputElement>) => unknown = (
193
- event,
194
- ) => {
195
- const {onBlur} = this.props;
196
- this.setState({focused: false}, () => {
197
- if (onBlur) {
198
- onBlur(event);
199
- }
200
- });
201
- };
202
-
203
- render(): React.ReactNode {
204
- const {
205
- id,
206
- type,
207
- label,
208
- description,
209
- value,
210
- disabled,
211
- required,
212
- validate,
213
- onChange,
214
- onKeyDown,
215
- placeholder,
216
- light,
217
- style,
218
- testId,
219
- readOnly,
220
- autoComplete,
221
- forwardedRef,
222
- ariaDescribedby,
223
- // NOTE: We are not using this prop, but we need to remove it from
224
- // `otherProps` so it doesn't override the `handleValidate` function
225
- // call. We use `otherProps` due to a limitation in TypeScript where
226
- // we can't easily extract the props when using a discriminated
227
- // union.
228
- /* eslint-disable @typescript-eslint/no-unused-vars */
229
- onValidate,
230
- onFocus,
231
- onBlur,
232
- /* eslint-enable @typescript-eslint/no-unused-vars */
233
- // numeric input props
234
- ...otherProps
235
- } = this.props;
236
-
237
- return (
238
- <IDProvider id={id} scope="labeled-text-field">
239
- {(uniqueId) => (
240
- <FieldHeading
241
- id={uniqueId}
242
- testId={testId}
243
- style={style}
244
- light={light}
245
- field={
246
- <TextField
247
- id={`${uniqueId}-field`}
248
- aria-describedby={
249
- ariaDescribedby
250
- ? ariaDescribedby
251
- : `${uniqueId}-error`
252
- }
253
- aria-required={required ? "true" : "false"}
254
- required={required}
255
- testId={testId && `${testId}-field`}
256
- type={type}
257
- value={value}
258
- placeholder={placeholder}
259
- disabled={disabled}
260
- validate={validate}
261
- onValidate={this.handleValidate}
262
- onChange={onChange}
263
- onKeyDown={onKeyDown}
264
- onFocus={this.handleFocus}
265
- onBlur={this.handleBlur}
266
- light={light}
267
- readOnly={readOnly}
268
- autoComplete={autoComplete}
269
- ref={forwardedRef}
270
- {...otherProps}
271
- />
272
- }
273
- label={label}
274
- description={description}
275
- required={!!required}
276
- error={(!this.state.focused && this.state.error) || ""}
277
- />
278
- )}
279
- </IDProvider>
280
- );
281
- }
282
- }
283
-
284
- type ExportProps = OmitConstrained<
285
- JSX.LibraryManagedAttributes<
286
- typeof LabeledTextField,
287
- React.ComponentProps<typeof LabeledTextField>
288
- >,
289
- "forwardedRef"
290
- >;
291
-
292
- /**
293
- * A LabeledTextField is an element used to accept a single line of text
294
- * from the user paired with a label, description, and error field elements.
295
- *
296
- * ### Usage
297
- *
298
- * ```jsx
299
- * import {LabeledTextField} from "@khanacademy/wonder-blocks-form";
300
- *
301
- * const [value, setValue] = React.useState("");
302
- *
303
- * <LabeledTextField
304
- * label="Label"
305
- * description="Hello, this is the description for this field"
306
- * placeholder="Placeholder"
307
- * value={value}
308
- * onChange={setValue}
309
- * />
310
- * ```
311
- */
312
-
313
- export default React.forwardRef<HTMLInputElement, ExportProps>((props, ref) => (
314
- <LabeledTextField {...props} forwardedRef={ref} />
315
- )) as React.ForwardRefExoticComponent<
316
- ExportProps & React.RefAttributes<HTMLInputElement>
317
- >;