@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
package/dist/es/index.js
ADDED
|
@@ -0,0 +1,1100 @@
|
|
|
1
|
+
import _extends from '@babel/runtime/helpers/extends';
|
|
2
|
+
import { Component, createElement, Fragment, Children, cloneElement, forwardRef } from 'react';
|
|
3
|
+
import _objectWithoutPropertiesLoose from '@babel/runtime/helpers/objectWithoutPropertiesLoose';
|
|
4
|
+
import { StyleSheet, css } from 'aphrodite';
|
|
5
|
+
import Color, { mix, fade } from '@khanacademy/wonder-blocks-color';
|
|
6
|
+
import { addStyle, UniqueIDProvider, View, IDProvider } from '@khanacademy/wonder-blocks-core';
|
|
7
|
+
import { getClickableBehavior } from '@khanacademy/wonder-blocks-clickable';
|
|
8
|
+
import { Strut } from '@khanacademy/wonder-blocks-layout';
|
|
9
|
+
import Spacing from '@khanacademy/wonder-blocks-spacing';
|
|
10
|
+
import { LabelMedium, LabelSmall, styles as styles$6 } from '@khanacademy/wonder-blocks-typography';
|
|
11
|
+
import Icon from '@khanacademy/wonder-blocks-icon';
|
|
12
|
+
|
|
13
|
+
const _excluded = ["checked", "disabled", "error", "groupName", "id", "testId", "hovered", "focused", "pressed", "waiting"];
|
|
14
|
+
const {
|
|
15
|
+
blue,
|
|
16
|
+
red,
|
|
17
|
+
white,
|
|
18
|
+
offWhite,
|
|
19
|
+
offBlack16,
|
|
20
|
+
offBlack32,
|
|
21
|
+
offBlack50
|
|
22
|
+
} = Color;
|
|
23
|
+
const StyledInput = addStyle("input");
|
|
24
|
+
const checkboxCheck = {
|
|
25
|
+
small: "M11.263 4.324a1 1 0 1 1 1.474 1.352l-5.5 6a1 1 0 0 1-1.505-.036l-2.5-3a1 1 0 1 1 1.536-1.28L6.536 9.48l4.727-5.157z"
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* The internal stateless ☑️ Checkbox
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
class CheckboxCore extends Component {
|
|
32
|
+
constructor(...args) {
|
|
33
|
+
super(...args);
|
|
34
|
+
|
|
35
|
+
this.handleChange = () => {
|
|
36
|
+
// Empty because change is handled by ClickableBehavior
|
|
37
|
+
return;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
render() {
|
|
42
|
+
const _this$props = this.props,
|
|
43
|
+
{
|
|
44
|
+
checked,
|
|
45
|
+
disabled,
|
|
46
|
+
error,
|
|
47
|
+
groupName,
|
|
48
|
+
id,
|
|
49
|
+
testId,
|
|
50
|
+
hovered,
|
|
51
|
+
focused,
|
|
52
|
+
pressed
|
|
53
|
+
} = _this$props,
|
|
54
|
+
sharedProps = _objectWithoutPropertiesLoose(_this$props, _excluded);
|
|
55
|
+
|
|
56
|
+
const stateStyles = _generateStyles(checked, error);
|
|
57
|
+
|
|
58
|
+
const defaultStyle = [sharedStyles.inputReset, sharedStyles.default, stateStyles.default, !disabled && (pressed ? stateStyles.active : (hovered || focused) && stateStyles.focus), disabled && sharedStyles.disabled];
|
|
59
|
+
const props = {
|
|
60
|
+
"data-test-id": testId
|
|
61
|
+
};
|
|
62
|
+
return /*#__PURE__*/createElement(Fragment, null, /*#__PURE__*/createElement(StyledInput, _extends({}, sharedProps, {
|
|
63
|
+
type: "checkbox",
|
|
64
|
+
"aria-invalid": error,
|
|
65
|
+
checked: checked,
|
|
66
|
+
disabled: disabled,
|
|
67
|
+
id: id,
|
|
68
|
+
name: groupName // Need to specify because this is a controlled React form
|
|
69
|
+
// component, but we handle the click via ClickableBehavior
|
|
70
|
+
,
|
|
71
|
+
onChange: this.handleChange,
|
|
72
|
+
style: defaultStyle
|
|
73
|
+
}, props)), checked && /*#__PURE__*/createElement(Icon, {
|
|
74
|
+
color: disabled ? offBlack32 : white,
|
|
75
|
+
icon: checkboxCheck,
|
|
76
|
+
size: "small",
|
|
77
|
+
style: sharedStyles.checkIcon
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
}
|
|
82
|
+
const size = 16;
|
|
83
|
+
const sharedStyles = StyleSheet.create({
|
|
84
|
+
// Reset the default styled input element
|
|
85
|
+
inputReset: {
|
|
86
|
+
appearance: "none",
|
|
87
|
+
WebkitAppearance: "none",
|
|
88
|
+
MozAppearance: "none"
|
|
89
|
+
},
|
|
90
|
+
default: {
|
|
91
|
+
height: size,
|
|
92
|
+
width: size,
|
|
93
|
+
minHeight: size,
|
|
94
|
+
minWidth: size,
|
|
95
|
+
margin: 0,
|
|
96
|
+
outline: "none",
|
|
97
|
+
boxSizing: "border-box",
|
|
98
|
+
borderStyle: "solid",
|
|
99
|
+
borderWidth: 1,
|
|
100
|
+
borderRadius: 3
|
|
101
|
+
},
|
|
102
|
+
disabled: {
|
|
103
|
+
cursor: "auto",
|
|
104
|
+
backgroundColor: offWhite,
|
|
105
|
+
borderColor: offBlack16,
|
|
106
|
+
borderWidth: 1
|
|
107
|
+
},
|
|
108
|
+
checkIcon: {
|
|
109
|
+
position: "absolute",
|
|
110
|
+
pointerEvents: "none"
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
const fadedBlue = mix(fade(blue, 0.16), white);
|
|
114
|
+
const activeBlue = mix(offBlack32, blue);
|
|
115
|
+
const fadedRed = mix(fade(red, 0.08), white);
|
|
116
|
+
const activeRed = mix(offBlack32, red);
|
|
117
|
+
const colors = {
|
|
118
|
+
default: {
|
|
119
|
+
faded: fadedBlue,
|
|
120
|
+
base: blue,
|
|
121
|
+
active: activeBlue
|
|
122
|
+
},
|
|
123
|
+
error: {
|
|
124
|
+
faded: fadedRed,
|
|
125
|
+
base: red,
|
|
126
|
+
active: activeRed
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
const styles = {};
|
|
130
|
+
|
|
131
|
+
const _generateStyles = (checked, error) => {
|
|
132
|
+
// "hash" the parameters
|
|
133
|
+
const styleKey = `${String(checked)}-${String(error)}`;
|
|
134
|
+
|
|
135
|
+
if (styles[styleKey]) {
|
|
136
|
+
return styles[styleKey];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const palette = error ? colors.error : colors.default;
|
|
140
|
+
let newStyles = {};
|
|
141
|
+
|
|
142
|
+
if (checked) {
|
|
143
|
+
newStyles = {
|
|
144
|
+
default: {
|
|
145
|
+
backgroundColor: palette.base,
|
|
146
|
+
borderWidth: 0
|
|
147
|
+
},
|
|
148
|
+
focus: {
|
|
149
|
+
boxShadow: `0 0 0 1px ${white}, 0 0 0 3px ${palette.base}`
|
|
150
|
+
},
|
|
151
|
+
active: {
|
|
152
|
+
boxShadow: `0 0 0 1px ${white}, 0 0 0 3px ${palette.active}`,
|
|
153
|
+
background: palette.active
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
} else {
|
|
157
|
+
newStyles = {
|
|
158
|
+
default: {
|
|
159
|
+
backgroundColor: error ? fadedRed : white,
|
|
160
|
+
borderColor: error ? red : offBlack50
|
|
161
|
+
},
|
|
162
|
+
focus: {
|
|
163
|
+
backgroundColor: error ? fadedRed : white,
|
|
164
|
+
borderColor: palette.base,
|
|
165
|
+
borderWidth: 2
|
|
166
|
+
},
|
|
167
|
+
active: {
|
|
168
|
+
backgroundColor: palette.faded,
|
|
169
|
+
borderColor: error ? activeRed : blue,
|
|
170
|
+
borderWidth: 2
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
styles[styleKey] = StyleSheet.create(newStyles);
|
|
176
|
+
return styles[styleKey];
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const _excluded$1 = ["checked", "disabled", "error", "groupName", "id", "testId", "hovered", "focused", "pressed", "waiting"];
|
|
180
|
+
const {
|
|
181
|
+
blue: blue$1,
|
|
182
|
+
red: red$1,
|
|
183
|
+
white: white$1,
|
|
184
|
+
offWhite: offWhite$1,
|
|
185
|
+
offBlack16: offBlack16$1,
|
|
186
|
+
offBlack32: offBlack32$1,
|
|
187
|
+
offBlack50: offBlack50$1
|
|
188
|
+
} = Color;
|
|
189
|
+
const StyledInput$1 = addStyle("input");
|
|
190
|
+
/**
|
|
191
|
+
* The internal stateless 🔘 Radio button
|
|
192
|
+
*/
|
|
193
|
+
|
|
194
|
+
class RadioCore extends Component {
|
|
195
|
+
constructor(...args) {
|
|
196
|
+
super(...args);
|
|
197
|
+
|
|
198
|
+
this.handleChange = () => {
|
|
199
|
+
// Empty because change is handled by ClickableBehavior
|
|
200
|
+
return;
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
render() {
|
|
205
|
+
const _this$props = this.props,
|
|
206
|
+
{
|
|
207
|
+
checked,
|
|
208
|
+
disabled,
|
|
209
|
+
error,
|
|
210
|
+
groupName,
|
|
211
|
+
id,
|
|
212
|
+
testId,
|
|
213
|
+
hovered,
|
|
214
|
+
focused,
|
|
215
|
+
pressed
|
|
216
|
+
} = _this$props,
|
|
217
|
+
sharedProps = _objectWithoutPropertiesLoose(_this$props, _excluded$1);
|
|
218
|
+
|
|
219
|
+
const stateStyles = _generateStyles$1(checked, error);
|
|
220
|
+
|
|
221
|
+
const defaultStyle = [sharedStyles$1.inputReset, sharedStyles$1.default, stateStyles.default, !disabled && (pressed ? stateStyles.active : (hovered || focused) && stateStyles.focus), disabled && sharedStyles$1.disabled];
|
|
222
|
+
const props = {
|
|
223
|
+
"data-test-id": testId
|
|
224
|
+
};
|
|
225
|
+
return /*#__PURE__*/createElement(Fragment, null, /*#__PURE__*/createElement(StyledInput$1, _extends({}, sharedProps, {
|
|
226
|
+
type: "radio",
|
|
227
|
+
"aria-invalid": error,
|
|
228
|
+
checked: checked,
|
|
229
|
+
disabled: disabled,
|
|
230
|
+
id: id,
|
|
231
|
+
name: groupName // Need to specify because this is a controlled React form
|
|
232
|
+
// component, but we handle the click via ClickableBehavior
|
|
233
|
+
,
|
|
234
|
+
onChange: this.handleChange,
|
|
235
|
+
style: defaultStyle
|
|
236
|
+
}, props)), disabled && checked && /*#__PURE__*/createElement("span", {
|
|
237
|
+
style: disabledChecked
|
|
238
|
+
}));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
}
|
|
242
|
+
const size$1 = 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
|
|
243
|
+
|
|
244
|
+
const disabledChecked = {
|
|
245
|
+
position: "absolute",
|
|
246
|
+
top: size$1 / 4,
|
|
247
|
+
left: size$1 / 4,
|
|
248
|
+
height: size$1 / 2,
|
|
249
|
+
width: size$1 / 2,
|
|
250
|
+
borderRadius: "50%",
|
|
251
|
+
backgroundColor: offBlack32$1
|
|
252
|
+
};
|
|
253
|
+
const sharedStyles$1 = StyleSheet.create({
|
|
254
|
+
// Reset the default styled input element
|
|
255
|
+
inputReset: {
|
|
256
|
+
appearance: "none",
|
|
257
|
+
WebkitAppearance: "none",
|
|
258
|
+
MozAppearance: "none"
|
|
259
|
+
},
|
|
260
|
+
default: {
|
|
261
|
+
height: size$1,
|
|
262
|
+
width: size$1,
|
|
263
|
+
minHeight: size$1,
|
|
264
|
+
minWidth: size$1,
|
|
265
|
+
margin: 0,
|
|
266
|
+
outline: "none",
|
|
267
|
+
boxSizing: "border-box",
|
|
268
|
+
borderStyle: "solid",
|
|
269
|
+
borderWidth: 1,
|
|
270
|
+
borderRadius: "50%"
|
|
271
|
+
},
|
|
272
|
+
disabled: {
|
|
273
|
+
cursor: "auto",
|
|
274
|
+
backgroundColor: offWhite$1,
|
|
275
|
+
borderColor: offBlack16$1,
|
|
276
|
+
borderWidth: 1
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
const fadedBlue$1 = mix(fade(blue$1, 0.16), white$1);
|
|
280
|
+
const activeBlue$1 = mix(offBlack32$1, blue$1);
|
|
281
|
+
const fadedRed$1 = mix(fade(red$1, 0.08), white$1);
|
|
282
|
+
const activeRed$1 = mix(offBlack32$1, red$1);
|
|
283
|
+
const colors$1 = {
|
|
284
|
+
default: {
|
|
285
|
+
faded: fadedBlue$1,
|
|
286
|
+
base: blue$1,
|
|
287
|
+
active: activeBlue$1
|
|
288
|
+
},
|
|
289
|
+
error: {
|
|
290
|
+
faded: fadedRed$1,
|
|
291
|
+
base: red$1,
|
|
292
|
+
active: activeRed$1
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
const styles$1 = {};
|
|
296
|
+
|
|
297
|
+
const _generateStyles$1 = (checked, error) => {
|
|
298
|
+
// "hash" the parameters
|
|
299
|
+
const styleKey = `${String(checked)}-${String(error)}`;
|
|
300
|
+
|
|
301
|
+
if (styles$1[styleKey]) {
|
|
302
|
+
return styles$1[styleKey];
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const palette = error ? colors$1.error : colors$1.default;
|
|
306
|
+
let newStyles = {};
|
|
307
|
+
|
|
308
|
+
if (checked) {
|
|
309
|
+
newStyles = {
|
|
310
|
+
default: {
|
|
311
|
+
backgroundColor: white$1,
|
|
312
|
+
borderColor: palette.base,
|
|
313
|
+
borderWidth: size$1 / 4
|
|
314
|
+
},
|
|
315
|
+
focus: {
|
|
316
|
+
boxShadow: `0 0 0 1px ${white$1}, 0 0 0 3px ${palette.base}`
|
|
317
|
+
},
|
|
318
|
+
active: {
|
|
319
|
+
boxShadow: `0 0 0 1px ${white$1}, 0 0 0 3px ${palette.active}`,
|
|
320
|
+
borderColor: palette.active
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
} else {
|
|
324
|
+
newStyles = {
|
|
325
|
+
default: {
|
|
326
|
+
backgroundColor: error ? fadedRed$1 : white$1,
|
|
327
|
+
borderColor: error ? red$1 : offBlack50$1
|
|
328
|
+
},
|
|
329
|
+
focus: {
|
|
330
|
+
backgroundColor: error ? fadedRed$1 : white$1,
|
|
331
|
+
borderColor: palette.base,
|
|
332
|
+
borderWidth: 2
|
|
333
|
+
},
|
|
334
|
+
active: {
|
|
335
|
+
backgroundColor: palette.faded,
|
|
336
|
+
borderColor: error ? activeRed$1 : blue$1,
|
|
337
|
+
borderWidth: 2
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
styles$1[styleKey] = StyleSheet.create(newStyles);
|
|
343
|
+
return styles$1[styleKey];
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
const _excluded$2 = ["label", "description", "onChange", "style", "className", "variant"];
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* This is a potentially labeled 🔘 or ☑️ item. This is an internal component
|
|
350
|
+
* that's wrapped by Checkbox and Radio. Choice is a wrapper for Checkbox and
|
|
351
|
+
* Radio with many of its props auto-populated, to be used with CheckboxGroup
|
|
352
|
+
* and RadioGroup. This design allows for more explicit prop typing. For
|
|
353
|
+
* example, we can make onChange a required prop on Checkbox but not on Choice
|
|
354
|
+
* (because for Choice, that prop would be auto-populated by CheckboxGroup).
|
|
355
|
+
*/
|
|
356
|
+
class ChoiceInternal extends Component {
|
|
357
|
+
constructor(...args) {
|
|
358
|
+
super(...args);
|
|
359
|
+
|
|
360
|
+
this.handleLabelClick = event => {
|
|
361
|
+
// Browsers automatically use the for attribute to select the input,
|
|
362
|
+
// but we use ClickableBehavior to handle this.
|
|
363
|
+
event.preventDefault();
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
this.handleClick = () => {
|
|
367
|
+
const {
|
|
368
|
+
checked,
|
|
369
|
+
onChange,
|
|
370
|
+
variant
|
|
371
|
+
} = this.props; // Radio buttons cannot be unchecked
|
|
372
|
+
|
|
373
|
+
if (variant === "radio" && checked) {
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
onChange(!checked);
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
getChoiceCoreComponent() {
|
|
382
|
+
if (this.props.variant === "radio") {
|
|
383
|
+
return RadioCore;
|
|
384
|
+
} else {
|
|
385
|
+
return CheckboxCore;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
getLabel() {
|
|
390
|
+
const {
|
|
391
|
+
disabled,
|
|
392
|
+
id,
|
|
393
|
+
label
|
|
394
|
+
} = this.props;
|
|
395
|
+
return /*#__PURE__*/createElement(LabelMedium, {
|
|
396
|
+
style: [styles$2.label, disabled && styles$2.disabledLabel]
|
|
397
|
+
}, /*#__PURE__*/createElement("label", {
|
|
398
|
+
htmlFor: id,
|
|
399
|
+
onClick: this.handleLabelClick
|
|
400
|
+
}, label));
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
getDescription(id) {
|
|
404
|
+
const {
|
|
405
|
+
description
|
|
406
|
+
} = this.props;
|
|
407
|
+
return /*#__PURE__*/createElement(LabelSmall, {
|
|
408
|
+
style: styles$2.description,
|
|
409
|
+
id: id
|
|
410
|
+
}, description);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
render() {
|
|
414
|
+
const _this$props = this.props,
|
|
415
|
+
{
|
|
416
|
+
label,
|
|
417
|
+
description,
|
|
418
|
+
style,
|
|
419
|
+
className,
|
|
420
|
+
variant
|
|
421
|
+
} = _this$props,
|
|
422
|
+
coreProps = _objectWithoutPropertiesLoose(_this$props, _excluded$2);
|
|
423
|
+
|
|
424
|
+
const ChoiceCore = this.getChoiceCoreComponent();
|
|
425
|
+
const ClickableBehavior = getClickableBehavior();
|
|
426
|
+
return /*#__PURE__*/createElement(UniqueIDProvider, {
|
|
427
|
+
mockOnFirstRender: true,
|
|
428
|
+
scope: "choice"
|
|
429
|
+
}, ids => {
|
|
430
|
+
const descriptionId = description && ids.get("description");
|
|
431
|
+
return /*#__PURE__*/createElement(View, {
|
|
432
|
+
style: style,
|
|
433
|
+
className: className
|
|
434
|
+
}, /*#__PURE__*/createElement(ClickableBehavior, {
|
|
435
|
+
disabled: coreProps.disabled,
|
|
436
|
+
onClick: this.handleClick,
|
|
437
|
+
role: variant
|
|
438
|
+
}, (state, childrenProps) => {
|
|
439
|
+
return /*#__PURE__*/createElement(View, _extends({
|
|
440
|
+
style: styles$2.wrapper
|
|
441
|
+
}, childrenProps, {
|
|
442
|
+
// We are resetting the tabIndex=0 from handlers
|
|
443
|
+
// because the ChoiceCore component will receive
|
|
444
|
+
// focus on basis of it being an input element.
|
|
445
|
+
tabIndex: -1
|
|
446
|
+
}), /*#__PURE__*/createElement(ChoiceCore, _extends({}, coreProps, state, {
|
|
447
|
+
"aria-describedby": descriptionId
|
|
448
|
+
})), /*#__PURE__*/createElement(Strut, {
|
|
449
|
+
size: Spacing.xSmall_8
|
|
450
|
+
}), label && this.getLabel());
|
|
451
|
+
}), description && this.getDescription(descriptionId));
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
}
|
|
456
|
+
ChoiceInternal.defaultProps = {
|
|
457
|
+
checked: false,
|
|
458
|
+
disabled: false,
|
|
459
|
+
error: false
|
|
460
|
+
};
|
|
461
|
+
const styles$2 = StyleSheet.create({
|
|
462
|
+
wrapper: {
|
|
463
|
+
flexDirection: "row",
|
|
464
|
+
alignItems: "flex-start",
|
|
465
|
+
outline: "none"
|
|
466
|
+
},
|
|
467
|
+
label: {
|
|
468
|
+
userSelect: "none",
|
|
469
|
+
// NOTE: The checkbox/radio button (height 16px) should be center
|
|
470
|
+
// aligned with the first line of the label. However, LabelMedium has a
|
|
471
|
+
// declared line height of 20px, so we need to adjust the top to get the
|
|
472
|
+
// desired alignment.
|
|
473
|
+
marginTop: -2
|
|
474
|
+
},
|
|
475
|
+
disabledLabel: {
|
|
476
|
+
color: Color.offBlack32
|
|
477
|
+
},
|
|
478
|
+
description: {
|
|
479
|
+
// 16 for icon + 8 for spacing strut
|
|
480
|
+
marginLeft: Spacing.medium_16 + Spacing.xSmall_8,
|
|
481
|
+
marginTop: Spacing.xxxSmall_4,
|
|
482
|
+
color: Color.offBlack64
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* ☑️ A nicely styled checkbox for all your checking needs. Can optionally take
|
|
488
|
+
* label and description props.
|
|
489
|
+
*
|
|
490
|
+
* If you want a whole group of Checkbox[es] that are related, see the Choice
|
|
491
|
+
* and CheckboxGroup components.
|
|
492
|
+
*/
|
|
493
|
+
class Checkbox extends Component {
|
|
494
|
+
render() {
|
|
495
|
+
return /*#__PURE__*/createElement(ChoiceInternal, _extends({
|
|
496
|
+
variant: "checkbox"
|
|
497
|
+
}, this.props));
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
}
|
|
501
|
+
Checkbox.defaultProps = {
|
|
502
|
+
disabled: false,
|
|
503
|
+
error: false
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* 🔘 A nicely styled radio button for all your non-AMFM radio button needs. Can
|
|
508
|
+
* optionally take label and description props.
|
|
509
|
+
*
|
|
510
|
+
* This component should not really be used by itself because radio buttons are
|
|
511
|
+
* often grouped together. See RadioGroup.
|
|
512
|
+
*/
|
|
513
|
+
class Radio extends Component {
|
|
514
|
+
render() {
|
|
515
|
+
return /*#__PURE__*/createElement(ChoiceInternal, _extends({
|
|
516
|
+
variant: "radio"
|
|
517
|
+
}, this.props));
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
}
|
|
521
|
+
Radio.defaultProps = {
|
|
522
|
+
disabled: false,
|
|
523
|
+
error: false
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
const _excluded$3 = ["value", "variant"];
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* This is a labeled 🔘 or ☑️ item. Choice is meant to be used as children of
|
|
530
|
+
* CheckboxGroup and RadioGroup because many of its props are auto-populated
|
|
531
|
+
* and not shown in the documentation here. See those components for usage
|
|
532
|
+
* examples.
|
|
533
|
+
*
|
|
534
|
+
* If you wish to use just a single field, use Checkbox or Radio with the
|
|
535
|
+
* optional label and description props.
|
|
536
|
+
*/
|
|
537
|
+
class Choice extends Component {
|
|
538
|
+
getChoiceComponent(variant) {
|
|
539
|
+
if (variant === "checkbox") {
|
|
540
|
+
return Checkbox;
|
|
541
|
+
} else {
|
|
542
|
+
return Radio;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
render() {
|
|
547
|
+
// we don't need this going into the ChoiceComponent
|
|
548
|
+
// eslint-disable-next-line no-unused-vars
|
|
549
|
+
const _this$props = this.props,
|
|
550
|
+
{
|
|
551
|
+
variant
|
|
552
|
+
} = _this$props,
|
|
553
|
+
remainingProps = _objectWithoutPropertiesLoose(_this$props, _excluded$3);
|
|
554
|
+
|
|
555
|
+
const ChoiceComponent = this.getChoiceComponent(variant);
|
|
556
|
+
return /*#__PURE__*/createElement(ChoiceComponent, remainingProps);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
}
|
|
560
|
+
Choice.defaultProps = {
|
|
561
|
+
checked: false,
|
|
562
|
+
disabled: false,
|
|
563
|
+
onChange: () => {}
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
const styles$3 = StyleSheet.create({
|
|
567
|
+
fieldset: {
|
|
568
|
+
border: "none",
|
|
569
|
+
padding: 0,
|
|
570
|
+
margin: 0
|
|
571
|
+
},
|
|
572
|
+
legend: {
|
|
573
|
+
padding: 0
|
|
574
|
+
},
|
|
575
|
+
description: {
|
|
576
|
+
marginTop: Spacing.xxxSmall_4,
|
|
577
|
+
color: Color.offBlack64
|
|
578
|
+
},
|
|
579
|
+
error: {
|
|
580
|
+
marginTop: Spacing.xxxSmall_4,
|
|
581
|
+
color: Color.red
|
|
582
|
+
},
|
|
583
|
+
defaultLineGap: {
|
|
584
|
+
marginTop: Spacing.xSmall_8
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
const StyledFieldset = addStyle("fieldset");
|
|
589
|
+
const StyledLegend = addStyle("legend");
|
|
590
|
+
/**
|
|
591
|
+
* A checkbox group allows multiple selection. This component auto-populates
|
|
592
|
+
* many props for its children Choice components. The Choice component is
|
|
593
|
+
* exposed for the user to apply custom styles or to indicate which choices are
|
|
594
|
+
* disabled.
|
|
595
|
+
*/
|
|
596
|
+
|
|
597
|
+
class CheckboxGroup extends Component {
|
|
598
|
+
handleChange(changedValue, originalCheckedState) {
|
|
599
|
+
const {
|
|
600
|
+
onChange,
|
|
601
|
+
selectedValues
|
|
602
|
+
} = this.props;
|
|
603
|
+
|
|
604
|
+
if (originalCheckedState) {
|
|
605
|
+
const index = selectedValues.indexOf(changedValue);
|
|
606
|
+
const updatedSelection = [].concat(selectedValues.slice(0, index), selectedValues.slice(index + 1));
|
|
607
|
+
onChange(updatedSelection);
|
|
608
|
+
} else {
|
|
609
|
+
onChange([].concat(selectedValues, [changedValue]));
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
render() {
|
|
614
|
+
const {
|
|
615
|
+
children,
|
|
616
|
+
label,
|
|
617
|
+
description,
|
|
618
|
+
errorMessage,
|
|
619
|
+
groupName,
|
|
620
|
+
selectedValues,
|
|
621
|
+
style,
|
|
622
|
+
testId
|
|
623
|
+
} = this.props;
|
|
624
|
+
return /*#__PURE__*/createElement(StyledFieldset, {
|
|
625
|
+
"data-test-id": testId,
|
|
626
|
+
style: styles$3.fieldset
|
|
627
|
+
}, /*#__PURE__*/createElement(View, {
|
|
628
|
+
style: style
|
|
629
|
+
}, typeof label === "string" ? /*#__PURE__*/createElement(StyledLegend, {
|
|
630
|
+
style: styles$3.legend
|
|
631
|
+
}, /*#__PURE__*/createElement(LabelMedium, null, label)) : label && label, typeof description === "string" ? /*#__PURE__*/createElement(LabelSmall, {
|
|
632
|
+
style: styles$3.description
|
|
633
|
+
}, description) : description && description, errorMessage && /*#__PURE__*/createElement(LabelSmall, {
|
|
634
|
+
style: styles$3.error
|
|
635
|
+
}, errorMessage), (label || description || errorMessage) && /*#__PURE__*/createElement(Strut, {
|
|
636
|
+
size: Spacing.small_12
|
|
637
|
+
}), Children.map(children, (child, index) => {
|
|
638
|
+
const {
|
|
639
|
+
style,
|
|
640
|
+
value
|
|
641
|
+
} = child.props;
|
|
642
|
+
const checked = selectedValues.includes(value);
|
|
643
|
+
return /*#__PURE__*/createElement(Fragment, null, /*#__PURE__*/cloneElement(child, {
|
|
644
|
+
checked: checked,
|
|
645
|
+
error: !!errorMessage,
|
|
646
|
+
groupName: groupName,
|
|
647
|
+
id: `${groupName}-${value}`,
|
|
648
|
+
key: value,
|
|
649
|
+
onChange: () => this.handleChange(value, checked),
|
|
650
|
+
style: [index > 0 && styles$3.defaultLineGap, style],
|
|
651
|
+
variant: "checkbox"
|
|
652
|
+
}));
|
|
653
|
+
})));
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
const StyledFieldset$1 = addStyle("fieldset");
|
|
659
|
+
const StyledLegend$1 = addStyle("legend");
|
|
660
|
+
/**
|
|
661
|
+
* A radio group allows only single selection. Like CheckboxGroup, this
|
|
662
|
+
* component auto-populates many props for its children Choice components. The
|
|
663
|
+
* Choice component is exposed for the user to apply custom styles or to
|
|
664
|
+
* indicate which choices are disabled. The use of the groupName prop is
|
|
665
|
+
* important to maintain expected keyboard navigation behavior for
|
|
666
|
+
* accessibility.
|
|
667
|
+
*/
|
|
668
|
+
|
|
669
|
+
class RadioGroup extends Component {
|
|
670
|
+
handleChange(changedValue) {
|
|
671
|
+
this.props.onChange(changedValue);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
render() {
|
|
675
|
+
const {
|
|
676
|
+
children,
|
|
677
|
+
label,
|
|
678
|
+
description,
|
|
679
|
+
errorMessage,
|
|
680
|
+
groupName,
|
|
681
|
+
selectedValue,
|
|
682
|
+
style,
|
|
683
|
+
testId
|
|
684
|
+
} = this.props;
|
|
685
|
+
return /*#__PURE__*/createElement(StyledFieldset$1, {
|
|
686
|
+
"data-test-id": testId,
|
|
687
|
+
style: styles$3.fieldset
|
|
688
|
+
}, /*#__PURE__*/createElement(View, {
|
|
689
|
+
style: style
|
|
690
|
+
}, label && /*#__PURE__*/createElement(StyledLegend$1, {
|
|
691
|
+
style: styles$3.legend
|
|
692
|
+
}, /*#__PURE__*/createElement(LabelMedium, null, label)), description && /*#__PURE__*/createElement(LabelSmall, {
|
|
693
|
+
style: styles$3.description
|
|
694
|
+
}, description), errorMessage && /*#__PURE__*/createElement(LabelSmall, {
|
|
695
|
+
style: styles$3.error
|
|
696
|
+
}, errorMessage), (label || description || errorMessage) && /*#__PURE__*/createElement(Strut, {
|
|
697
|
+
size: Spacing.small_12
|
|
698
|
+
}), Children.map(children, (child, index) => {
|
|
699
|
+
const {
|
|
700
|
+
style,
|
|
701
|
+
value
|
|
702
|
+
} = child.props;
|
|
703
|
+
const checked = selectedValue === value;
|
|
704
|
+
return /*#__PURE__*/createElement(Fragment, null, /*#__PURE__*/cloneElement(child, {
|
|
705
|
+
checked: checked,
|
|
706
|
+
error: !!errorMessage,
|
|
707
|
+
groupName: groupName,
|
|
708
|
+
id: `${groupName}-${value}`,
|
|
709
|
+
key: value,
|
|
710
|
+
onChange: () => this.handleChange(value),
|
|
711
|
+
style: [index > 0 && styles$3.defaultLineGap, style],
|
|
712
|
+
variant: "radio"
|
|
713
|
+
}));
|
|
714
|
+
})));
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
const _excluded$4 = ["id", "type", "value", "disabled", "onKeyDown", "placeholder", "required", "light", "style", "testId", "readOnly", "autoComplete", "forwardedRef", "onFocus", "onBlur", "onValidate", "validate", "onChange"];
|
|
720
|
+
|
|
721
|
+
// TODO(WB-1081): Change class name back to TextField after Styleguidist is gone.
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* A TextField is an element used to accept a single line of text from the user.
|
|
725
|
+
*/
|
|
726
|
+
class TextFieldInternal extends Component {
|
|
727
|
+
constructor(props) {
|
|
728
|
+
super(props);
|
|
729
|
+
this.state = {
|
|
730
|
+
error: null,
|
|
731
|
+
focused: false
|
|
732
|
+
};
|
|
733
|
+
|
|
734
|
+
this.maybeValidate = newValue => {
|
|
735
|
+
const {
|
|
736
|
+
validate,
|
|
737
|
+
onValidate
|
|
738
|
+
} = this.props;
|
|
739
|
+
|
|
740
|
+
if (validate) {
|
|
741
|
+
const maybeError = validate(newValue) || null;
|
|
742
|
+
this.setState({
|
|
743
|
+
error: maybeError
|
|
744
|
+
}, () => {
|
|
745
|
+
if (onValidate) {
|
|
746
|
+
onValidate(maybeError);
|
|
747
|
+
}
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
};
|
|
751
|
+
|
|
752
|
+
this.handleChange = event => {
|
|
753
|
+
const {
|
|
754
|
+
onChange
|
|
755
|
+
} = this.props;
|
|
756
|
+
const newValue = event.target.value;
|
|
757
|
+
this.maybeValidate(newValue);
|
|
758
|
+
onChange(newValue);
|
|
759
|
+
};
|
|
760
|
+
|
|
761
|
+
this.handleFocus = event => {
|
|
762
|
+
const {
|
|
763
|
+
onFocus
|
|
764
|
+
} = this.props;
|
|
765
|
+
this.setState({
|
|
766
|
+
focused: true
|
|
767
|
+
}, () => {
|
|
768
|
+
if (onFocus) {
|
|
769
|
+
onFocus(event);
|
|
770
|
+
}
|
|
771
|
+
});
|
|
772
|
+
};
|
|
773
|
+
|
|
774
|
+
this.handleBlur = event => {
|
|
775
|
+
const {
|
|
776
|
+
onBlur
|
|
777
|
+
} = this.props;
|
|
778
|
+
this.setState({
|
|
779
|
+
focused: false
|
|
780
|
+
}, () => {
|
|
781
|
+
if (onBlur) {
|
|
782
|
+
onBlur(event);
|
|
783
|
+
}
|
|
784
|
+
});
|
|
785
|
+
};
|
|
786
|
+
|
|
787
|
+
if (props.validate) {
|
|
788
|
+
// Ensures error is updated on unmounted server-side renders
|
|
789
|
+
this.state.error = props.validate(props.value) || null;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
componentDidMount() {
|
|
794
|
+
this.maybeValidate(this.props.value);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
render() {
|
|
798
|
+
const _this$props = this.props,
|
|
799
|
+
{
|
|
800
|
+
id,
|
|
801
|
+
type,
|
|
802
|
+
value,
|
|
803
|
+
disabled,
|
|
804
|
+
onKeyDown,
|
|
805
|
+
placeholder,
|
|
806
|
+
required,
|
|
807
|
+
light,
|
|
808
|
+
style,
|
|
809
|
+
testId,
|
|
810
|
+
readOnly,
|
|
811
|
+
autoComplete,
|
|
812
|
+
forwardedRef
|
|
813
|
+
} = _this$props,
|
|
814
|
+
otherProps = _objectWithoutPropertiesLoose(_this$props, _excluded$4);
|
|
815
|
+
|
|
816
|
+
return /*#__PURE__*/createElement("input", _extends({
|
|
817
|
+
className: css([styles$4.input, styles$6.LabelMedium, styles$4.default, // Prioritizes disabled, then focused, then error (if any)
|
|
818
|
+
disabled ? styles$4.disabled : this.state.focused ? [styles$4.focused, light && styles$4.defaultLight] : this.state.error && [styles$4.error, light && styles$4.errorLight], style && style]),
|
|
819
|
+
id: id,
|
|
820
|
+
type: type,
|
|
821
|
+
placeholder: placeholder,
|
|
822
|
+
value: value,
|
|
823
|
+
disabled: disabled,
|
|
824
|
+
onChange: this.handleChange,
|
|
825
|
+
onKeyDown: onKeyDown,
|
|
826
|
+
onFocus: this.handleFocus,
|
|
827
|
+
onBlur: this.handleBlur,
|
|
828
|
+
required: required,
|
|
829
|
+
"data-test-id": testId,
|
|
830
|
+
readOnly: readOnly,
|
|
831
|
+
autoComplete: autoComplete,
|
|
832
|
+
ref: forwardedRef
|
|
833
|
+
}, otherProps));
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
TextFieldInternal.defaultProps = {
|
|
839
|
+
type: "text",
|
|
840
|
+
disabled: false,
|
|
841
|
+
light: false
|
|
842
|
+
};
|
|
843
|
+
const styles$4 = StyleSheet.create({
|
|
844
|
+
input: {
|
|
845
|
+
width: "100%",
|
|
846
|
+
height: 40,
|
|
847
|
+
borderRadius: 4,
|
|
848
|
+
boxSizing: "border-box",
|
|
849
|
+
paddingLeft: Spacing.medium_16,
|
|
850
|
+
margin: 0,
|
|
851
|
+
outline: "none",
|
|
852
|
+
boxShadow: "none"
|
|
853
|
+
},
|
|
854
|
+
default: {
|
|
855
|
+
background: Color.white,
|
|
856
|
+
border: `1px solid ${Color.offBlack16}`,
|
|
857
|
+
color: Color.offBlack,
|
|
858
|
+
"::placeholder": {
|
|
859
|
+
color: Color.offBlack64
|
|
860
|
+
}
|
|
861
|
+
},
|
|
862
|
+
error: {
|
|
863
|
+
background: `${mix(fade(Color.red, 0.06), Color.white)}`,
|
|
864
|
+
border: `1px solid ${Color.red}`,
|
|
865
|
+
color: Color.offBlack,
|
|
866
|
+
"::placeholder": {
|
|
867
|
+
color: Color.offBlack64
|
|
868
|
+
}
|
|
869
|
+
},
|
|
870
|
+
disabled: {
|
|
871
|
+
background: Color.offWhite,
|
|
872
|
+
border: `1px solid ${Color.offBlack16}`,
|
|
873
|
+
color: Color.offBlack64,
|
|
874
|
+
"::placeholder": {
|
|
875
|
+
color: Color.offBlack32
|
|
876
|
+
}
|
|
877
|
+
},
|
|
878
|
+
focused: {
|
|
879
|
+
background: Color.white,
|
|
880
|
+
border: `1px solid ${Color.blue}`,
|
|
881
|
+
color: Color.offBlack,
|
|
882
|
+
"::placeholder": {
|
|
883
|
+
color: Color.offBlack64
|
|
884
|
+
}
|
|
885
|
+
},
|
|
886
|
+
defaultLight: {
|
|
887
|
+
boxShadow: `0px 0px 0px 1px ${Color.blue}, 0px 0px 0px 2px ${Color.white}`
|
|
888
|
+
},
|
|
889
|
+
errorLight: {
|
|
890
|
+
boxShadow: `0px 0px 0px 1px ${Color.red}, 0px 0px 0px 2px ${Color.white}`
|
|
891
|
+
}
|
|
892
|
+
});
|
|
893
|
+
const TextField = /*#__PURE__*/forwardRef((props, ref) => /*#__PURE__*/createElement(TextFieldInternal, _extends({}, props, {
|
|
894
|
+
forwardedRef: ref
|
|
895
|
+
})));
|
|
896
|
+
|
|
897
|
+
/**
|
|
898
|
+
* A FieldHeading is an element that provides a label, description, and error element
|
|
899
|
+
* to present better context and hints to any type of form field component.
|
|
900
|
+
*/
|
|
901
|
+
class FieldHeading extends Component {
|
|
902
|
+
renderLabel() {
|
|
903
|
+
const {
|
|
904
|
+
label,
|
|
905
|
+
id,
|
|
906
|
+
testId
|
|
907
|
+
} = this.props;
|
|
908
|
+
return /*#__PURE__*/createElement(Fragment, null, typeof label === "string" ? /*#__PURE__*/createElement(LabelMedium, {
|
|
909
|
+
style: styles$5.label,
|
|
910
|
+
tag: "label",
|
|
911
|
+
htmlFor: id && `${id}-field`,
|
|
912
|
+
testId: testId && `${testId}-label`
|
|
913
|
+
}, label) : label, /*#__PURE__*/createElement(Strut, {
|
|
914
|
+
size: Spacing.xxxSmall_4
|
|
915
|
+
}));
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
maybeRenderDescription() {
|
|
919
|
+
const {
|
|
920
|
+
description,
|
|
921
|
+
testId
|
|
922
|
+
} = this.props;
|
|
923
|
+
|
|
924
|
+
if (!description) {
|
|
925
|
+
return null;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
return /*#__PURE__*/createElement(Fragment, null, typeof description === "string" ? /*#__PURE__*/createElement(LabelSmall, {
|
|
929
|
+
style: styles$5.description,
|
|
930
|
+
testId: testId && `${testId}-description`
|
|
931
|
+
}, description) : description, /*#__PURE__*/createElement(Strut, {
|
|
932
|
+
size: Spacing.xxxSmall_4
|
|
933
|
+
}));
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
maybeRenderError() {
|
|
937
|
+
const {
|
|
938
|
+
error,
|
|
939
|
+
id,
|
|
940
|
+
testId
|
|
941
|
+
} = this.props;
|
|
942
|
+
|
|
943
|
+
if (!error) {
|
|
944
|
+
return null;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
return /*#__PURE__*/createElement(Fragment, null, /*#__PURE__*/createElement(Strut, {
|
|
948
|
+
size: Spacing.small_12
|
|
949
|
+
}), typeof error === "string" ? /*#__PURE__*/createElement(LabelSmall, {
|
|
950
|
+
style: styles$5.error,
|
|
951
|
+
role: "alert",
|
|
952
|
+
id: id && `${id}-error`,
|
|
953
|
+
testId: testId && `${testId}-error`
|
|
954
|
+
}, error) : error);
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
render() {
|
|
958
|
+
const {
|
|
959
|
+
field,
|
|
960
|
+
style
|
|
961
|
+
} = this.props;
|
|
962
|
+
return /*#__PURE__*/createElement(View, {
|
|
963
|
+
style: style
|
|
964
|
+
}, this.renderLabel(), this.maybeRenderDescription(), /*#__PURE__*/createElement(Strut, {
|
|
965
|
+
size: Spacing.xSmall_8
|
|
966
|
+
}), field, this.maybeRenderError());
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
}
|
|
970
|
+
const styles$5 = StyleSheet.create({
|
|
971
|
+
label: {
|
|
972
|
+
color: Color.offBlack
|
|
973
|
+
},
|
|
974
|
+
description: {
|
|
975
|
+
color: Color.offBlack64
|
|
976
|
+
},
|
|
977
|
+
error: {
|
|
978
|
+
color: Color.red
|
|
979
|
+
}
|
|
980
|
+
});
|
|
981
|
+
|
|
982
|
+
// TODO(WB-1081): Change class name back to LabeledTextField after Styleguidist is gone.
|
|
983
|
+
|
|
984
|
+
/**
|
|
985
|
+
* A LabeledTextField is an element used to accept a single line of text
|
|
986
|
+
* from the user paired with a label, description, and error field elements.
|
|
987
|
+
*/
|
|
988
|
+
class LabeledTextFieldInternal extends Component {
|
|
989
|
+
constructor(props) {
|
|
990
|
+
super(props);
|
|
991
|
+
|
|
992
|
+
this.handleValidate = errorMessage => {
|
|
993
|
+
const {
|
|
994
|
+
onValidate
|
|
995
|
+
} = this.props;
|
|
996
|
+
this.setState({
|
|
997
|
+
error: errorMessage
|
|
998
|
+
}, () => {
|
|
999
|
+
if (onValidate) {
|
|
1000
|
+
onValidate(errorMessage);
|
|
1001
|
+
}
|
|
1002
|
+
});
|
|
1003
|
+
};
|
|
1004
|
+
|
|
1005
|
+
this.handleFocus = event => {
|
|
1006
|
+
const {
|
|
1007
|
+
onFocus
|
|
1008
|
+
} = this.props;
|
|
1009
|
+
this.setState({
|
|
1010
|
+
focused: true
|
|
1011
|
+
}, () => {
|
|
1012
|
+
if (onFocus) {
|
|
1013
|
+
onFocus(event);
|
|
1014
|
+
}
|
|
1015
|
+
});
|
|
1016
|
+
};
|
|
1017
|
+
|
|
1018
|
+
this.handleBlur = event => {
|
|
1019
|
+
const {
|
|
1020
|
+
onBlur
|
|
1021
|
+
} = this.props;
|
|
1022
|
+
this.setState({
|
|
1023
|
+
focused: false
|
|
1024
|
+
}, () => {
|
|
1025
|
+
if (onBlur) {
|
|
1026
|
+
onBlur(event);
|
|
1027
|
+
}
|
|
1028
|
+
});
|
|
1029
|
+
};
|
|
1030
|
+
|
|
1031
|
+
this.state = {
|
|
1032
|
+
error: null,
|
|
1033
|
+
focused: false
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
render() {
|
|
1038
|
+
const {
|
|
1039
|
+
id,
|
|
1040
|
+
type,
|
|
1041
|
+
label,
|
|
1042
|
+
description,
|
|
1043
|
+
value,
|
|
1044
|
+
disabled,
|
|
1045
|
+
validate,
|
|
1046
|
+
onChange,
|
|
1047
|
+
onKeyDown,
|
|
1048
|
+
placeholder,
|
|
1049
|
+
light,
|
|
1050
|
+
style,
|
|
1051
|
+
testId,
|
|
1052
|
+
readOnly,
|
|
1053
|
+
autoComplete,
|
|
1054
|
+
forwardedRef
|
|
1055
|
+
} = this.props;
|
|
1056
|
+
return /*#__PURE__*/createElement(IDProvider, {
|
|
1057
|
+
id: id,
|
|
1058
|
+
scope: "labeled-text-field"
|
|
1059
|
+
}, uniqueId => /*#__PURE__*/createElement(FieldHeading, {
|
|
1060
|
+
id: uniqueId,
|
|
1061
|
+
testId: testId,
|
|
1062
|
+
style: style,
|
|
1063
|
+
field: /*#__PURE__*/createElement(TextField, {
|
|
1064
|
+
id: `${uniqueId}-field`,
|
|
1065
|
+
"aria-describedby": `${uniqueId}-error`,
|
|
1066
|
+
"aria-invalid": this.state.error ? "true" : "false",
|
|
1067
|
+
testId: testId && `${testId}-field`,
|
|
1068
|
+
type: type,
|
|
1069
|
+
value: value,
|
|
1070
|
+
placeholder: placeholder,
|
|
1071
|
+
disabled: disabled,
|
|
1072
|
+
validate: validate,
|
|
1073
|
+
onValidate: this.handleValidate,
|
|
1074
|
+
onChange: onChange,
|
|
1075
|
+
onKeyDown: onKeyDown,
|
|
1076
|
+
onFocus: this.handleFocus,
|
|
1077
|
+
onBlur: this.handleBlur,
|
|
1078
|
+
light: light,
|
|
1079
|
+
readOnly: readOnly,
|
|
1080
|
+
autoComplete: autoComplete,
|
|
1081
|
+
ref: forwardedRef
|
|
1082
|
+
}),
|
|
1083
|
+
label: label,
|
|
1084
|
+
description: description,
|
|
1085
|
+
error: !this.state.focused && this.state.error || ""
|
|
1086
|
+
}));
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
LabeledTextFieldInternal.defaultProps = {
|
|
1092
|
+
type: "text",
|
|
1093
|
+
disabled: false,
|
|
1094
|
+
light: false
|
|
1095
|
+
};
|
|
1096
|
+
const LabeledTextField = /*#__PURE__*/forwardRef((props, ref) => /*#__PURE__*/createElement(LabeledTextFieldInternal, _extends({}, props, {
|
|
1097
|
+
forwardedRef: ref
|
|
1098
|
+
})));
|
|
1099
|
+
|
|
1100
|
+
export { Checkbox, CheckboxGroup, Choice, LabeledTextField, Radio, RadioGroup, TextField };
|