@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,437 +0,0 @@
1
- import * as React from "react";
2
- import {StyleSheet} from "aphrodite";
3
-
4
- import {IDProvider, addStyle} from "@khanacademy/wonder-blocks-core";
5
- import {border, color, mix, spacing} from "@khanacademy/wonder-blocks-tokens";
6
- import {styles as typographyStyles} from "@khanacademy/wonder-blocks-typography";
7
-
8
- import type {StyleType, AriaProps} from "@khanacademy/wonder-blocks-core";
9
- import {OmitConstrained} from "../util/types";
10
-
11
- export type TextFieldType = "text" | "password" | "email" | "number" | "tel";
12
-
13
- type WithForwardRef = {
14
- forwardedRef: React.ForwardedRef<HTMLInputElement>;
15
- };
16
-
17
- const defaultErrorMessage = "This field is required.";
18
-
19
- const StyledInput = addStyle("input");
20
-
21
- type CommonProps = AriaProps & {
22
- /**
23
- * An optional unique identifier for the TextField.
24
- * If no id is specified, a unique id will be auto-generated.
25
- */
26
- id?: string;
27
- /**
28
- * The input value.
29
- */
30
- value: string;
31
- /**
32
- * The name for the input control. This is submitted along with
33
- * the form data.
34
- */
35
- name?: string;
36
- /**
37
- * Makes a read-only input field that cannot be focused. Defaults to false.
38
- */
39
- disabled: boolean;
40
- /**
41
- * Provide a validation for the input value.
42
- * Return a string error message or null | void for a valid input.
43
- */
44
- validate?: (value: string) => string | null | void;
45
- /**
46
- * Called right after the TextField input is validated.
47
- */
48
- onValidate?: (errorMessage?: string | null | undefined) => unknown;
49
- /**
50
- * Called when the value has changed.
51
- */
52
- onChange: (newValue: string) => unknown;
53
- /**
54
- * Called when a key is pressed.
55
- */
56
- onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => unknown;
57
- /**
58
- * Called when the element has been focused.
59
- */
60
- onFocus?: (event: React.FocusEvent<HTMLInputElement>) => unknown;
61
- /**
62
- * Called when the element has been blurred.
63
- */
64
- onBlur?: (event: React.FocusEvent<HTMLInputElement>) => unknown;
65
- /**
66
- * Provide hints or examples of what to enter.
67
- */
68
- placeholder?: string;
69
- /**
70
- * Whether this field is required to to continue, or the error message to
71
- * render if this field is left blank.
72
- *
73
- * This can be a boolean or a string.
74
- *
75
- * String:
76
- * Please pass in a translated string to use as the error message that will
77
- * render if the user leaves this field blank. If this field is required,
78
- * and a string is not passed in, a default untranslated string will render
79
- * upon error.
80
- * Note: The string will not be used if a `validate` prop is passed in.
81
- *
82
- * Example message: i18n._("A password is required to log in.")
83
- *
84
- * Boolean:
85
- * True/false indicating whether this field is required. Please do not pass
86
- * in `true` if possible - pass in the error string instead.
87
- * If `true` is passed, and a `validate` prop is not passed, that means
88
- * there is no corresponding message and the default untranlsated message
89
- * will be used.
90
- */
91
- required?: boolean | string;
92
- /**
93
- * Change the default focus ring color to fit a dark background.
94
- */
95
- light: boolean;
96
- /**
97
- * Custom styles for the input.
98
- */
99
- style?: StyleType;
100
- /**
101
- * Optional test ID for e2e testing.
102
- */
103
- testId?: string;
104
- /**
105
- * Specifies if the input field is read-only.
106
- */
107
- readOnly?: boolean;
108
- /**
109
- * Whether this field should autofocus on page load.
110
- */
111
- autoFocus?: boolean;
112
- /**
113
- * Specifies if the input field allows autocomplete.
114
- */
115
- autoComplete?: string;
116
- };
117
-
118
- type OtherInputProps = CommonProps & {
119
- type: "text" | "password" | "email" | "tel";
120
- };
121
-
122
- // Props that are only available for inputs of type "number".
123
- export type NumericInputProps = {
124
- type: "number";
125
- /**
126
- * The minimum numeric value for the input.
127
- */
128
- min?: number;
129
- /**
130
- * The maximum numeric value for the input.
131
- */
132
- max?: number;
133
- /**
134
- * The numeric value to increment or decrement by.
135
- * Requires the input to be multiples of this value.
136
- */
137
- step?: number;
138
- };
139
-
140
- type FullNumericInputProps = CommonProps & NumericInputProps;
141
- type Props = OtherInputProps | FullNumericInputProps;
142
- type PropsWithForwardRef = Props & WithForwardRef;
143
-
144
- type DefaultProps = {
145
- type: PropsWithForwardRef["type"];
146
- disabled: PropsWithForwardRef["disabled"];
147
- light: PropsWithForwardRef["light"];
148
- };
149
-
150
- type State = {
151
- /**
152
- * Displayed when the validation fails.
153
- */
154
- error: string | null | undefined;
155
- };
156
-
157
- /**
158
- * A TextField is an element used to accept a single line of text from the user.
159
- */
160
- class TextField extends React.Component<PropsWithForwardRef, State> {
161
- static defaultProps: DefaultProps = {
162
- type: "text",
163
- disabled: false,
164
- light: false,
165
- };
166
-
167
- constructor(props: PropsWithForwardRef) {
168
- super(props);
169
- if (props.validate && props.value !== "") {
170
- // Ensures error is updated on unmounted server-side renders
171
- this.state.error = props.validate(props.value) || null;
172
- }
173
- }
174
-
175
- state: State = {
176
- error: null,
177
- };
178
-
179
- componentDidMount() {
180
- if (this.props.value !== "") {
181
- this.maybeValidate(this.props.value);
182
- }
183
- }
184
-
185
- maybeValidate: (newValue: string) => void = (newValue) => {
186
- const {validate, onValidate, required} = this.props;
187
-
188
- if (validate) {
189
- const maybeError = validate(newValue) || null;
190
- this.setState({error: maybeError}, () => {
191
- if (onValidate) {
192
- onValidate(maybeError);
193
- }
194
- });
195
- } else if (required) {
196
- const requiredString =
197
- typeof required === "string" ? required : defaultErrorMessage;
198
- const maybeError = newValue ? null : requiredString;
199
- this.setState({error: maybeError}, () => {
200
- if (onValidate) {
201
- onValidate(maybeError);
202
- }
203
- });
204
- }
205
- };
206
-
207
- handleChange: (event: React.ChangeEvent<HTMLInputElement>) => unknown = (
208
- event,
209
- ) => {
210
- const {onChange} = this.props;
211
- const newValue = event.target.value;
212
- this.maybeValidate(newValue);
213
- onChange(newValue);
214
- };
215
-
216
- handleFocus: (event: React.FocusEvent<HTMLInputElement>) => unknown = (
217
- event,
218
- ) => {
219
- const {onFocus} = this.props;
220
- if (onFocus) {
221
- onFocus(event);
222
- }
223
- };
224
-
225
- handleBlur: (event: React.FocusEvent<HTMLInputElement>) => unknown = (
226
- event,
227
- ) => {
228
- const {onBlur} = this.props;
229
- if (onBlur) {
230
- onBlur(event);
231
- }
232
- };
233
-
234
- getStyles = (): StyleType => {
235
- const {disabled, light} = this.props;
236
- const {error} = this.state;
237
- // Base styles are the styles that apply regardless of light mode
238
- const baseStyles = [styles.input, typographyStyles.LabelMedium];
239
- const defaultStyles = [
240
- styles.default,
241
- !disabled && styles.defaultFocus,
242
- disabled && styles.disabled,
243
- !!error && styles.error,
244
- ];
245
- const lightStyles = [
246
- styles.light,
247
- !disabled && styles.lightFocus,
248
- disabled && styles.lightDisabled,
249
- !!error && styles.lightError,
250
- ];
251
- return [...baseStyles, ...(light ? lightStyles : defaultStyles)];
252
- };
253
-
254
- render(): React.ReactNode {
255
- const {
256
- id,
257
- type,
258
- value,
259
- name,
260
- disabled,
261
- onKeyDown,
262
- placeholder,
263
- style,
264
- testId,
265
- readOnly,
266
- autoFocus,
267
- autoComplete,
268
- forwardedRef,
269
- // The following props are being included here to avoid
270
- // passing them down to the otherProps spread
271
- /* eslint-disable @typescript-eslint/no-unused-vars */
272
- light,
273
- onFocus,
274
- onBlur,
275
- onValidate,
276
- validate,
277
- onChange,
278
- required,
279
- /* eslint-enable @typescript-eslint/no-unused-vars */
280
- // Should only include Aria related props
281
- ...otherProps
282
- } = this.props;
283
-
284
- return (
285
- <IDProvider id={id} scope="text-field">
286
- {(uniqueId) => (
287
- <StyledInput
288
- style={[this.getStyles(), style]}
289
- id={uniqueId}
290
- type={type}
291
- placeholder={placeholder}
292
- value={value}
293
- name={name}
294
- disabled={disabled}
295
- onChange={this.handleChange}
296
- onKeyDown={onKeyDown}
297
- onFocus={this.handleFocus}
298
- onBlur={this.handleBlur}
299
- data-testid={testId}
300
- readOnly={readOnly}
301
- autoFocus={autoFocus}
302
- autoComplete={autoComplete}
303
- ref={forwardedRef}
304
- {...otherProps}
305
- aria-invalid={this.state.error ? "true" : "false"}
306
- />
307
- )}
308
- </IDProvider>
309
- );
310
- }
311
- }
312
-
313
- const styles = StyleSheet.create({
314
- input: {
315
- width: "100%",
316
- height: 40,
317
- borderRadius: border.radius.medium_4,
318
- boxSizing: "border-box",
319
- paddingLeft: spacing.medium_16,
320
- margin: 0,
321
- },
322
- default: {
323
- background: color.white,
324
- border: `1px solid ${color.offBlack50}`,
325
- color: color.offBlack,
326
- "::placeholder": {
327
- color: color.offBlack64,
328
- },
329
- },
330
- defaultFocus: {
331
- ":focus-visible": {
332
- borderColor: color.blue,
333
- outline: `1px solid ${color.blue}`,
334
- outlineOffset: 0, // Explicitly set outline offset to 0 because Safari sets a default offset
335
- },
336
- },
337
- error: {
338
- background: color.fadedRed8,
339
- border: `1px solid ${color.red}`,
340
- color: color.offBlack,
341
- "::placeholder": {
342
- color: color.offBlack64,
343
- },
344
- ":focus-visible": {
345
- outlineColor: color.red,
346
- borderColor: color.red,
347
- },
348
- },
349
- disabled: {
350
- background: color.offWhite,
351
- border: `1px solid ${color.offBlack16}`,
352
- color: color.offBlack64,
353
- "::placeholder": {
354
- color: color.offBlack64,
355
- },
356
- cursor: "not-allowed",
357
- ":focus-visible": {
358
- outline: "none",
359
- boxShadow: `0 0 0 1px ${color.white}, 0 0 0 3px ${color.offBlack32}`,
360
- },
361
- },
362
- light: {
363
- background: color.white,
364
- border: `1px solid ${color.offBlack16}`,
365
- color: color.offBlack,
366
- "::placeholder": {
367
- color: color.offBlack64,
368
- },
369
- },
370
- lightFocus: {
371
- ":focus-visible": {
372
- outline: `1px solid ${color.blue}`,
373
- outlineOffset: 0, // Explicitly set outline offset to 0 because Safari sets a default offset
374
- borderColor: color.blue,
375
- boxShadow: `0px 0px 0px 2px ${color.blue}, 0px 0px 0px 3px ${color.white}`,
376
- },
377
- },
378
- lightDisabled: {
379
- backgroundColor: "transparent",
380
- border: `1px solid ${color.white32}`,
381
- color: color.white64,
382
- "::placeholder": {
383
- color: color.white64,
384
- },
385
- cursor: "not-allowed",
386
- ":focus-visible": {
387
- borderColor: mix(color.white32, color.blue),
388
- outline: "none",
389
- boxShadow: `0 0 0 1px ${color.offBlack32}, 0 0 0 3px ${color.fadedBlue}`,
390
- },
391
- },
392
- lightError: {
393
- background: color.fadedRed8,
394
- border: `1px solid ${color.red}`,
395
- boxShadow: `0px 0px 0px 1px ${color.red}, 0px 0px 0px 2px ${color.white}`,
396
- color: color.offBlack,
397
- "::placeholder": {
398
- color: color.offBlack64,
399
- },
400
- ":focus-visible": {
401
- outlineColor: color.red,
402
- borderColor: color.red,
403
- boxShadow: `0px 0px 0px 2px ${color.red}, 0px 0px 0px 3px ${color.white}`,
404
- },
405
- },
406
- });
407
-
408
- type ExportProps = OmitConstrained<
409
- JSX.LibraryManagedAttributes<
410
- typeof TextField,
411
- React.ComponentProps<typeof TextField>
412
- >,
413
- "forwardedRef"
414
- >;
415
-
416
- /**
417
- * A TextField is an element used to accept a single line of text from the user.
418
- *
419
- * ### Usage
420
- *
421
- * ```jsx
422
- * import {TextField} from "@khanacademy/wonder-blocks-form";
423
- *
424
- * const [value, setValue] = React.useState("");
425
- *
426
- * <TextField
427
- * id="some-unique-text-field-id"
428
- * value={value}
429
- * onChange={setValue}
430
- * />
431
- * ```
432
- */
433
- export default React.forwardRef<HTMLInputElement, ExportProps>((props, ref) => (
434
- <TextField {...props} forwardedRef={ref} />
435
- )) as React.ForwardRefExoticComponent<
436
- ExportProps & React.RefAttributes<HTMLInputElement>
437
- >;
package/src/index.ts DELETED
@@ -1,17 +0,0 @@
1
- import Checkbox from "./components/checkbox";
2
- import Choice from "./components/choice";
3
- import CheckboxGroup from "./components/checkbox-group";
4
- import RadioGroup from "./components/radio-group";
5
- import TextField from "./components/text-field";
6
- import LabeledTextField from "./components/labeled-text-field";
7
- import TextArea from "./components/text-area";
8
-
9
- export {
10
- Checkbox,
11
- Choice,
12
- CheckboxGroup,
13
- RadioGroup,
14
- TextField,
15
- LabeledTextField,
16
- TextArea,
17
- };
package/src/util/types.ts DELETED
@@ -1,85 +0,0 @@
1
- // NOTE(sophie): Unfortunately, styleguidist does not pull prop definitions
2
- // from imported types. We've duplicated the shared props for each component
3
- // they apply to, so that the prop definitions will show up on the generated
4
- // guide.
5
- import type {AriaProps, StyleType} from "@khanacademy/wonder-blocks-core";
6
-
7
- import Choice from "../components/choice";
8
-
9
- // Checkbox is in indeterminate state when `checked` is `null` | `undefined`
10
- export type Checked = boolean | null | undefined;
11
-
12
- // Shared props for radio-core and checkbox-core
13
- export type ChoiceCoreProps = AriaProps & {
14
- /** Whether this component is checked */
15
- checked: Checked;
16
- /** Whether this component is disabled */
17
- disabled: boolean;
18
- /** Whether this component should show an error state */
19
- error: boolean;
20
- /** Name for the checkbox or radio button group */
21
- groupName?: string;
22
- /** Unique identifier attached to the HTML input element. If used, need to
23
- * guarantee that the ID is unique within everything rendered on a page.
24
- * Used to match <label> with <input> elements for screenreaders. */
25
- id?: string;
26
- /** Optional test ID for e2e testing */
27
- testId?: string;
28
- /** Function that executes when the choice is clicked. */
29
- onClick: () => void;
30
- };
31
-
32
- // Props for checkbox and radio button
33
- export type ChoiceComponentProps = ChoiceCoreProps & {
34
- /** Callback when this component is selected. The newCheckedState is the
35
- * new checked state of the component. */
36
- onChange: (newCheckedState: boolean) => unknown;
37
- /** Optional label for the field. */
38
- label?: string;
39
- /** Optional description for the field. */
40
- description?: string;
41
- /** Ignored because only applicable to Choice components in a group. */
42
- value?: string;
43
- /** Optional styling for the container. Does not style the component. */
44
- style?: StyleType;
45
- };
46
-
47
- export type SharedGroupProps = {
48
- /** Children should be Choice components. */
49
- children: typeof Choice;
50
- /** Group name for this checkbox or radio group. Should be unique for all
51
- * such groups displayed on a page. */
52
- groupName: string;
53
- /** Optional label for the group. This label is optional to allow for
54
- * greater flexibility in implementing checkbox and radio groups. */
55
- label?: string;
56
- /** Optional description for the group. */
57
- description?: string;
58
- /** Optional error message. If supplied, the group will be displayed in an
59
- * error state, along with this error message. If no error state is desired,
60
- * simply do not supply this prop, or pass along null. */
61
- errorMessage?: string;
62
- /** Custom styling for this group of checkboxes. */
63
- style?: StyleType;
64
- };
65
-
66
- export type CheckboxGroupProps = {
67
- /** Callback for when selection of the group has changed. Passes the newly
68
- * selected values. */
69
- onChange: (selectedValues: Array<string>) => unknown;
70
- /** An array of the values of the selected values in this checkbox group. */
71
- selectedValues: Array<string>;
72
- };
73
-
74
- export type RadioGroupProps = {
75
- /** Callback for when the selected value of the radio group has changed. */
76
- onChange: (selectedValue: string) => unknown;
77
- /** Value of the selected radio item. */
78
- selectedValue: string;
79
- };
80
-
81
- // For more information, see:
82
- // https://github.com/microsoft/TypeScript/wiki/FAQ#add-a-key-constraint-to-omit
83
- export type OmitConstrained<T, K> = {
84
- [P in keyof T as Exclude<P, K & keyof any>]: T[P];
85
- };
@@ -1,19 +0,0 @@
1
- {
2
- "exclude": ["dist"],
3
- "extends": "../tsconfig-shared.json",
4
- "compilerOptions": {
5
- "outDir": "./dist",
6
- "rootDir": "src",
7
- },
8
- "references": [
9
- {"path": "../wonder-blocks-button/tsconfig-build.json"},
10
- {"path": "../wonder-blocks-clickable/tsconfig-build.json"},
11
- {"path": "../wonder-blocks-core/tsconfig-build.json"},
12
- {"path": "../wonder-blocks-i18n/tsconfig-build.json"},
13
- {"path": "../wonder-blocks-icon/tsconfig-build.json"},
14
- {"path": "../wonder-blocks-layout/tsconfig-build.json"},
15
- {"path": "../wonder-blocks-link/tsconfig-build.json"},
16
- {"path": "../wonder-blocks-tokens/tsconfig-build.json"},
17
- {"path": "../wonder-blocks-typography/tsconfig-build.json"},
18
- ]
19
- }