@khanacademy/wonder-blocks-form 2.3.0 → 2.4.0
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/CHANGELOG.md +24 -0
- package/dist/es/index.js +216 -190
- package/dist/index.js +33 -7
- package/package.json +11 -12
- package/src/components/__tests__/checkbox-group.test.js +1 -0
- package/src/components/__tests__/field-heading.test.js +1 -0
- package/src/components/__tests__/labeled-text-field.test.js +140 -0
- package/src/components/__tests__/radio-group.test.js +1 -0
- package/src/components/__tests__/text-field.test.js +1 -18
- package/src/components/checkbox-core.js +1 -2
- package/src/components/field-heading.js +20 -2
- package/src/components/labeled-text-field.js +32 -6
- package/src/components/labeled-text-field.stories.js +98 -12
- package/src/components/text-field.js +43 -13
- package/src/components/text-field.stories.js +45 -18
- package/LICENSE +0 -21
package/dist/index.js
CHANGED
|
@@ -254,6 +254,7 @@ function _extends() { _extends = Object.assign || function (target) { for (var i
|
|
|
254
254
|
|
|
255
255
|
|
|
256
256
|
|
|
257
|
+
const defaultErrorMessage = "This field is required.";
|
|
257
258
|
|
|
258
259
|
// TODO(WB-1081): Change class name back to TextField after Styleguidist is gone.
|
|
259
260
|
|
|
@@ -271,7 +272,8 @@ class TextFieldInternal extends react__WEBPACK_IMPORTED_MODULE_0__["Component"]
|
|
|
271
272
|
this.maybeValidate = newValue => {
|
|
272
273
|
const {
|
|
273
274
|
validate,
|
|
274
|
-
onValidate
|
|
275
|
+
onValidate,
|
|
276
|
+
required
|
|
275
277
|
} = this.props;
|
|
276
278
|
|
|
277
279
|
if (validate) {
|
|
@@ -283,6 +285,16 @@ class TextFieldInternal extends react__WEBPACK_IMPORTED_MODULE_0__["Component"]
|
|
|
283
285
|
onValidate(maybeError);
|
|
284
286
|
}
|
|
285
287
|
});
|
|
288
|
+
} else if (required) {
|
|
289
|
+
const requiredString = typeof required === "string" ? required : defaultErrorMessage;
|
|
290
|
+
const maybeError = newValue ? null : requiredString;
|
|
291
|
+
this.setState({
|
|
292
|
+
error: maybeError
|
|
293
|
+
}, () => {
|
|
294
|
+
if (onValidate) {
|
|
295
|
+
onValidate(maybeError);
|
|
296
|
+
}
|
|
297
|
+
});
|
|
286
298
|
}
|
|
287
299
|
};
|
|
288
300
|
|
|
@@ -321,14 +333,16 @@ class TextFieldInternal extends react__WEBPACK_IMPORTED_MODULE_0__["Component"]
|
|
|
321
333
|
});
|
|
322
334
|
};
|
|
323
335
|
|
|
324
|
-
if (props.validate) {
|
|
336
|
+
if (props.validate && props.value !== "") {
|
|
325
337
|
// Ensures error is updated on unmounted server-side renders
|
|
326
338
|
this.state.error = props.validate(props.value) || null;
|
|
327
339
|
}
|
|
328
340
|
}
|
|
329
341
|
|
|
330
342
|
componentDidMount() {
|
|
331
|
-
|
|
343
|
+
if (this.props.value !== "") {
|
|
344
|
+
this.maybeValidate(this.props.value);
|
|
345
|
+
}
|
|
332
346
|
}
|
|
333
347
|
|
|
334
348
|
render() {
|
|
@@ -339,7 +353,6 @@ class TextFieldInternal extends react__WEBPACK_IMPORTED_MODULE_0__["Component"]
|
|
|
339
353
|
disabled,
|
|
340
354
|
onKeyDown,
|
|
341
355
|
placeholder,
|
|
342
|
-
required,
|
|
343
356
|
light,
|
|
344
357
|
style,
|
|
345
358
|
testId,
|
|
@@ -355,6 +368,7 @@ class TextFieldInternal extends react__WEBPACK_IMPORTED_MODULE_0__["Component"]
|
|
|
355
368
|
onValidate,
|
|
356
369
|
validate,
|
|
357
370
|
onChange,
|
|
371
|
+
required,
|
|
358
372
|
|
|
359
373
|
/* eslint-enable no-unused-vars */
|
|
360
374
|
// Should only include Aria related props
|
|
@@ -372,7 +386,6 @@ class TextFieldInternal extends react__WEBPACK_IMPORTED_MODULE_0__["Component"]
|
|
|
372
386
|
onKeyDown: onKeyDown,
|
|
373
387
|
onFocus: this.handleFocus,
|
|
374
388
|
onBlur: this.handleBlur,
|
|
375
|
-
required: required,
|
|
376
389
|
"data-test-id": testId,
|
|
377
390
|
readOnly: readOnly,
|
|
378
391
|
autoComplete: autoComplete,
|
|
@@ -926,6 +939,7 @@ class LabeledTextFieldInternal extends react__WEBPACK_IMPORTED_MODULE_0__["Compo
|
|
|
926
939
|
description,
|
|
927
940
|
value,
|
|
928
941
|
disabled,
|
|
942
|
+
required,
|
|
929
943
|
validate,
|
|
930
944
|
onChange,
|
|
931
945
|
onKeyDown,
|
|
@@ -949,6 +963,8 @@ class LabeledTextFieldInternal extends react__WEBPACK_IMPORTED_MODULE_0__["Compo
|
|
|
949
963
|
id: `${uniqueId}-field`,
|
|
950
964
|
"aria-describedby": ariaDescribedby ? ariaDescribedby : `${uniqueId}-error`,
|
|
951
965
|
"aria-invalid": this.state.error ? "true" : "false",
|
|
966
|
+
"aria-required": required ? "true" : "false",
|
|
967
|
+
required: required,
|
|
952
968
|
testId: testId && `${testId}-field`,
|
|
953
969
|
type: type,
|
|
954
970
|
value: value,
|
|
@@ -967,6 +983,7 @@ class LabeledTextFieldInternal extends react__WEBPACK_IMPORTED_MODULE_0__["Compo
|
|
|
967
983
|
}),
|
|
968
984
|
label: label,
|
|
969
985
|
description: description,
|
|
986
|
+
required: !!required,
|
|
970
987
|
error: !this.state.focused && this.state.error || ""
|
|
971
988
|
}));
|
|
972
989
|
}
|
|
@@ -1396,24 +1413,30 @@ const _generateStyles = (checked, error) => {
|
|
|
1396
1413
|
|
|
1397
1414
|
|
|
1398
1415
|
|
|
1399
|
-
|
|
1416
|
+
const StyledSpan = Object(_khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_2__["addStyle"])("span");
|
|
1400
1417
|
/**
|
|
1401
1418
|
* A FieldHeading is an element that provides a label, description, and error element
|
|
1402
1419
|
* to present better context and hints to any type of form field component.
|
|
1403
1420
|
*/
|
|
1421
|
+
|
|
1404
1422
|
class FieldHeading extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
|
|
1405
1423
|
renderLabel() {
|
|
1406
1424
|
const {
|
|
1407
1425
|
label,
|
|
1408
1426
|
id,
|
|
1427
|
+
required,
|
|
1409
1428
|
testId
|
|
1410
1429
|
} = this.props;
|
|
1430
|
+
const requiredIcon = /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["createElement"](StyledSpan, {
|
|
1431
|
+
style: styles.required,
|
|
1432
|
+
"aria-hidden": true
|
|
1433
|
+
}, " ", "*");
|
|
1411
1434
|
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["createElement"](react__WEBPACK_IMPORTED_MODULE_0__["Fragment"], null, typeof label === "string" ? /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["createElement"](_khanacademy_wonder_blocks_typography__WEBPACK_IMPORTED_MODULE_6__["LabelMedium"], {
|
|
1412
1435
|
style: styles.label,
|
|
1413
1436
|
tag: "label",
|
|
1414
1437
|
htmlFor: id && `${id}-field`,
|
|
1415
1438
|
testId: testId && `${testId}-label`
|
|
1416
|
-
}, label) : label, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["createElement"](_khanacademy_wonder_blocks_layout__WEBPACK_IMPORTED_MODULE_4__["Strut"], {
|
|
1439
|
+
}, label, required && requiredIcon) : label, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["createElement"](_khanacademy_wonder_blocks_layout__WEBPACK_IMPORTED_MODULE_4__["Strut"], {
|
|
1417
1440
|
size: _khanacademy_wonder_blocks_spacing__WEBPACK_IMPORTED_MODULE_5___default.a.xxxSmall_4
|
|
1418
1441
|
}));
|
|
1419
1442
|
}
|
|
@@ -1479,6 +1502,9 @@ const styles = aphrodite__WEBPACK_IMPORTED_MODULE_1__["StyleSheet"].create({
|
|
|
1479
1502
|
},
|
|
1480
1503
|
error: {
|
|
1481
1504
|
color: _khanacademy_wonder_blocks_color__WEBPACK_IMPORTED_MODULE_3___default.a.red
|
|
1505
|
+
},
|
|
1506
|
+
required: {
|
|
1507
|
+
color: _khanacademy_wonder_blocks_color__WEBPACK_IMPORTED_MODULE_3___default.a.red
|
|
1482
1508
|
}
|
|
1483
1509
|
});
|
|
1484
1510
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@khanacademy/wonder-blocks-form",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"design": "v1",
|
|
5
5
|
"description": "Form components for Wonder Blocks.",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -15,21 +15,20 @@
|
|
|
15
15
|
"access": "public"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@babel/runtime": "^7.
|
|
19
|
-
"@khanacademy/wonder-blocks-clickable": "^2.2.
|
|
20
|
-
"@khanacademy/wonder-blocks-color": "^1.1.
|
|
21
|
-
"@khanacademy/wonder-blocks-core": "^
|
|
22
|
-
"@khanacademy/wonder-blocks-icon": "^1.2.
|
|
23
|
-
"@khanacademy/wonder-blocks-layout": "^1.4.
|
|
24
|
-
"@khanacademy/wonder-blocks-spacing": "^3.0.
|
|
25
|
-
"@khanacademy/wonder-blocks-typography": "^1.1.
|
|
18
|
+
"@babel/runtime": "^7.16.3",
|
|
19
|
+
"@khanacademy/wonder-blocks-clickable": "^2.2.3",
|
|
20
|
+
"@khanacademy/wonder-blocks-color": "^1.1.20",
|
|
21
|
+
"@khanacademy/wonder-blocks-core": "^4.2.1",
|
|
22
|
+
"@khanacademy/wonder-blocks-icon": "^1.2.25",
|
|
23
|
+
"@khanacademy/wonder-blocks-layout": "^1.4.7",
|
|
24
|
+
"@khanacademy/wonder-blocks-spacing": "^3.0.5",
|
|
25
|
+
"@khanacademy/wonder-blocks-typography": "^1.1.29"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
28
|
"aphrodite": "^1.2.5",
|
|
29
29
|
"react": "16.14.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"wb-dev-build-settings": "^0.
|
|
33
|
-
}
|
|
34
|
-
"gitHead": "61090a61b6e9d2a735976d5fd53a15d06f10c853"
|
|
32
|
+
"wb-dev-build-settings": "^0.3.0"
|
|
33
|
+
}
|
|
35
34
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
//@flow
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import {mount} from "enzyme";
|
|
4
|
+
import "jest-enzyme";
|
|
4
5
|
import {render, screen} from "@testing-library/react";
|
|
6
|
+
import userEvent from "@testing-library/user-event";
|
|
5
7
|
|
|
6
8
|
import {StyleSheet} from "aphrodite";
|
|
7
9
|
import LabeledTextField from "../labeled-text-field.js";
|
|
@@ -484,3 +486,141 @@ describe("LabeledTextField", () => {
|
|
|
484
486
|
expect(textField).toHaveProp("autoComplete", autoComplete);
|
|
485
487
|
});
|
|
486
488
|
});
|
|
489
|
+
|
|
490
|
+
describe("Required LabeledTextField", () => {
|
|
491
|
+
test("has * when `required` prop is true", () => {
|
|
492
|
+
// Arrange
|
|
493
|
+
|
|
494
|
+
// Act
|
|
495
|
+
render(
|
|
496
|
+
<LabeledTextField
|
|
497
|
+
label="Label"
|
|
498
|
+
value=""
|
|
499
|
+
onChange={() => {}}
|
|
500
|
+
required={true}
|
|
501
|
+
/>,
|
|
502
|
+
);
|
|
503
|
+
|
|
504
|
+
// Assert
|
|
505
|
+
expect(screen.getByText("*")).toBeInTheDocument();
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
test("does not have * when `required` prop is false", () => {
|
|
509
|
+
// Arrange
|
|
510
|
+
|
|
511
|
+
// Act
|
|
512
|
+
render(
|
|
513
|
+
<LabeledTextField
|
|
514
|
+
label="Label"
|
|
515
|
+
value=""
|
|
516
|
+
onChange={() => {}}
|
|
517
|
+
required={false}
|
|
518
|
+
/>,
|
|
519
|
+
);
|
|
520
|
+
|
|
521
|
+
// Assert
|
|
522
|
+
expect(screen.queryByText("*")).not.toBeInTheDocument();
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
test("aria-required is true when `required` prop is true", () => {
|
|
526
|
+
// Arrange
|
|
527
|
+
|
|
528
|
+
// Act
|
|
529
|
+
render(
|
|
530
|
+
<LabeledTextField
|
|
531
|
+
label="Label"
|
|
532
|
+
value=""
|
|
533
|
+
onChange={() => {}}
|
|
534
|
+
testId="foo-labeled-text-field"
|
|
535
|
+
required={true}
|
|
536
|
+
/>,
|
|
537
|
+
);
|
|
538
|
+
|
|
539
|
+
const textField = screen.getByTestId("foo-labeled-text-field-field");
|
|
540
|
+
|
|
541
|
+
// Assert
|
|
542
|
+
expect(textField).toHaveAttribute("aria-required", "true");
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
test("aria-required is false when `required` prop is false", () => {
|
|
546
|
+
// Arrange
|
|
547
|
+
|
|
548
|
+
// Act
|
|
549
|
+
render(
|
|
550
|
+
<LabeledTextField
|
|
551
|
+
label="Label"
|
|
552
|
+
value=""
|
|
553
|
+
onChange={() => {}}
|
|
554
|
+
testId="foo-labeled-text-field"
|
|
555
|
+
required={false}
|
|
556
|
+
/>,
|
|
557
|
+
);
|
|
558
|
+
|
|
559
|
+
const textField = screen.getByTestId("foo-labeled-text-field-field");
|
|
560
|
+
|
|
561
|
+
// Assert
|
|
562
|
+
expect(textField).toHaveAttribute("aria-required", "false");
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
test("displays the default message when the `required` prop is `true`", () => {
|
|
566
|
+
// Arrange
|
|
567
|
+
const TextFieldWrapper = () => {
|
|
568
|
+
const [value, setValue] = React.useState("");
|
|
569
|
+
return (
|
|
570
|
+
<LabeledTextField
|
|
571
|
+
label="Label"
|
|
572
|
+
value={value}
|
|
573
|
+
onChange={setValue}
|
|
574
|
+
required={true}
|
|
575
|
+
testId="test-labeled-text-field"
|
|
576
|
+
/>
|
|
577
|
+
);
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
render(<TextFieldWrapper />);
|
|
581
|
+
|
|
582
|
+
const textField = screen.getByTestId("test-labeled-text-field-field");
|
|
583
|
+
textField.focus();
|
|
584
|
+
userEvent.paste(textField, "a");
|
|
585
|
+
userEvent.clear(textField);
|
|
586
|
+
|
|
587
|
+
// Act
|
|
588
|
+
textField.blur();
|
|
589
|
+
|
|
590
|
+
// Assert
|
|
591
|
+
expect(screen.getByRole("alert")).toHaveTextContent(
|
|
592
|
+
"This field is required.",
|
|
593
|
+
);
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
test("displays the string passed into `required`", () => {
|
|
597
|
+
// Arrange
|
|
598
|
+
const errorMessage = "This is an example error message.";
|
|
599
|
+
|
|
600
|
+
const TextFieldWrapper = () => {
|
|
601
|
+
const [value, setValue] = React.useState("");
|
|
602
|
+
return (
|
|
603
|
+
<LabeledTextField
|
|
604
|
+
label="Label"
|
|
605
|
+
value={value}
|
|
606
|
+
onChange={setValue}
|
|
607
|
+
required={errorMessage}
|
|
608
|
+
testId="test-labeled-text-field"
|
|
609
|
+
/>
|
|
610
|
+
);
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
render(<TextFieldWrapper />);
|
|
614
|
+
|
|
615
|
+
const textField = screen.getByTestId("test-labeled-text-field-field");
|
|
616
|
+
textField.focus();
|
|
617
|
+
userEvent.paste(textField, "a");
|
|
618
|
+
userEvent.clear(textField);
|
|
619
|
+
|
|
620
|
+
// Act
|
|
621
|
+
textField.blur();
|
|
622
|
+
|
|
623
|
+
// Assert
|
|
624
|
+
expect(screen.getByRole("alert")).toHaveTextContent(errorMessage);
|
|
625
|
+
});
|
|
626
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// @flow
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import {mount} from "enzyme";
|
|
4
|
+
import "jest-enzyme";
|
|
4
5
|
|
|
5
6
|
import TextField from "../text-field.js";
|
|
6
7
|
|
|
@@ -329,24 +330,6 @@ describe("TextField", () => {
|
|
|
329
330
|
);
|
|
330
331
|
});
|
|
331
332
|
|
|
332
|
-
it("required prop is passed to the input element", () => {
|
|
333
|
-
// Arrange
|
|
334
|
-
const wrapper = mount(
|
|
335
|
-
<TextField
|
|
336
|
-
id={"tf-1"}
|
|
337
|
-
value="Text"
|
|
338
|
-
onChange={() => {}}
|
|
339
|
-
required={true}
|
|
340
|
-
/>,
|
|
341
|
-
);
|
|
342
|
-
|
|
343
|
-
// Act
|
|
344
|
-
|
|
345
|
-
// Assert
|
|
346
|
-
const input = wrapper.find("input");
|
|
347
|
-
expect(input).toContainMatchingElement("[required=true]");
|
|
348
|
-
});
|
|
349
|
-
|
|
350
333
|
it("testId is passed to the input element", () => {
|
|
351
334
|
// Arrange
|
|
352
335
|
const testId = "some-test-id";
|
|
@@ -24,8 +24,7 @@ const {blue, red, white, offWhite, offBlack16, offBlack32, offBlack50} = Color;
|
|
|
24
24
|
const StyledInput = addStyle("input");
|
|
25
25
|
|
|
26
26
|
const checkboxCheck: IconAsset = {
|
|
27
|
-
small:
|
|
28
|
-
"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",
|
|
27
|
+
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",
|
|
29
28
|
};
|
|
30
29
|
|
|
31
30
|
/**
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import {StyleSheet} from "aphrodite";
|
|
4
4
|
|
|
5
|
-
import {View, type StyleType} from "@khanacademy/wonder-blocks-core";
|
|
5
|
+
import {View, addStyle, type StyleType} from "@khanacademy/wonder-blocks-core";
|
|
6
6
|
import Color from "@khanacademy/wonder-blocks-color";
|
|
7
7
|
import {Strut} from "@khanacademy/wonder-blocks-layout";
|
|
8
8
|
import Spacing from "@khanacademy/wonder-blocks-spacing";
|
|
@@ -28,6 +28,11 @@ type Props = {|
|
|
|
28
28
|
*/
|
|
29
29
|
description?: string | React.Element<Typography>,
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Whether this field is required to continue.
|
|
33
|
+
*/
|
|
34
|
+
required?: boolean,
|
|
35
|
+
|
|
31
36
|
/**
|
|
32
37
|
* The message for the error element.
|
|
33
38
|
*/
|
|
@@ -52,13 +57,22 @@ type Props = {|
|
|
|
52
57
|
testId?: string,
|
|
53
58
|
|};
|
|
54
59
|
|
|
60
|
+
const StyledSpan = addStyle("span");
|
|
61
|
+
|
|
55
62
|
/**
|
|
56
63
|
* A FieldHeading is an element that provides a label, description, and error element
|
|
57
64
|
* to present better context and hints to any type of form field component.
|
|
58
65
|
*/
|
|
59
66
|
export default class FieldHeading extends React.Component<Props> {
|
|
60
67
|
renderLabel(): React.Node {
|
|
61
|
-
const {label, id, testId} = this.props;
|
|
68
|
+
const {label, id, required, testId} = this.props;
|
|
69
|
+
|
|
70
|
+
const requiredIcon = (
|
|
71
|
+
<StyledSpan style={styles.required} aria-hidden={true}>
|
|
72
|
+
{" "}
|
|
73
|
+
*
|
|
74
|
+
</StyledSpan>
|
|
75
|
+
);
|
|
62
76
|
|
|
63
77
|
return (
|
|
64
78
|
<React.Fragment>
|
|
@@ -70,6 +84,7 @@ export default class FieldHeading extends React.Component<Props> {
|
|
|
70
84
|
testId={testId && `${testId}-label`}
|
|
71
85
|
>
|
|
72
86
|
{label}
|
|
87
|
+
{required && requiredIcon}
|
|
73
88
|
</LabelMedium>
|
|
74
89
|
) : (
|
|
75
90
|
label
|
|
@@ -154,4 +169,7 @@ const styles = StyleSheet.create({
|
|
|
154
169
|
error: {
|
|
155
170
|
color: Color.red,
|
|
156
171
|
},
|
|
172
|
+
required: {
|
|
173
|
+
color: Color.red,
|
|
174
|
+
},
|
|
157
175
|
});
|
|
@@ -41,6 +41,30 @@ type Props = {|
|
|
|
41
41
|
*/
|
|
42
42
|
disabled: boolean,
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Whether this field is required to to continue, or the error message to
|
|
46
|
+
* render if this field is left blank.
|
|
47
|
+
*
|
|
48
|
+
* This can be a boolean or a string.
|
|
49
|
+
*
|
|
50
|
+
* String:
|
|
51
|
+
* Please pass in a translated string to use as the error message that will
|
|
52
|
+
* render if the user leaves this field blank. If this field is required,
|
|
53
|
+
* and a string is not passed in, a default untranslated string will render
|
|
54
|
+
* upon error.
|
|
55
|
+
* Note: The string will not be used if a `validate` prop is passed in.
|
|
56
|
+
*
|
|
57
|
+
* Example message: i18n._("A password is required to log in.")
|
|
58
|
+
*
|
|
59
|
+
* Boolean:
|
|
60
|
+
* True/false indicating whether this field is required. Please do not pass
|
|
61
|
+
* in `true` if possible - pass in the error string instead.
|
|
62
|
+
* If `true` is passed, and a `validate` prop is not passed, that means
|
|
63
|
+
* there is no corresponding message and the default untranlsated message
|
|
64
|
+
* will be used.
|
|
65
|
+
*/
|
|
66
|
+
required?: boolean | string,
|
|
67
|
+
|
|
44
68
|
/**
|
|
45
69
|
* Identifies the element or elements that describes this text field.
|
|
46
70
|
*/
|
|
@@ -202,6 +226,7 @@ class LabeledTextFieldInternal extends React.Component<
|
|
|
202
226
|
description,
|
|
203
227
|
value,
|
|
204
228
|
disabled,
|
|
229
|
+
required,
|
|
205
230
|
validate,
|
|
206
231
|
onChange,
|
|
207
232
|
onKeyDown,
|
|
@@ -233,6 +258,8 @@ class LabeledTextFieldInternal extends React.Component<
|
|
|
233
258
|
aria-invalid={
|
|
234
259
|
this.state.error ? "true" : "false"
|
|
235
260
|
}
|
|
261
|
+
aria-required={required ? "true" : "false"}
|
|
262
|
+
required={required}
|
|
236
263
|
testId={testId && `${testId}-field`}
|
|
237
264
|
type={type}
|
|
238
265
|
value={value}
|
|
@@ -252,6 +279,7 @@ class LabeledTextFieldInternal extends React.Component<
|
|
|
252
279
|
}
|
|
253
280
|
label={label}
|
|
254
281
|
description={description}
|
|
282
|
+
required={!!required}
|
|
255
283
|
error={(!this.state.focused && this.state.error) || ""}
|
|
256
284
|
/>
|
|
257
285
|
)}
|
|
@@ -265,11 +293,9 @@ type ExportProps = $Diff<
|
|
|
265
293
|
WithForwardRef,
|
|
266
294
|
>;
|
|
267
295
|
|
|
268
|
-
const LabeledTextField: React.AbstractComponent<
|
|
269
|
-
ExportProps,
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
<LabeledTextFieldInternal {...props} forwardedRef={ref} />
|
|
273
|
-
));
|
|
296
|
+
const LabeledTextField: React.AbstractComponent<ExportProps, HTMLInputElement> =
|
|
297
|
+
React.forwardRef<ExportProps, HTMLInputElement>((props, ref) => (
|
|
298
|
+
<LabeledTextFieldInternal {...props} forwardedRef={ref} />
|
|
299
|
+
));
|
|
274
300
|
|
|
275
301
|
export default LabeledTextField;
|