@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,129 @@
|
|
|
1
|
+
This example has a disabled item. Selecting the last item results in an error
|
|
2
|
+
state for the entire group.
|
|
3
|
+
|
|
4
|
+
Try out the keyboard navigation! Use up/left and down/right to navigate among
|
|
5
|
+
the choices, and use tab to navigate between this radio group and the rest of
|
|
6
|
+
the page.
|
|
7
|
+
|
|
8
|
+
```js
|
|
9
|
+
import {Choice, RadioGroup} from "@khanacademy/wonder-blocks-form";
|
|
10
|
+
import {View} from "@khanacademy/wonder-blocks-core";
|
|
11
|
+
import {StyleSheet} from "aphrodite";
|
|
12
|
+
|
|
13
|
+
const styles = StyleSheet.create({
|
|
14
|
+
wrapper: {
|
|
15
|
+
width: 300,
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
class RadioGroupPokemonExample extends React.Component {
|
|
20
|
+
constructor() {
|
|
21
|
+
super();
|
|
22
|
+
this.state = {
|
|
23
|
+
selectedValue: null,
|
|
24
|
+
};
|
|
25
|
+
this.handleChange = this.handleChange.bind(this);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
handleChange(change) {
|
|
29
|
+
console.log(`${change} was selected!`);
|
|
30
|
+
const error = this.checkForError(change);
|
|
31
|
+
this.setState({
|
|
32
|
+
selectedValue: change,
|
|
33
|
+
error: error,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
checkForError(input) {
|
|
38
|
+
if (input === "infiltrator") {
|
|
39
|
+
return "Superman isn't a Pokemon!";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
render() {
|
|
44
|
+
return <RadioGroup
|
|
45
|
+
label="Choose a starter!"
|
|
46
|
+
description="Your first Pokemon"
|
|
47
|
+
errorMessage={this.state.error}
|
|
48
|
+
groupName="Pokemon"
|
|
49
|
+
onChange={this.handleChange}
|
|
50
|
+
selectedValue={this.state.selectedValue}
|
|
51
|
+
>
|
|
52
|
+
<Choice label="Bulbasaur" value="bulb" />
|
|
53
|
+
<Choice label="Charmander" value="char" description="Oops, we ran out of Charmanders" disabled />
|
|
54
|
+
<Choice label="Squirtle" value="squirt" />
|
|
55
|
+
<Choice label="Pikachu" value="pika" />
|
|
56
|
+
<Choice label="Superman" value="infiltrator" />
|
|
57
|
+
</RadioGroup>
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
<View style={styles.wrapper}>
|
|
61
|
+
<RadioGroupPokemonExample />
|
|
62
|
+
</View>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
This example shows how to use custom styling to change the appearance of the
|
|
66
|
+
radio group to look more like a multiple choice question.
|
|
67
|
+
|
|
68
|
+
```js
|
|
69
|
+
import {Choice, RadioGroup} from "@khanacademy/wonder-blocks-form";
|
|
70
|
+
import {View} from "@khanacademy/wonder-blocks-core";
|
|
71
|
+
import Color from "@khanacademy/wonder-blocks-color";
|
|
72
|
+
import {LabelLarge} from "@khanacademy/wonder-blocks-typography";
|
|
73
|
+
import {StyleSheet} from "aphrodite";
|
|
74
|
+
|
|
75
|
+
const styles = StyleSheet.create({
|
|
76
|
+
wrapper: {
|
|
77
|
+
width: 650,
|
|
78
|
+
},
|
|
79
|
+
choice: {
|
|
80
|
+
margin: 0,
|
|
81
|
+
height: 48,
|
|
82
|
+
borderTop: "solid 1px #CCC",
|
|
83
|
+
justifyContent: "center",
|
|
84
|
+
":last-child": {
|
|
85
|
+
borderBottom: "solid 1px #CCC",
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
prompt: {
|
|
89
|
+
marginBottom: 16,
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
class ClassSelectorExample extends React.Component {
|
|
94
|
+
constructor() {
|
|
95
|
+
super();
|
|
96
|
+
this.state = {
|
|
97
|
+
selectedValue: null,
|
|
98
|
+
};
|
|
99
|
+
this.handleChange = this.handleChange.bind(this);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
handleChange(change) {
|
|
103
|
+
console.log(`${change} was selected!`);
|
|
104
|
+
this.setState({
|
|
105
|
+
selectedValue: change,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
render() {
|
|
110
|
+
return <RadioGroup
|
|
111
|
+
groupName="science-classes"
|
|
112
|
+
onChange={this.handleChange}
|
|
113
|
+
selectedValue={this.state.selectedValue}
|
|
114
|
+
>
|
|
115
|
+
<Choice label="A" value="1" style={styles.choice} />
|
|
116
|
+
<Choice label="B" value="2" style={styles.choice} />
|
|
117
|
+
<Choice label="AB" value="3" style={styles.choice} />
|
|
118
|
+
<Choice label="O" value="4" style={styles.choice} />
|
|
119
|
+
</RadioGroup>
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
<View>
|
|
124
|
+
<LabelLarge style={styles.prompt}>
|
|
125
|
+
Select your blood type
|
|
126
|
+
</LabelLarge>
|
|
127
|
+
<ClassSelectorExample />
|
|
128
|
+
</View>
|
|
129
|
+
```
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
|
|
5
|
+
import type {AriaProps, StyleType} from "@khanacademy/wonder-blocks-core";
|
|
6
|
+
import ChoiceInternal from "./choice-internal.js";
|
|
7
|
+
|
|
8
|
+
// Keep synced with ChoiceComponentProps in ../util/types.js
|
|
9
|
+
type ChoiceComponentProps = {|
|
|
10
|
+
...AriaProps,
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Whether this component is checked
|
|
14
|
+
*/
|
|
15
|
+
checked: boolean,
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Whether this component is disabled
|
|
19
|
+
*/
|
|
20
|
+
disabled: boolean,
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Whether this component should show an error state
|
|
24
|
+
*/
|
|
25
|
+
error: boolean,
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Callback when this component is selected. The newCheckedState is the
|
|
29
|
+
* new checked state of the component.
|
|
30
|
+
*/
|
|
31
|
+
onChange: (newCheckedState: boolean) => mixed,
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Optional label for the field.
|
|
35
|
+
*/
|
|
36
|
+
label?: string,
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Optional description for the field.
|
|
40
|
+
*/
|
|
41
|
+
description?: string,
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Unique identifier attached to the HTML input element. If used, need to
|
|
45
|
+
* guarantee that the ID is unique within everything rendered on a page.
|
|
46
|
+
* Used to match `<label>` with `<input>` elements for screenreaders.
|
|
47
|
+
*/
|
|
48
|
+
id?: string,
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Optional styling for the container. Does not style the component.
|
|
52
|
+
*/
|
|
53
|
+
style?: StyleType,
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Adds CSS classes to the Checkbox.
|
|
57
|
+
*/
|
|
58
|
+
className?: string,
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Optional test ID for e2e testing
|
|
62
|
+
*/
|
|
63
|
+
testId?: string,
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Name for the checkbox or radio button group. Only applicable for group
|
|
67
|
+
* contexts, auto-populated by group components via Choice.
|
|
68
|
+
* @ignore
|
|
69
|
+
*/
|
|
70
|
+
groupName?: string,
|
|
71
|
+
|};
|
|
72
|
+
|
|
73
|
+
type DefaultProps = {|
|
|
74
|
+
disabled: $PropertyType<ChoiceComponentProps, "disabled">,
|
|
75
|
+
error: $PropertyType<ChoiceComponentProps, "error">,
|
|
76
|
+
|};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 🔘 A nicely styled radio button for all your non-AMFM radio button needs. Can
|
|
80
|
+
* optionally take label and description props.
|
|
81
|
+
*
|
|
82
|
+
* This component should not really be used by itself because radio buttons are
|
|
83
|
+
* often grouped together. See RadioGroup.
|
|
84
|
+
*/ export default class Radio extends React.Component<ChoiceComponentProps> {
|
|
85
|
+
static defaultProps: DefaultProps = {
|
|
86
|
+
disabled: false,
|
|
87
|
+
error: false,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
render(): React.Node {
|
|
91
|
+
return <ChoiceInternal variant="radio" {...this.props} />;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
The radio button has various styles for clickable states. Here are sets of default radio buttons, radio buttons in an error state, and disabled radio buttons.
|
|
2
|
+
```js
|
|
3
|
+
import {View} from "@khanacademy/wonder-blocks-core";
|
|
4
|
+
import {Radio} from "@khanacademy/wonder-blocks-form";
|
|
5
|
+
import {StyleSheet} from "aphrodite";
|
|
6
|
+
|
|
7
|
+
const styles = StyleSheet.create({
|
|
8
|
+
row: {
|
|
9
|
+
flexDirection: "row",
|
|
10
|
+
},
|
|
11
|
+
marginRight: {
|
|
12
|
+
marginRight: 16,
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const handleChange = (checked) => console.log(`clicked on radio, will be checked=${checked.toString()}`);
|
|
17
|
+
|
|
18
|
+
<View style={styles.row}>
|
|
19
|
+
<Radio error={false} checked={false} style={styles.marginRight} onChange={handleChange} />
|
|
20
|
+
<Radio error={false} checked={true} style={styles.marginRight} onChange={handleChange} />
|
|
21
|
+
<Radio error={true} checked={false} style={styles.marginRight} onChange={handleChange} />
|
|
22
|
+
<Radio error={true} checked={true} style={styles.marginRight} onChange={handleChange} />
|
|
23
|
+
<Radio disabled={true} checked={false} style={styles.marginRight} onChange={handleChange} />
|
|
24
|
+
<Radio disabled={true} checked={true} style={styles.marginRight} onChange={handleChange} />
|
|
25
|
+
</View>
|
|
26
|
+
```
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import {StyleSheet, css} from "aphrodite";
|
|
5
|
+
|
|
6
|
+
import Color, {mix, fade} from "@khanacademy/wonder-blocks-color";
|
|
7
|
+
import Spacing from "@khanacademy/wonder-blocks-spacing";
|
|
8
|
+
import {styles as typographyStyles} from "@khanacademy/wonder-blocks-typography";
|
|
9
|
+
import type {StyleType, AriaProps} from "@khanacademy/wonder-blocks-core";
|
|
10
|
+
|
|
11
|
+
export type TextFieldType = "text" | "password" | "email" | "number" | "tel";
|
|
12
|
+
|
|
13
|
+
type WithForwardRef = {|forwardedRef: React.Ref<"input">|};
|
|
14
|
+
|
|
15
|
+
type Props = {|
|
|
16
|
+
...AriaProps,
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The unique identifier for the input.
|
|
20
|
+
*/
|
|
21
|
+
id: string,
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Determines the type of input. Defaults to text.
|
|
25
|
+
*/
|
|
26
|
+
type: TextFieldType,
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The input value.
|
|
30
|
+
*/
|
|
31
|
+
value: string,
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Makes a read-only input field that cannot be focused. Defaults to false.
|
|
35
|
+
*/
|
|
36
|
+
disabled: boolean,
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Provide a validation for the input value.
|
|
40
|
+
* Return a string error message or null | void for a valid input.
|
|
41
|
+
*/
|
|
42
|
+
validate?: (value: string) => ?string,
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Called right after the TextField input is validated.
|
|
46
|
+
*/
|
|
47
|
+
onValidate?: (errorMessage: ?string) => mixed,
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Called when the value has changed.
|
|
51
|
+
*/
|
|
52
|
+
onChange: (newValue: string) => mixed,
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Called when a key is pressed.
|
|
56
|
+
*/
|
|
57
|
+
onKeyDown?: (event: SyntheticKeyboardEvent<HTMLInputElement>) => mixed,
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Called when the element has been focused.
|
|
61
|
+
*/
|
|
62
|
+
onFocus?: (event: SyntheticFocusEvent<HTMLInputElement>) => mixed,
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Called when the element has been blurred.
|
|
66
|
+
*/
|
|
67
|
+
onBlur?: (event: SyntheticFocusEvent<HTMLInputElement>) => mixed,
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Provide hints or examples of what to enter.
|
|
71
|
+
*/
|
|
72
|
+
placeholder?: string,
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Whether this component is required.
|
|
76
|
+
*/
|
|
77
|
+
required?: boolean,
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Change the default focus ring color to fit a dark background.
|
|
81
|
+
*/
|
|
82
|
+
light: boolean,
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Custom styles for the input.
|
|
86
|
+
*/
|
|
87
|
+
style?: StyleType,
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Optional test ID for e2e testing.
|
|
91
|
+
*/
|
|
92
|
+
testId?: string,
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Specifies if the input field is read-only.
|
|
96
|
+
*/
|
|
97
|
+
readOnly?: boolean,
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Specifies if the input field allows autocomplete.
|
|
101
|
+
*/
|
|
102
|
+
autoComplete?: string,
|
|
103
|
+
|};
|
|
104
|
+
|
|
105
|
+
type PropsWithForwardRef = {|
|
|
106
|
+
...Props,
|
|
107
|
+
...WithForwardRef,
|
|
108
|
+
|};
|
|
109
|
+
|
|
110
|
+
type DefaultProps = {|
|
|
111
|
+
type: $PropertyType<PropsWithForwardRef, "type">,
|
|
112
|
+
disabled: $PropertyType<PropsWithForwardRef, "disabled">,
|
|
113
|
+
light: $PropertyType<PropsWithForwardRef, "light">,
|
|
114
|
+
|};
|
|
115
|
+
|
|
116
|
+
type State = {|
|
|
117
|
+
/**
|
|
118
|
+
* Displayed when the validation fails.
|
|
119
|
+
*/
|
|
120
|
+
error: ?string,
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* The user focuses on this field.
|
|
124
|
+
*/
|
|
125
|
+
focused: boolean,
|
|
126
|
+
|};
|
|
127
|
+
|
|
128
|
+
// TODO(WB-1081): Change class name back to TextField after Styleguidist is gone.
|
|
129
|
+
/**
|
|
130
|
+
* A TextField is an element used to accept a single line of text from the user.
|
|
131
|
+
*/
|
|
132
|
+
class TextFieldInternal extends React.Component<PropsWithForwardRef, State> {
|
|
133
|
+
static defaultProps: DefaultProps = {
|
|
134
|
+
type: "text",
|
|
135
|
+
disabled: false,
|
|
136
|
+
light: false,
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
constructor(props: PropsWithForwardRef) {
|
|
140
|
+
super(props);
|
|
141
|
+
if (props.validate) {
|
|
142
|
+
// Ensures error is updated on unmounted server-side renders
|
|
143
|
+
this.state.error = props.validate(props.value) || null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
state: State = {
|
|
148
|
+
error: null,
|
|
149
|
+
focused: false,
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
componentDidMount() {
|
|
153
|
+
this.maybeValidate(this.props.value);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
maybeValidate: (newValue: string) => void = (newValue) => {
|
|
157
|
+
const {validate, onValidate} = this.props;
|
|
158
|
+
if (validate) {
|
|
159
|
+
const maybeError = validate(newValue) || null;
|
|
160
|
+
this.setState({error: maybeError}, () => {
|
|
161
|
+
if (onValidate) {
|
|
162
|
+
onValidate(maybeError);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
handleChange: (event: SyntheticInputEvent<HTMLInputElement>) => mixed = (
|
|
169
|
+
event,
|
|
170
|
+
) => {
|
|
171
|
+
const {onChange} = this.props;
|
|
172
|
+
const newValue = event.target.value;
|
|
173
|
+
this.maybeValidate(newValue);
|
|
174
|
+
onChange(newValue);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
handleFocus: (event: SyntheticFocusEvent<HTMLInputElement>) => mixed = (
|
|
178
|
+
event,
|
|
179
|
+
) => {
|
|
180
|
+
const {onFocus} = this.props;
|
|
181
|
+
this.setState({focused: true}, () => {
|
|
182
|
+
if (onFocus) {
|
|
183
|
+
onFocus(event);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
handleBlur: (event: SyntheticFocusEvent<HTMLInputElement>) => mixed = (
|
|
189
|
+
event,
|
|
190
|
+
) => {
|
|
191
|
+
const {onBlur} = this.props;
|
|
192
|
+
this.setState({focused: false}, () => {
|
|
193
|
+
if (onBlur) {
|
|
194
|
+
onBlur(event);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
render(): React.Node {
|
|
200
|
+
const {
|
|
201
|
+
id,
|
|
202
|
+
type,
|
|
203
|
+
value,
|
|
204
|
+
disabled,
|
|
205
|
+
onKeyDown,
|
|
206
|
+
placeholder,
|
|
207
|
+
required,
|
|
208
|
+
light,
|
|
209
|
+
style,
|
|
210
|
+
testId,
|
|
211
|
+
readOnly,
|
|
212
|
+
autoComplete,
|
|
213
|
+
forwardedRef,
|
|
214
|
+
// The following props are being included here to avoid
|
|
215
|
+
// passing them down to the otherProps spread
|
|
216
|
+
/* eslint-disable no-unused-vars */
|
|
217
|
+
onFocus,
|
|
218
|
+
onBlur,
|
|
219
|
+
onValidate,
|
|
220
|
+
validate,
|
|
221
|
+
onChange,
|
|
222
|
+
/* eslint-enable no-unused-vars */
|
|
223
|
+
// Should only include Aria related props
|
|
224
|
+
...otherProps
|
|
225
|
+
} = this.props;
|
|
226
|
+
return (
|
|
227
|
+
<input
|
|
228
|
+
className={css([
|
|
229
|
+
styles.input,
|
|
230
|
+
typographyStyles.LabelMedium,
|
|
231
|
+
styles.default,
|
|
232
|
+
// Prioritizes disabled, then focused, then error (if any)
|
|
233
|
+
disabled
|
|
234
|
+
? styles.disabled
|
|
235
|
+
: this.state.focused
|
|
236
|
+
? [styles.focused, light && styles.defaultLight]
|
|
237
|
+
: this.state.error && [
|
|
238
|
+
styles.error,
|
|
239
|
+
light && styles.errorLight,
|
|
240
|
+
],
|
|
241
|
+
style && style,
|
|
242
|
+
])}
|
|
243
|
+
id={id}
|
|
244
|
+
type={type}
|
|
245
|
+
placeholder={placeholder}
|
|
246
|
+
value={value}
|
|
247
|
+
disabled={disabled}
|
|
248
|
+
onChange={this.handleChange}
|
|
249
|
+
onKeyDown={onKeyDown}
|
|
250
|
+
onFocus={this.handleFocus}
|
|
251
|
+
onBlur={this.handleBlur}
|
|
252
|
+
required={required}
|
|
253
|
+
data-test-id={testId}
|
|
254
|
+
readOnly={readOnly}
|
|
255
|
+
autoComplete={autoComplete}
|
|
256
|
+
ref={forwardedRef}
|
|
257
|
+
{...otherProps}
|
|
258
|
+
/>
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const styles = StyleSheet.create({
|
|
264
|
+
input: {
|
|
265
|
+
width: "100%",
|
|
266
|
+
height: 40,
|
|
267
|
+
borderRadius: 4,
|
|
268
|
+
boxSizing: "border-box",
|
|
269
|
+
paddingLeft: Spacing.medium_16,
|
|
270
|
+
margin: 0,
|
|
271
|
+
outline: "none",
|
|
272
|
+
boxShadow: "none",
|
|
273
|
+
},
|
|
274
|
+
default: {
|
|
275
|
+
background: Color.white,
|
|
276
|
+
border: `1px solid ${Color.offBlack16}`,
|
|
277
|
+
color: Color.offBlack,
|
|
278
|
+
"::placeholder": {
|
|
279
|
+
color: Color.offBlack64,
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
error: {
|
|
283
|
+
background: `${mix(fade(Color.red, 0.06), Color.white)}`,
|
|
284
|
+
border: `1px solid ${Color.red}`,
|
|
285
|
+
color: Color.offBlack,
|
|
286
|
+
"::placeholder": {
|
|
287
|
+
color: Color.offBlack64,
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
disabled: {
|
|
291
|
+
background: Color.offWhite,
|
|
292
|
+
border: `1px solid ${Color.offBlack16}`,
|
|
293
|
+
color: Color.offBlack64,
|
|
294
|
+
"::placeholder": {
|
|
295
|
+
color: Color.offBlack32,
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
focused: {
|
|
299
|
+
background: Color.white,
|
|
300
|
+
border: `1px solid ${Color.blue}`,
|
|
301
|
+
color: Color.offBlack,
|
|
302
|
+
"::placeholder": {
|
|
303
|
+
color: Color.offBlack64,
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
defaultLight: {
|
|
307
|
+
boxShadow: `0px 0px 0px 1px ${Color.blue}, 0px 0px 0px 2px ${Color.white}`,
|
|
308
|
+
},
|
|
309
|
+
errorLight: {
|
|
310
|
+
boxShadow: `0px 0px 0px 1px ${Color.red}, 0px 0px 0px 2px ${Color.white}`,
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
type ExportProps = $Diff<
|
|
315
|
+
React.ElementConfig<typeof TextFieldInternal>,
|
|
316
|
+
WithForwardRef,
|
|
317
|
+
>;
|
|
318
|
+
|
|
319
|
+
const TextField: React.AbstractComponent<
|
|
320
|
+
ExportProps,
|
|
321
|
+
HTMLInputElement,
|
|
322
|
+
> = React.forwardRef<ExportProps, HTMLInputElement>((props, ref) => (
|
|
323
|
+
<TextFieldInternal {...props} forwardedRef={ref} />
|
|
324
|
+
));
|
|
325
|
+
|
|
326
|
+
export default TextField;
|