@khanacademy/wonder-blocks-form 2.2.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/LICENSE +21 -0
- package/dist/es/index.js +1100 -0
- package/dist/index.js +1419 -0
- package/dist/index.js.flow +2 -0
- package/docs.md +1 -0
- package/package.json +35 -0
- package/src/__tests__/__snapshots__/custom-snapshot.test.js.snap +1349 -0
- package/src/__tests__/__snapshots__/generated-snapshot.test.js.snap +6126 -0
- package/src/__tests__/custom-snapshot.test.js +66 -0
- package/src/__tests__/generated-snapshot.test.js +654 -0
- package/src/components/__tests__/checkbox-group.test.js +84 -0
- package/src/components/__tests__/field-heading.test.js +182 -0
- package/src/components/__tests__/labeled-text-field.test.js +442 -0
- package/src/components/__tests__/radio-group.test.js +84 -0
- package/src/components/__tests__/text-field.test.js +424 -0
- package/src/components/checkbox-core.js +201 -0
- package/src/components/checkbox-group.js +161 -0
- package/src/components/checkbox-group.md +200 -0
- package/src/components/checkbox.js +94 -0
- package/src/components/checkbox.md +134 -0
- package/src/components/choice-internal.js +206 -0
- package/src/components/choice.js +104 -0
- package/src/components/field-heading.js +157 -0
- package/src/components/field-heading.md +43 -0
- package/src/components/group-styles.js +35 -0
- package/src/components/labeled-text-field.js +265 -0
- package/src/components/labeled-text-field.md +535 -0
- package/src/components/labeled-text-field.stories.js +359 -0
- package/src/components/radio-core.js +176 -0
- package/src/components/radio-group.js +142 -0
- package/src/components/radio-group.md +129 -0
- package/src/components/radio.js +93 -0
- package/src/components/radio.md +26 -0
- package/src/components/text-field.js +326 -0
- package/src/components/text-field.md +770 -0
- package/src/components/text-field.stories.js +513 -0
- package/src/index.js +18 -0
- package/src/util/types.js +77 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
|
|
4
|
+
import {IDProvider, type StyleType} from "@khanacademy/wonder-blocks-core";
|
|
5
|
+
import {type Typography} from "@khanacademy/wonder-blocks-typography";
|
|
6
|
+
|
|
7
|
+
import FieldHeading from "./field-heading.js";
|
|
8
|
+
import TextField, {type TextFieldType} from "./text-field.js";
|
|
9
|
+
|
|
10
|
+
type WithForwardRef = {|forwardedRef: React.Ref<"input">|};
|
|
11
|
+
|
|
12
|
+
type Props = {|
|
|
13
|
+
/**
|
|
14
|
+
* An optional unique identifier for the TextField.
|
|
15
|
+
* If no id is specified, a unique id will be auto-generated.
|
|
16
|
+
*/
|
|
17
|
+
id?: string,
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Determines the type of input. Defaults to text.
|
|
21
|
+
*/
|
|
22
|
+
type: TextFieldType,
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Provide a label for the TextField.
|
|
26
|
+
*/
|
|
27
|
+
label: string | React.Element<Typography>,
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Provide a description for the TextField.
|
|
31
|
+
*/
|
|
32
|
+
description?: string | React.Element<Typography>,
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The input value.
|
|
36
|
+
*/
|
|
37
|
+
value: string,
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Makes a read-only input field that cannot be focused. Defaults to false.
|
|
41
|
+
*/
|
|
42
|
+
disabled: boolean,
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Provide a validation for the input value.
|
|
46
|
+
* Return a string error message or null | void for a valid input.
|
|
47
|
+
*/
|
|
48
|
+
validate?: (value: string) => ?string,
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Called when the TextField input is validated.
|
|
52
|
+
*/
|
|
53
|
+
onValidate?: (errorMessage: ?string) => mixed,
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Called when the value has changed.
|
|
57
|
+
*/
|
|
58
|
+
onChange: (newValue: string) => mixed,
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Called when a key is pressed.
|
|
62
|
+
*/
|
|
63
|
+
onKeyDown?: (event: SyntheticKeyboardEvent<HTMLInputElement>) => mixed,
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Called when the element has been focused.
|
|
67
|
+
*/
|
|
68
|
+
onFocus?: (event: SyntheticFocusEvent<HTMLInputElement>) => mixed,
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Called when the element has been blurred.
|
|
72
|
+
*/
|
|
73
|
+
onBlur?: (event: SyntheticFocusEvent<HTMLInputElement>) => mixed,
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Provide hints or examples of what to enter.
|
|
77
|
+
*/
|
|
78
|
+
placeholder?: string,
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Change the field’s sub-components to fit a dark background.
|
|
82
|
+
*/
|
|
83
|
+
light: boolean,
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Custom styles for the container.
|
|
87
|
+
*
|
|
88
|
+
* Note: This style is passed to the field heading container
|
|
89
|
+
* due to scenarios where we would like to set a specific
|
|
90
|
+
* width for the text field. If we apply the style directly
|
|
91
|
+
* to the text field, the container would not be affected.
|
|
92
|
+
* For example, setting text field to "width: 50%" would not
|
|
93
|
+
* affect the container since text field is a child of the container.
|
|
94
|
+
* In this case, the container would maintain its width ocuppying
|
|
95
|
+
* unnecessary space when the text field is smaller.
|
|
96
|
+
*/
|
|
97
|
+
style?: StyleType,
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Optional test ID for e2e testing.
|
|
101
|
+
*/
|
|
102
|
+
testId?: string,
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Specifies if the TextField is read-only.
|
|
106
|
+
*/
|
|
107
|
+
readOnly?: boolean,
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Specifies if the TextField allows autocomplete.
|
|
111
|
+
*/
|
|
112
|
+
autoComplete?: string,
|
|
113
|
+
|};
|
|
114
|
+
|
|
115
|
+
type PropsWithForwardRef = {|
|
|
116
|
+
...Props,
|
|
117
|
+
...WithForwardRef,
|
|
118
|
+
|};
|
|
119
|
+
|
|
120
|
+
type DefaultProps = {|
|
|
121
|
+
type: $PropertyType<PropsWithForwardRef, "type">,
|
|
122
|
+
disabled: $PropertyType<PropsWithForwardRef, "disabled">,
|
|
123
|
+
light: $PropertyType<PropsWithForwardRef, "light">,
|
|
124
|
+
|};
|
|
125
|
+
|
|
126
|
+
type State = {|
|
|
127
|
+
/**
|
|
128
|
+
* Displayed when the validation fails.
|
|
129
|
+
*/
|
|
130
|
+
error: ?string,
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* The user focuses on the textfield.
|
|
134
|
+
*/
|
|
135
|
+
focused: boolean,
|
|
136
|
+
|};
|
|
137
|
+
|
|
138
|
+
// TODO(WB-1081): Change class name back to LabeledTextField after Styleguidist is gone.
|
|
139
|
+
/**
|
|
140
|
+
* A LabeledTextField is an element used to accept a single line of text
|
|
141
|
+
* from the user paired with a label, description, and error field elements.
|
|
142
|
+
*/
|
|
143
|
+
class LabeledTextFieldInternal extends React.Component<
|
|
144
|
+
PropsWithForwardRef,
|
|
145
|
+
State,
|
|
146
|
+
> {
|
|
147
|
+
static defaultProps: DefaultProps = {
|
|
148
|
+
type: "text",
|
|
149
|
+
disabled: false,
|
|
150
|
+
light: false,
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
constructor(props: PropsWithForwardRef) {
|
|
154
|
+
super(props);
|
|
155
|
+
this.state = {
|
|
156
|
+
error: null,
|
|
157
|
+
focused: false,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
handleValidate: (errorMessage: ?string) => mixed = (errorMessage) => {
|
|
162
|
+
const {onValidate} = this.props;
|
|
163
|
+
this.setState({error: errorMessage}, () => {
|
|
164
|
+
if (onValidate) {
|
|
165
|
+
onValidate(errorMessage);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
handleFocus: (event: SyntheticFocusEvent<HTMLInputElement>) => mixed = (
|
|
171
|
+
event,
|
|
172
|
+
) => {
|
|
173
|
+
const {onFocus} = this.props;
|
|
174
|
+
this.setState({focused: true}, () => {
|
|
175
|
+
if (onFocus) {
|
|
176
|
+
onFocus(event);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
handleBlur: (event: SyntheticFocusEvent<HTMLInputElement>) => mixed = (
|
|
182
|
+
event,
|
|
183
|
+
) => {
|
|
184
|
+
const {onBlur} = this.props;
|
|
185
|
+
this.setState({focused: false}, () => {
|
|
186
|
+
if (onBlur) {
|
|
187
|
+
onBlur(event);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
render(): React.Node {
|
|
193
|
+
const {
|
|
194
|
+
id,
|
|
195
|
+
type,
|
|
196
|
+
label,
|
|
197
|
+
description,
|
|
198
|
+
value,
|
|
199
|
+
disabled,
|
|
200
|
+
validate,
|
|
201
|
+
onChange,
|
|
202
|
+
onKeyDown,
|
|
203
|
+
placeholder,
|
|
204
|
+
light,
|
|
205
|
+
style,
|
|
206
|
+
testId,
|
|
207
|
+
readOnly,
|
|
208
|
+
autoComplete,
|
|
209
|
+
forwardedRef,
|
|
210
|
+
} = this.props;
|
|
211
|
+
|
|
212
|
+
return (
|
|
213
|
+
<IDProvider id={id} scope="labeled-text-field">
|
|
214
|
+
{(uniqueId) => (
|
|
215
|
+
<FieldHeading
|
|
216
|
+
id={uniqueId}
|
|
217
|
+
testId={testId}
|
|
218
|
+
style={style}
|
|
219
|
+
field={
|
|
220
|
+
<TextField
|
|
221
|
+
id={`${uniqueId}-field`}
|
|
222
|
+
aria-describedby={`${uniqueId}-error`}
|
|
223
|
+
aria-invalid={
|
|
224
|
+
this.state.error ? "true" : "false"
|
|
225
|
+
}
|
|
226
|
+
testId={testId && `${testId}-field`}
|
|
227
|
+
type={type}
|
|
228
|
+
value={value}
|
|
229
|
+
placeholder={placeholder}
|
|
230
|
+
disabled={disabled}
|
|
231
|
+
validate={validate}
|
|
232
|
+
onValidate={this.handleValidate}
|
|
233
|
+
onChange={onChange}
|
|
234
|
+
onKeyDown={onKeyDown}
|
|
235
|
+
onFocus={this.handleFocus}
|
|
236
|
+
onBlur={this.handleBlur}
|
|
237
|
+
light={light}
|
|
238
|
+
readOnly={readOnly}
|
|
239
|
+
autoComplete={autoComplete}
|
|
240
|
+
ref={forwardedRef}
|
|
241
|
+
/>
|
|
242
|
+
}
|
|
243
|
+
label={label}
|
|
244
|
+
description={description}
|
|
245
|
+
error={(!this.state.focused && this.state.error) || ""}
|
|
246
|
+
/>
|
|
247
|
+
)}
|
|
248
|
+
</IDProvider>
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
type ExportProps = $Diff<
|
|
254
|
+
React.ElementConfig<typeof LabeledTextFieldInternal>,
|
|
255
|
+
WithForwardRef,
|
|
256
|
+
>;
|
|
257
|
+
|
|
258
|
+
const LabeledTextField: React.AbstractComponent<
|
|
259
|
+
ExportProps,
|
|
260
|
+
HTMLInputElement,
|
|
261
|
+
> = React.forwardRef<ExportProps, HTMLInputElement>((props, ref) => (
|
|
262
|
+
<LabeledTextFieldInternal {...props} forwardedRef={ref} />
|
|
263
|
+
));
|
|
264
|
+
|
|
265
|
+
export default LabeledTextField;
|