@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,206 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import {StyleSheet} from "aphrodite";
|
|
5
|
+
|
|
6
|
+
import Color from "@khanacademy/wonder-blocks-color";
|
|
7
|
+
import {View, UniqueIDProvider} from "@khanacademy/wonder-blocks-core";
|
|
8
|
+
import {getClickableBehavior} from "@khanacademy/wonder-blocks-clickable";
|
|
9
|
+
import {Strut} from "@khanacademy/wonder-blocks-layout";
|
|
10
|
+
import Spacing from "@khanacademy/wonder-blocks-spacing";
|
|
11
|
+
import {LabelMedium, LabelSmall} from "@khanacademy/wonder-blocks-typography";
|
|
12
|
+
import type {AriaProps, StyleType} from "@khanacademy/wonder-blocks-core";
|
|
13
|
+
import CheckboxCore from "./checkbox-core.js";
|
|
14
|
+
import RadioCore from "./radio-core.js";
|
|
15
|
+
|
|
16
|
+
type Props = {|
|
|
17
|
+
...AriaProps,
|
|
18
|
+
|
|
19
|
+
/** Whether this choice is checked. */
|
|
20
|
+
checked: boolean,
|
|
21
|
+
|
|
22
|
+
/** Whether this choice option is disabled. */
|
|
23
|
+
disabled: boolean,
|
|
24
|
+
|
|
25
|
+
/** Whether this choice is in error mode. */
|
|
26
|
+
error: boolean,
|
|
27
|
+
|
|
28
|
+
/** Returns the new checked state of the component. */
|
|
29
|
+
onChange: (newCheckedState: boolean) => mixed,
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Used for accessibility purposes, where the label id should match the
|
|
33
|
+
* input id.
|
|
34
|
+
*/
|
|
35
|
+
id?: string,
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Optional additional styling.
|
|
39
|
+
*/
|
|
40
|
+
style?: StyleType,
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Adds CSS classes to the Button.
|
|
44
|
+
*/
|
|
45
|
+
className?: string,
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Optional id for testing purposes.
|
|
49
|
+
*/
|
|
50
|
+
testId?: string,
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Label for the field.
|
|
54
|
+
*/
|
|
55
|
+
label?: string,
|
|
56
|
+
|
|
57
|
+
/** Optional description for the field. */
|
|
58
|
+
description?: string,
|
|
59
|
+
|
|
60
|
+
/** Auto-populated by parent's groupName prop if in a group. */
|
|
61
|
+
groupName?: string,
|
|
62
|
+
|
|
63
|
+
/** Takes either "radio" or "checkbox" value. */
|
|
64
|
+
variant: "radio" | "checkbox",
|
|
65
|
+
|};
|
|
66
|
+
|
|
67
|
+
type DefaultProps = {|
|
|
68
|
+
checked: $PropertyType<Props, "checked">,
|
|
69
|
+
disabled: $PropertyType<Props, "disabled">,
|
|
70
|
+
error: $PropertyType<Props, "error">,
|
|
71
|
+
|};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* This is a potentially labeled 🔘 or ☑️ item. This is an internal component
|
|
75
|
+
* that's wrapped by Checkbox and Radio. Choice is a wrapper for Checkbox and
|
|
76
|
+
* Radio with many of its props auto-populated, to be used with CheckboxGroup
|
|
77
|
+
* and RadioGroup. This design allows for more explicit prop typing. For
|
|
78
|
+
* example, we can make onChange a required prop on Checkbox but not on Choice
|
|
79
|
+
* (because for Choice, that prop would be auto-populated by CheckboxGroup).
|
|
80
|
+
*/ export default class ChoiceInternal extends React.Component<Props> {
|
|
81
|
+
static defaultProps: DefaultProps = {
|
|
82
|
+
checked: false,
|
|
83
|
+
disabled: false,
|
|
84
|
+
error: false,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
handleLabelClick: (event: SyntheticEvent<>) => void = (event) => {
|
|
88
|
+
// Browsers automatically use the for attribute to select the input,
|
|
89
|
+
// but we use ClickableBehavior to handle this.
|
|
90
|
+
event.preventDefault();
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
handleClick: () => void = () => {
|
|
94
|
+
const {checked, onChange, variant} = this.props;
|
|
95
|
+
// Radio buttons cannot be unchecked
|
|
96
|
+
if (variant === "radio" && checked) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
onChange(!checked);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
getChoiceCoreComponent(): typeof RadioCore | typeof CheckboxCore {
|
|
103
|
+
if (this.props.variant === "radio") {
|
|
104
|
+
return RadioCore;
|
|
105
|
+
} else {
|
|
106
|
+
return CheckboxCore;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
getLabel(): React.Node {
|
|
110
|
+
const {disabled, id, label} = this.props;
|
|
111
|
+
return (
|
|
112
|
+
<LabelMedium
|
|
113
|
+
style={[styles.label, disabled && styles.disabledLabel]}
|
|
114
|
+
>
|
|
115
|
+
<label htmlFor={id} onClick={this.handleLabelClick}>
|
|
116
|
+
{label}
|
|
117
|
+
</label>
|
|
118
|
+
</LabelMedium>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
getDescription(id: string | void): React.Node {
|
|
122
|
+
const {description} = this.props;
|
|
123
|
+
return (
|
|
124
|
+
<LabelSmall style={styles.description} id={id}>
|
|
125
|
+
{description}
|
|
126
|
+
</LabelSmall>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
render(): React.Node {
|
|
130
|
+
const {
|
|
131
|
+
label,
|
|
132
|
+
description,
|
|
133
|
+
// eslint-disable-next-line no-unused-vars
|
|
134
|
+
onChange,
|
|
135
|
+
style,
|
|
136
|
+
className,
|
|
137
|
+
variant,
|
|
138
|
+
...coreProps
|
|
139
|
+
} = this.props;
|
|
140
|
+
const ChoiceCore = this.getChoiceCoreComponent();
|
|
141
|
+
const ClickableBehavior = getClickableBehavior();
|
|
142
|
+
return (
|
|
143
|
+
<UniqueIDProvider mockOnFirstRender={true} scope="choice">
|
|
144
|
+
{(ids) => {
|
|
145
|
+
const descriptionId = description && ids.get("description");
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
<View style={style} className={className}>
|
|
149
|
+
<ClickableBehavior
|
|
150
|
+
disabled={coreProps.disabled}
|
|
151
|
+
onClick={this.handleClick}
|
|
152
|
+
role={variant}
|
|
153
|
+
>
|
|
154
|
+
{(state, childrenProps) => {
|
|
155
|
+
return (
|
|
156
|
+
<View
|
|
157
|
+
style={styles.wrapper}
|
|
158
|
+
{...childrenProps}
|
|
159
|
+
// We are resetting the tabIndex=0 from handlers
|
|
160
|
+
// because the ChoiceCore component will receive
|
|
161
|
+
// focus on basis of it being an input element.
|
|
162
|
+
tabIndex={-1}
|
|
163
|
+
>
|
|
164
|
+
<ChoiceCore
|
|
165
|
+
{...coreProps}
|
|
166
|
+
{...state}
|
|
167
|
+
aria-describedby={descriptionId}
|
|
168
|
+
/>
|
|
169
|
+
<Strut size={Spacing.xSmall_8} />
|
|
170
|
+
{label && this.getLabel()}
|
|
171
|
+
</View>
|
|
172
|
+
);
|
|
173
|
+
}}
|
|
174
|
+
</ClickableBehavior>
|
|
175
|
+
{description && this.getDescription(descriptionId)}
|
|
176
|
+
</View>
|
|
177
|
+
);
|
|
178
|
+
}}
|
|
179
|
+
</UniqueIDProvider>
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const styles = StyleSheet.create({
|
|
184
|
+
wrapper: {
|
|
185
|
+
flexDirection: "row",
|
|
186
|
+
alignItems: "flex-start",
|
|
187
|
+
outline: "none",
|
|
188
|
+
},
|
|
189
|
+
label: {
|
|
190
|
+
userSelect: "none",
|
|
191
|
+
// NOTE: The checkbox/radio button (height 16px) should be center
|
|
192
|
+
// aligned with the first line of the label. However, LabelMedium has a
|
|
193
|
+
// declared line height of 20px, so we need to adjust the top to get the
|
|
194
|
+
// desired alignment.
|
|
195
|
+
marginTop: -2,
|
|
196
|
+
},
|
|
197
|
+
disabledLabel: {
|
|
198
|
+
color: Color.offBlack32,
|
|
199
|
+
},
|
|
200
|
+
description: {
|
|
201
|
+
// 16 for icon + 8 for spacing strut
|
|
202
|
+
marginLeft: Spacing.medium_16 + Spacing.xSmall_8,
|
|
203
|
+
marginTop: Spacing.xxxSmall_4,
|
|
204
|
+
color: Color.offBlack64,
|
|
205
|
+
},
|
|
206
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
|
|
5
|
+
import type {AriaProps, StyleType} from "@khanacademy/wonder-blocks-core";
|
|
6
|
+
import Checkbox from "./checkbox.js";
|
|
7
|
+
import Radio from "./radio.js";
|
|
8
|
+
|
|
9
|
+
type Props = {|
|
|
10
|
+
...AriaProps,
|
|
11
|
+
|
|
12
|
+
/** User-defined. Label for the field. */
|
|
13
|
+
label: string,
|
|
14
|
+
|
|
15
|
+
/** User-defined. Optional description for the field. */
|
|
16
|
+
description?: string,
|
|
17
|
+
|
|
18
|
+
/** User-defined. Should be distinct for each item in the group. */
|
|
19
|
+
value: string,
|
|
20
|
+
|
|
21
|
+
/** User-defined. Whether this choice option is disabled. Default false. */
|
|
22
|
+
disabled: boolean,
|
|
23
|
+
|
|
24
|
+
/** User-defined. Optional id for testing purposes. */
|
|
25
|
+
testId?: string,
|
|
26
|
+
|
|
27
|
+
/** User-defined. Optional additional styling. */
|
|
28
|
+
style?: StyleType,
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Auto-populated by parent. Whether this choice is checked.
|
|
32
|
+
* @ignore
|
|
33
|
+
*/
|
|
34
|
+
checked: boolean,
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Auto-populated by parent. Whether this choice is in error mode (everything
|
|
38
|
+
* in a choice group would be in error mode at the same time).
|
|
39
|
+
* @ignore
|
|
40
|
+
*/
|
|
41
|
+
error?: boolean,
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Auto-populated by parent. Used for accessibility purposes, where the label
|
|
45
|
+
* id should match the input id.
|
|
46
|
+
* @ignore
|
|
47
|
+
*/
|
|
48
|
+
id?: string,
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Auto-populated by parent's groupName prop.
|
|
52
|
+
* @ignore
|
|
53
|
+
*/
|
|
54
|
+
groupName?: string,
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Auto-populated by parent. Returns the new checked state of the component.
|
|
58
|
+
* @ignore
|
|
59
|
+
*/
|
|
60
|
+
onChange: (newCheckedState: boolean) => mixed,
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Auto-populated by parent.
|
|
64
|
+
* @ignore
|
|
65
|
+
*/
|
|
66
|
+
variant?: "radio" | "checkbox",
|
|
67
|
+
|};
|
|
68
|
+
|
|
69
|
+
type DefaultProps = {|
|
|
70
|
+
checked: $PropertyType<Props, "checked">,
|
|
71
|
+
disabled: $PropertyType<Props, "disabled">,
|
|
72
|
+
onChange: $PropertyType<Props, "onChange">,
|
|
73
|
+
|};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* This is a labeled 🔘 or ☑️ item. Choice is meant to be used as children of
|
|
77
|
+
* CheckboxGroup and RadioGroup because many of its props are auto-populated
|
|
78
|
+
* and not shown in the documentation here. See those components for usage
|
|
79
|
+
* examples.
|
|
80
|
+
*
|
|
81
|
+
* If you wish to use just a single field, use Checkbox or Radio with the
|
|
82
|
+
* optional label and description props.
|
|
83
|
+
*/ export default class Choice extends React.Component<Props> {
|
|
84
|
+
static defaultProps: DefaultProps = {
|
|
85
|
+
checked: false,
|
|
86
|
+
disabled: false,
|
|
87
|
+
onChange: () => {},
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
getChoiceComponent(variant: ?string): typeof Radio | typeof Checkbox {
|
|
91
|
+
if (variant === "checkbox") {
|
|
92
|
+
return Checkbox;
|
|
93
|
+
} else {
|
|
94
|
+
return Radio;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
render(): React.Node {
|
|
98
|
+
// we don't need this going into the ChoiceComponent
|
|
99
|
+
// eslint-disable-next-line no-unused-vars
|
|
100
|
+
const {value, variant, ...remainingProps} = this.props;
|
|
101
|
+
const ChoiceComponent = this.getChoiceComponent(variant);
|
|
102
|
+
return <ChoiceComponent {...remainingProps} />;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import {StyleSheet} from "aphrodite";
|
|
4
|
+
|
|
5
|
+
import {View, type StyleType} from "@khanacademy/wonder-blocks-core";
|
|
6
|
+
import Color from "@khanacademy/wonder-blocks-color";
|
|
7
|
+
import {Strut} from "@khanacademy/wonder-blocks-layout";
|
|
8
|
+
import Spacing from "@khanacademy/wonder-blocks-spacing";
|
|
9
|
+
import {
|
|
10
|
+
type Typography,
|
|
11
|
+
LabelMedium,
|
|
12
|
+
LabelSmall,
|
|
13
|
+
} from "@khanacademy/wonder-blocks-typography";
|
|
14
|
+
|
|
15
|
+
type Props = {|
|
|
16
|
+
/**
|
|
17
|
+
* The form field component.
|
|
18
|
+
*/
|
|
19
|
+
field: React.Node,
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The title for the label element.
|
|
23
|
+
*/
|
|
24
|
+
label: string | React.Element<Typography>,
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The text for the description element.
|
|
28
|
+
*/
|
|
29
|
+
description?: string | React.Element<Typography>,
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The message for the error element.
|
|
33
|
+
*/
|
|
34
|
+
error?: string | React.Element<Typography>,
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Custom styles for the field heading container.
|
|
38
|
+
*/
|
|
39
|
+
style?: StyleType,
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A unique id to link the label (and optional error) to the field.
|
|
43
|
+
*
|
|
44
|
+
* The label will assume that the field will have its id formatted as `${id}-field`.
|
|
45
|
+
* The field can assume that the error will have its id formatted as `${id}-error`.
|
|
46
|
+
*/
|
|
47
|
+
id?: string,
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Optional test ID for e2e testing.
|
|
51
|
+
*/
|
|
52
|
+
testId?: string,
|
|
53
|
+
|};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* A FieldHeading is an element that provides a label, description, and error element
|
|
57
|
+
* to present better context and hints to any type of form field component.
|
|
58
|
+
*/
|
|
59
|
+
export default class FieldHeading extends React.Component<Props> {
|
|
60
|
+
renderLabel(): React.Node {
|
|
61
|
+
const {label, id, testId} = this.props;
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<React.Fragment>
|
|
65
|
+
{typeof label === "string" ? (
|
|
66
|
+
<LabelMedium
|
|
67
|
+
style={styles.label}
|
|
68
|
+
tag="label"
|
|
69
|
+
htmlFor={id && `${id}-field`}
|
|
70
|
+
testId={testId && `${testId}-label`}
|
|
71
|
+
>
|
|
72
|
+
{label}
|
|
73
|
+
</LabelMedium>
|
|
74
|
+
) : (
|
|
75
|
+
label
|
|
76
|
+
)}
|
|
77
|
+
<Strut size={Spacing.xxxSmall_4} />
|
|
78
|
+
</React.Fragment>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
maybeRenderDescription(): ?React.Node {
|
|
83
|
+
const {description, testId} = this.props;
|
|
84
|
+
|
|
85
|
+
if (!description) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<React.Fragment>
|
|
91
|
+
{typeof description === "string" ? (
|
|
92
|
+
<LabelSmall
|
|
93
|
+
style={styles.description}
|
|
94
|
+
testId={testId && `${testId}-description`}
|
|
95
|
+
>
|
|
96
|
+
{description}
|
|
97
|
+
</LabelSmall>
|
|
98
|
+
) : (
|
|
99
|
+
description
|
|
100
|
+
)}
|
|
101
|
+
<Strut size={Spacing.xxxSmall_4} />
|
|
102
|
+
</React.Fragment>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
maybeRenderError(): ?React.Node {
|
|
107
|
+
const {error, id, testId} = this.props;
|
|
108
|
+
|
|
109
|
+
if (!error) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<React.Fragment>
|
|
115
|
+
<Strut size={Spacing.small_12} />
|
|
116
|
+
{typeof error === "string" ? (
|
|
117
|
+
<LabelSmall
|
|
118
|
+
style={styles.error}
|
|
119
|
+
role="alert"
|
|
120
|
+
id={id && `${id}-error`}
|
|
121
|
+
testId={testId && `${testId}-error`}
|
|
122
|
+
>
|
|
123
|
+
{error}
|
|
124
|
+
</LabelSmall>
|
|
125
|
+
) : (
|
|
126
|
+
error
|
|
127
|
+
)}
|
|
128
|
+
</React.Fragment>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
render(): React.Node {
|
|
133
|
+
const {field, style} = this.props;
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<View style={style}>
|
|
137
|
+
{this.renderLabel()}
|
|
138
|
+
{this.maybeRenderDescription()}
|
|
139
|
+
<Strut size={Spacing.xSmall_8} />
|
|
140
|
+
{field}
|
|
141
|
+
{this.maybeRenderError()}
|
|
142
|
+
</View>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const styles = StyleSheet.create({
|
|
148
|
+
label: {
|
|
149
|
+
color: Color.offBlack,
|
|
150
|
+
},
|
|
151
|
+
description: {
|
|
152
|
+
color: Color.offBlack64,
|
|
153
|
+
},
|
|
154
|
+
error: {
|
|
155
|
+
color: Color.red,
|
|
156
|
+
},
|
|
157
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
```js
|
|
2
|
+
import {View} from "@khanacademy/wonder-blocks-core";
|
|
3
|
+
import {TextField} from "@khanacademy/wonder-blocks-form";
|
|
4
|
+
|
|
5
|
+
class FieldHeadingExample extends React.Component {
|
|
6
|
+
constructor(props) {
|
|
7
|
+
super(props);
|
|
8
|
+
this.state = {
|
|
9
|
+
value: "",
|
|
10
|
+
};
|
|
11
|
+
this.handleKeyDown = this.handleKeyDown.bind(this);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
handleKeyDown(event) {
|
|
15
|
+
if (event.key === "Enter") {
|
|
16
|
+
event.currentTarget.blur();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
render() {
|
|
21
|
+
return (
|
|
22
|
+
<FieldHeading
|
|
23
|
+
field={
|
|
24
|
+
<TextField
|
|
25
|
+
id="tf-1"
|
|
26
|
+
type="text"
|
|
27
|
+
value={this.state.value}
|
|
28
|
+
placeholder="Username"
|
|
29
|
+
onChange={(newValue) => this.setState({value: newValue})}
|
|
30
|
+
onKeyDown={this.handleKeyDown}
|
|
31
|
+
/>
|
|
32
|
+
}
|
|
33
|
+
label="Username"
|
|
34
|
+
description="Please enter your username."
|
|
35
|
+
error="That username is already taken."
|
|
36
|
+
/>
|
|
37
|
+
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
<FieldHeadingExample />
|
|
43
|
+
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import {StyleSheet} from "aphrodite";
|
|
3
|
+
|
|
4
|
+
import Color from "@khanacademy/wonder-blocks-color";
|
|
5
|
+
import Spacing from "@khanacademy/wonder-blocks-spacing";
|
|
6
|
+
|
|
7
|
+
import type {StyleDeclaration} from "aphrodite";
|
|
8
|
+
|
|
9
|
+
const styles: StyleDeclaration = StyleSheet.create({
|
|
10
|
+
fieldset: {
|
|
11
|
+
border: "none",
|
|
12
|
+
padding: 0,
|
|
13
|
+
margin: 0,
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
legend: {
|
|
17
|
+
padding: 0,
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
description: {
|
|
21
|
+
marginTop: Spacing.xxxSmall_4,
|
|
22
|
+
color: Color.offBlack64,
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
error: {
|
|
26
|
+
marginTop: Spacing.xxxSmall_4,
|
|
27
|
+
color: Color.red,
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
defaultLineGap: {
|
|
31
|
+
marginTop: Spacing.xSmall_8,
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export default styles;
|