@khanacademy/wonder-blocks-form 4.0.7 → 4.0.9
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/components/checkbox-core.d.ts +1 -8
- package/dist/components/checkbox-core.js.flow +4 -10
- package/dist/components/choice-internal.d.ts +1 -2
- package/dist/components/choice-internal.js.flow +1 -2
- package/dist/components/radio-core.d.ts +1 -8
- package/dist/components/radio-core.js.flow +4 -10
- package/dist/es/index.js +84 -85
- package/dist/index.js +83 -84
- package/dist/util/types.d.ts +2 -0
- package/dist/util/types.js.flow +5 -0
- package/package.json +7 -7
- package/src/__tests__/__snapshots__/custom-snapshot.test.tsx.snap +168 -958
- package/src/__tests__/custom-snapshot.test.tsx +28 -43
- package/src/components/__tests__/checkbox.test.js +84 -0
- package/src/components/__tests__/labeled-text-field.test.tsx +4 -1
- package/src/components/checkbox-core.tsx +37 -34
- package/src/components/checkbox-group.tsx +2 -2
- package/src/components/choice-internal.tsx +31 -39
- package/src/components/radio-core.tsx +37 -34
- package/src/components/radio-group.tsx +2 -2
- package/src/components/text-field.tsx +13 -8
- package/src/util/types.ts +2 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -5,32 +5,24 @@ import CheckboxCore from "../components/checkbox-core";
|
|
|
5
5
|
import RadioCore from "../components/radio-core";
|
|
6
6
|
|
|
7
7
|
const states = ["default", "error", "disabled"];
|
|
8
|
-
const clickableStates = ["default", "hovered", "pressed"];
|
|
9
8
|
const checkedStates = [false, true];
|
|
10
9
|
|
|
11
10
|
describe("CheckboxCore", () => {
|
|
12
11
|
states.forEach((state: any) => {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
focused={clickableState === "focused"}
|
|
28
|
-
waiting={false}
|
|
29
|
-
/>,
|
|
30
|
-
)
|
|
31
|
-
.toJSON();
|
|
32
|
-
expect(tree).toMatchSnapshot();
|
|
33
|
-
});
|
|
12
|
+
checkedStates.forEach((checked: any) => {
|
|
13
|
+
test(`type:${state} checked:${String(checked)}`, () => {
|
|
14
|
+
const disabled = state === "disabled";
|
|
15
|
+
const tree = renderer
|
|
16
|
+
.create(
|
|
17
|
+
<CheckboxCore
|
|
18
|
+
checked={checked}
|
|
19
|
+
disabled={disabled}
|
|
20
|
+
error={state === "error"}
|
|
21
|
+
onClick={() => {}}
|
|
22
|
+
/>,
|
|
23
|
+
)
|
|
24
|
+
.toJSON();
|
|
25
|
+
expect(tree).toMatchSnapshot();
|
|
34
26
|
});
|
|
35
27
|
});
|
|
36
28
|
});
|
|
@@ -38,27 +30,20 @@ describe("CheckboxCore", () => {
|
|
|
38
30
|
|
|
39
31
|
describe("RadioCore", () => {
|
|
40
32
|
states.forEach((state: any) => {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
focused={clickableState === "focused"}
|
|
56
|
-
waiting={false}
|
|
57
|
-
/>,
|
|
58
|
-
)
|
|
59
|
-
.toJSON();
|
|
60
|
-
expect(tree).toMatchSnapshot();
|
|
61
|
-
});
|
|
33
|
+
checkedStates.forEach((checked: any) => {
|
|
34
|
+
test(`type:${state} checked:${String(checked)}`, () => {
|
|
35
|
+
const disabled = state === "disabled";
|
|
36
|
+
const tree = renderer
|
|
37
|
+
.create(
|
|
38
|
+
<RadioCore
|
|
39
|
+
checked={checked}
|
|
40
|
+
disabled={disabled}
|
|
41
|
+
error={state === "error"}
|
|
42
|
+
onClick={() => {}}
|
|
43
|
+
/>,
|
|
44
|
+
)
|
|
45
|
+
.toJSON();
|
|
46
|
+
expect(tree).toMatchSnapshot();
|
|
62
47
|
});
|
|
63
48
|
});
|
|
64
49
|
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
//@flow
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import {render, screen} from "@testing-library/react";
|
|
4
|
+
|
|
5
|
+
import Checkbox from "../checkbox";
|
|
6
|
+
|
|
7
|
+
describe("Checkbox", () => {
|
|
8
|
+
test("uses the ID prop when it is specified", () => {
|
|
9
|
+
// Arrange, Act
|
|
10
|
+
render(
|
|
11
|
+
<Checkbox
|
|
12
|
+
id="specified-checkbox-id"
|
|
13
|
+
label="Receive assignment reminders for Algebra"
|
|
14
|
+
description="You will receive a reminder 24 hours before each deadline"
|
|
15
|
+
checked={false}
|
|
16
|
+
onChange={() => {}}
|
|
17
|
+
/>,
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
const checkbox = screen.getByRole("checkbox");
|
|
21
|
+
|
|
22
|
+
// Assert
|
|
23
|
+
expect(checkbox).toHaveAttribute("id", "specified-checkbox-id");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("provides a unique ID when the ID prop is not specified", () => {
|
|
27
|
+
// Arrange, Act
|
|
28
|
+
render(
|
|
29
|
+
<Checkbox
|
|
30
|
+
label="Receive assignment reminders for Algebra"
|
|
31
|
+
description="You will receive a reminder 24 hours before each deadline"
|
|
32
|
+
checked={false}
|
|
33
|
+
onChange={() => {}}
|
|
34
|
+
/>,
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const checkbox = screen.getByRole("checkbox");
|
|
38
|
+
|
|
39
|
+
// Assert
|
|
40
|
+
expect(checkbox).toHaveAttribute("id", "uid-choice-1-main");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("clicking the checkbox triggers `onChange`", () => {
|
|
44
|
+
// Arrange
|
|
45
|
+
const onChangeSpy = jest.fn();
|
|
46
|
+
render(
|
|
47
|
+
<Checkbox
|
|
48
|
+
label="Receive assignment reminders for Algebra"
|
|
49
|
+
description="You will receive a reminder 24 hours before each deadline"
|
|
50
|
+
checked={false}
|
|
51
|
+
onChange={onChangeSpy}
|
|
52
|
+
/>,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
// Act
|
|
56
|
+
const checkbox = screen.getByRole("checkbox");
|
|
57
|
+
checkbox.click();
|
|
58
|
+
|
|
59
|
+
// Assert
|
|
60
|
+
expect(onChangeSpy).toHaveBeenCalled();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("clicks the label triggers `onChange`", () => {
|
|
64
|
+
// Arrange
|
|
65
|
+
const onChangeSpy = jest.fn();
|
|
66
|
+
render(
|
|
67
|
+
<Checkbox
|
|
68
|
+
label="Receive assignment reminders for Algebra"
|
|
69
|
+
description="You will receive a reminder 24 hours before each deadline"
|
|
70
|
+
checked={false}
|
|
71
|
+
onChange={onChangeSpy}
|
|
72
|
+
/>,
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// Act
|
|
76
|
+
const checkboxLabel = screen.getByText(
|
|
77
|
+
"Receive assignment reminders for Algebra",
|
|
78
|
+
);
|
|
79
|
+
checkboxLabel.click();
|
|
80
|
+
|
|
81
|
+
// Assert
|
|
82
|
+
expect(onChangeSpy).toHaveBeenCalled();
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -3,6 +3,7 @@ import {render, screen, fireEvent} from "@testing-library/react";
|
|
|
3
3
|
import userEvent from "@testing-library/user-event";
|
|
4
4
|
|
|
5
5
|
import {StyleSheet} from "aphrodite";
|
|
6
|
+
import Color from "@khanacademy/wonder-blocks-color";
|
|
6
7
|
import LabeledTextField from "../labeled-text-field";
|
|
7
8
|
|
|
8
9
|
describe("LabeledTextField", () => {
|
|
@@ -392,7 +393,9 @@ describe("LabeledTextField", () => {
|
|
|
392
393
|
textField.focus();
|
|
393
394
|
|
|
394
395
|
// Assert
|
|
395
|
-
expect(textField
|
|
396
|
+
expect(textField).toHaveStyle({
|
|
397
|
+
boxShadow: `0px 0px 0px 1px ${Color.blue}, 0px 0px 0px 2px ${Color.white}`,
|
|
398
|
+
});
|
|
396
399
|
});
|
|
397
400
|
|
|
398
401
|
it("style prop is passed to fieldheading", async () => {
|
|
@@ -9,13 +9,6 @@ import type {IconAsset} from "@khanacademy/wonder-blocks-icon";
|
|
|
9
9
|
|
|
10
10
|
import type {ChoiceCoreProps} from "../util/types";
|
|
11
11
|
|
|
12
|
-
type Props = ChoiceCoreProps & {
|
|
13
|
-
hovered: boolean;
|
|
14
|
-
focused: boolean;
|
|
15
|
-
pressed: boolean;
|
|
16
|
-
waiting: boolean;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
12
|
const {blue, red, white, offWhite, offBlack16, offBlack32, offBlack50} = Color;
|
|
20
13
|
|
|
21
14
|
const StyledInput = addStyle("input");
|
|
@@ -27,7 +20,7 @@ const checkboxCheck: IconAsset = {
|
|
|
27
20
|
/**
|
|
28
21
|
* The internal stateless ☑️ Checkbox
|
|
29
22
|
*/
|
|
30
|
-
export default class CheckboxCore extends React.Component<
|
|
23
|
+
export default class CheckboxCore extends React.Component<ChoiceCoreProps> {
|
|
31
24
|
handleChange: () => void = () => {
|
|
32
25
|
// Empty because change is handled by ClickableBehavior
|
|
33
26
|
return;
|
|
@@ -41,10 +34,6 @@ export default class CheckboxCore extends React.Component<Props> {
|
|
|
41
34
|
groupName,
|
|
42
35
|
id,
|
|
43
36
|
testId,
|
|
44
|
-
hovered,
|
|
45
|
-
focused,
|
|
46
|
-
pressed,
|
|
47
|
-
waiting: _,
|
|
48
37
|
...sharedProps
|
|
49
38
|
} = this.props;
|
|
50
39
|
|
|
@@ -53,11 +42,7 @@ export default class CheckboxCore extends React.Component<Props> {
|
|
|
53
42
|
const defaultStyle = [
|
|
54
43
|
sharedStyles.inputReset,
|
|
55
44
|
sharedStyles.default,
|
|
56
|
-
stateStyles.default,
|
|
57
|
-
!disabled &&
|
|
58
|
-
(pressed
|
|
59
|
-
? stateStyles.active
|
|
60
|
-
: (hovered || focused) && stateStyles.focus),
|
|
45
|
+
!disabled && stateStyles.default,
|
|
61
46
|
disabled && sharedStyles.disabled,
|
|
62
47
|
];
|
|
63
48
|
|
|
@@ -165,13 +150,21 @@ const _generateStyles = (checked: boolean, error: boolean) => {
|
|
|
165
150
|
default: {
|
|
166
151
|
backgroundColor: palette.base,
|
|
167
152
|
borderWidth: 0,
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
153
|
+
|
|
154
|
+
// Focus and hover have the same style. Focus style only shows
|
|
155
|
+
// up with keyboard navigation.
|
|
156
|
+
":focus-visible": {
|
|
157
|
+
boxShadow: `0 0 0 1px ${white}, 0 0 0 3px ${palette.base}`,
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
":hover": {
|
|
161
|
+
boxShadow: `0 0 0 1px ${white}, 0 0 0 3px ${palette.base}`,
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
":active": {
|
|
165
|
+
boxShadow: `0 0 0 1px ${white}, 0 0 0 3px ${palette.active}`,
|
|
166
|
+
background: palette.active,
|
|
167
|
+
},
|
|
175
168
|
},
|
|
176
169
|
};
|
|
177
170
|
} else {
|
|
@@ -179,16 +172,26 @@ const _generateStyles = (checked: boolean, error: boolean) => {
|
|
|
179
172
|
default: {
|
|
180
173
|
backgroundColor: error ? fadedRed : white,
|
|
181
174
|
borderColor: error ? red : offBlack50,
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
175
|
+
|
|
176
|
+
// Focus and hover have the same style. Focus style only shows
|
|
177
|
+
// up with keyboard navigation.
|
|
178
|
+
":focus-visible": {
|
|
179
|
+
backgroundColor: error ? fadedRed : white,
|
|
180
|
+
borderColor: palette.base,
|
|
181
|
+
borderWidth: 2,
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
":hover": {
|
|
185
|
+
backgroundColor: error ? fadedRed : white,
|
|
186
|
+
borderColor: palette.base,
|
|
187
|
+
borderWidth: 2,
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
":active": {
|
|
191
|
+
backgroundColor: palette.faded,
|
|
192
|
+
borderColor: error ? activeRed : blue,
|
|
193
|
+
borderWidth: 2,
|
|
194
|
+
},
|
|
192
195
|
},
|
|
193
196
|
};
|
|
194
197
|
}
|
|
@@ -59,8 +59,8 @@ type CheckboxGroupProps = {
|
|
|
59
59
|
testId?: string;
|
|
60
60
|
};
|
|
61
61
|
|
|
62
|
-
const StyledFieldset = addStyle
|
|
63
|
-
const StyledLegend = addStyle
|
|
62
|
+
const StyledFieldset = addStyle("fieldset");
|
|
63
|
+
const StyledLegend = addStyle("legend");
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
66
|
* A checkbox group allows multiple selection. This component auto-populates
|
|
@@ -3,7 +3,6 @@ import {StyleSheet} from "aphrodite";
|
|
|
3
3
|
|
|
4
4
|
import Color from "@khanacademy/wonder-blocks-color";
|
|
5
5
|
import {View, UniqueIDProvider} from "@khanacademy/wonder-blocks-core";
|
|
6
|
-
import {getClickableBehavior} from "@khanacademy/wonder-blocks-clickable";
|
|
7
6
|
import {Strut} from "@khanacademy/wonder-blocks-layout";
|
|
8
7
|
import Spacing from "@khanacademy/wonder-blocks-spacing";
|
|
9
8
|
import {LabelMedium, LabelSmall} from "@khanacademy/wonder-blocks-typography";
|
|
@@ -69,12 +68,6 @@ type DefaultProps = {
|
|
|
69
68
|
error: false,
|
|
70
69
|
};
|
|
71
70
|
|
|
72
|
-
handleLabelClick: (event: React.SyntheticEvent) => void = (event) => {
|
|
73
|
-
// Browsers automatically use the for attribute to select the input,
|
|
74
|
-
// but we use ClickableBehavior to handle this.
|
|
75
|
-
event.preventDefault();
|
|
76
|
-
};
|
|
77
|
-
|
|
78
71
|
handleClick: () => void = () => {
|
|
79
72
|
const {checked, onChange, variant} = this.props;
|
|
80
73
|
// Radio buttons cannot be unchecked
|
|
@@ -91,15 +84,13 @@ type DefaultProps = {
|
|
|
91
84
|
return CheckboxCore;
|
|
92
85
|
}
|
|
93
86
|
}
|
|
94
|
-
getLabel(): React.ReactNode {
|
|
95
|
-
const {disabled,
|
|
87
|
+
getLabel(id: string): React.ReactNode {
|
|
88
|
+
const {disabled, label} = this.props;
|
|
96
89
|
return (
|
|
97
90
|
<LabelMedium
|
|
98
91
|
style={[styles.label, disabled && styles.disabledLabel]}
|
|
99
92
|
>
|
|
100
|
-
<label htmlFor={id}
|
|
101
|
-
{label}
|
|
102
|
-
</label>
|
|
93
|
+
<label htmlFor={id}>{label}</label>
|
|
103
94
|
</LabelMedium>
|
|
104
95
|
);
|
|
105
96
|
}
|
|
@@ -115,18 +106,30 @@ type DefaultProps = {
|
|
|
115
106
|
const {
|
|
116
107
|
label,
|
|
117
108
|
description,
|
|
109
|
+
id,
|
|
118
110
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
119
111
|
onChange,
|
|
120
112
|
style,
|
|
121
113
|
className,
|
|
114
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
122
115
|
variant,
|
|
123
116
|
...coreProps
|
|
124
117
|
} = this.props;
|
|
125
118
|
const ChoiceCore = this.getChoiceCoreComponent();
|
|
126
|
-
|
|
119
|
+
|
|
127
120
|
return (
|
|
128
121
|
<UniqueIDProvider mockOnFirstRender={true} scope="choice">
|
|
129
122
|
{(ids) => {
|
|
123
|
+
// A choice element should always have a unique ID set
|
|
124
|
+
// so that the label can always refer to this element.
|
|
125
|
+
// This guarantees that clicking on the label will
|
|
126
|
+
// always click on the choice as well. If an ID is
|
|
127
|
+
// passed in as a prop, use that one. Otherwise,
|
|
128
|
+
// create a unique ID using the provider.
|
|
129
|
+
const uniqueId = id || ids.get("main");
|
|
130
|
+
|
|
131
|
+
// Create a unique ID for the description section to be
|
|
132
|
+
// used by this element's `aria-describedby`.
|
|
130
133
|
const descriptionId = description
|
|
131
134
|
? ids.get("description")
|
|
132
135
|
: undefined;
|
|
@@ -134,32 +137,22 @@ type DefaultProps = {
|
|
|
134
137
|
return (
|
|
135
138
|
// @ts-expect-error [FEI-5019] - TS2769 - No overload matches this call.
|
|
136
139
|
<View style={style} className={className}>
|
|
137
|
-
<
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
140
|
+
<View
|
|
141
|
+
style={styles.wrapper}
|
|
142
|
+
// We are resetting the tabIndex=0 from handlers
|
|
143
|
+
// because the ChoiceCore component will receive
|
|
144
|
+
// focus on basis of it being an input element.
|
|
145
|
+
tabIndex={-1}
|
|
141
146
|
>
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
>
|
|
152
|
-
<ChoiceCore
|
|
153
|
-
{...coreProps}
|
|
154
|
-
{...state}
|
|
155
|
-
aria-describedby={descriptionId}
|
|
156
|
-
/>
|
|
157
|
-
<Strut size={Spacing.xSmall_8} />
|
|
158
|
-
{label && this.getLabel()}
|
|
159
|
-
</View>
|
|
160
|
-
);
|
|
161
|
-
}}
|
|
162
|
-
</ClickableBehavior>
|
|
147
|
+
<ChoiceCore
|
|
148
|
+
{...coreProps}
|
|
149
|
+
id={uniqueId}
|
|
150
|
+
aria-describedby={descriptionId}
|
|
151
|
+
onClick={this.handleClick}
|
|
152
|
+
/>
|
|
153
|
+
<Strut size={Spacing.xSmall_8} />
|
|
154
|
+
{label && this.getLabel(uniqueId)}
|
|
155
|
+
</View>
|
|
163
156
|
{description && this.getDescription(descriptionId)}
|
|
164
157
|
</View>
|
|
165
158
|
);
|
|
@@ -175,7 +168,6 @@ const styles = StyleSheet.create({
|
|
|
175
168
|
outline: "none",
|
|
176
169
|
},
|
|
177
170
|
label: {
|
|
178
|
-
userSelect: "none",
|
|
179
171
|
// NOTE: The checkbox/radio button (height 16px) should be center
|
|
180
172
|
// aligned with the first line of the label. However, LabelMedium has a
|
|
181
173
|
// declared line height of 20px, so we need to adjust the top to get the
|
|
@@ -6,20 +6,13 @@ import {addStyle} from "@khanacademy/wonder-blocks-core";
|
|
|
6
6
|
|
|
7
7
|
import type {ChoiceCoreProps} from "../util/types";
|
|
8
8
|
|
|
9
|
-
type Props = ChoiceCoreProps & {
|
|
10
|
-
hovered: boolean;
|
|
11
|
-
focused: boolean;
|
|
12
|
-
pressed: boolean;
|
|
13
|
-
waiting: boolean;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
9
|
const {blue, red, white, offWhite, offBlack16, offBlack32, offBlack50} = Color;
|
|
17
10
|
|
|
18
11
|
const StyledInput = addStyle("input");
|
|
19
12
|
|
|
20
13
|
/**
|
|
21
14
|
* The internal stateless 🔘 Radio button
|
|
22
|
-
*/ export default class RadioCore extends React.Component<
|
|
15
|
+
*/ export default class RadioCore extends React.Component<ChoiceCoreProps> {
|
|
23
16
|
handleChange: () => void = () => {
|
|
24
17
|
// Empty because change is handled by ClickableBehavior
|
|
25
18
|
return;
|
|
@@ -33,21 +26,13 @@ const StyledInput = addStyle("input");
|
|
|
33
26
|
groupName,
|
|
34
27
|
id,
|
|
35
28
|
testId,
|
|
36
|
-
hovered,
|
|
37
|
-
focused,
|
|
38
|
-
pressed,
|
|
39
|
-
waiting: _,
|
|
40
29
|
...sharedProps
|
|
41
30
|
} = this.props;
|
|
42
31
|
const stateStyles = _generateStyles(checked, error);
|
|
43
32
|
const defaultStyle = [
|
|
44
33
|
sharedStyles.inputReset,
|
|
45
34
|
sharedStyles.default,
|
|
46
|
-
stateStyles.default,
|
|
47
|
-
!disabled &&
|
|
48
|
-
(pressed
|
|
49
|
-
? stateStyles.active
|
|
50
|
-
: (hovered || focused) && stateStyles.focus),
|
|
35
|
+
!disabled && stateStyles.default,
|
|
51
36
|
disabled && sharedStyles.disabled,
|
|
52
37
|
];
|
|
53
38
|
const props = {
|
|
@@ -141,13 +126,21 @@ const _generateStyles = (checked: boolean, error: boolean) => {
|
|
|
141
126
|
backgroundColor: white,
|
|
142
127
|
borderColor: palette.base,
|
|
143
128
|
borderWidth: size / 4,
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
129
|
+
|
|
130
|
+
// Focus and hover have the same style. Focus style only shows
|
|
131
|
+
// up with keyboard navigation.
|
|
132
|
+
":focus-visible": {
|
|
133
|
+
boxShadow: `0 0 0 1px ${white}, 0 0 0 3px ${palette.base}`,
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
":hover": {
|
|
137
|
+
boxShadow: `0 0 0 1px ${white}, 0 0 0 3px ${palette.base}`,
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
":active": {
|
|
141
|
+
boxShadow: `0 0 0 1px ${white}, 0 0 0 3px ${palette.active}`,
|
|
142
|
+
borderColor: palette.active,
|
|
143
|
+
},
|
|
151
144
|
},
|
|
152
145
|
};
|
|
153
146
|
} else {
|
|
@@ -155,16 +148,26 @@ const _generateStyles = (checked: boolean, error: boolean) => {
|
|
|
155
148
|
default: {
|
|
156
149
|
backgroundColor: error ? fadedRed : white,
|
|
157
150
|
borderColor: error ? red : offBlack50,
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
151
|
+
|
|
152
|
+
// Focus and hover have the same style. Focus style only shows
|
|
153
|
+
// up with keyboard navigation.
|
|
154
|
+
":focus-visible": {
|
|
155
|
+
backgroundColor: error ? fadedRed : white,
|
|
156
|
+
borderColor: palette.base,
|
|
157
|
+
borderWidth: 2,
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
":hover": {
|
|
161
|
+
backgroundColor: error ? fadedRed : white,
|
|
162
|
+
borderColor: palette.base,
|
|
163
|
+
borderWidth: 2,
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
":active": {
|
|
167
|
+
backgroundColor: palette.faded,
|
|
168
|
+
borderColor: error ? activeRed : blue,
|
|
169
|
+
borderWidth: 2,
|
|
170
|
+
},
|
|
168
171
|
},
|
|
169
172
|
};
|
|
170
173
|
}
|
|
@@ -58,8 +58,8 @@ type RadioGroupProps = {
|
|
|
58
58
|
testId?: string;
|
|
59
59
|
};
|
|
60
60
|
|
|
61
|
-
const StyledFieldset = addStyle
|
|
62
|
-
const StyledLegend = addStyle
|
|
61
|
+
const StyledFieldset = addStyle("fieldset");
|
|
62
|
+
const StyledLegend = addStyle("legend");
|
|
63
63
|
|
|
64
64
|
/**
|
|
65
65
|
* A radio group allows only single selection. Like CheckboxGroup, this
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import {StyleSheet
|
|
2
|
+
import {StyleSheet} from "aphrodite";
|
|
3
3
|
|
|
4
4
|
import Color, {mix, fade} from "@khanacademy/wonder-blocks-color";
|
|
5
|
+
import {addStyle} from "@khanacademy/wonder-blocks-core";
|
|
5
6
|
import Spacing from "@khanacademy/wonder-blocks-spacing";
|
|
6
7
|
import {styles as typographyStyles} from "@khanacademy/wonder-blocks-typography";
|
|
8
|
+
|
|
7
9
|
import type {StyleType, AriaProps} from "@khanacademy/wonder-blocks-core";
|
|
8
10
|
|
|
9
11
|
export type TextFieldType = "text" | "password" | "email" | "number" | "tel";
|
|
@@ -14,6 +16,8 @@ type WithForwardRef = {
|
|
|
14
16
|
|
|
15
17
|
const defaultErrorMessage = "This field is required.";
|
|
16
18
|
|
|
19
|
+
const StyledInput = addStyle("input");
|
|
20
|
+
|
|
17
21
|
type Props = AriaProps & {
|
|
18
22
|
/**
|
|
19
23
|
* The unique identifier for the input.
|
|
@@ -233,12 +237,10 @@ class TextField extends React.Component<PropsWithForwardRef, State> {
|
|
|
233
237
|
// Should only include Aria related props
|
|
234
238
|
...otherProps
|
|
235
239
|
} = this.props;
|
|
240
|
+
|
|
236
241
|
return (
|
|
237
|
-
<
|
|
238
|
-
|
|
239
|
-
// here b/c `style` allows nested arrays of styles, but `css()`
|
|
240
|
-
// only allows a flat array.
|
|
241
|
-
className={css([
|
|
242
|
+
<StyledInput
|
|
243
|
+
style={[
|
|
242
244
|
styles.input,
|
|
243
245
|
typographyStyles.LabelMedium,
|
|
244
246
|
styles.default,
|
|
@@ -247,12 +249,15 @@ class TextField extends React.Component<PropsWithForwardRef, State> {
|
|
|
247
249
|
? styles.disabled
|
|
248
250
|
: this.state.focused
|
|
249
251
|
? [styles.focused, light && styles.defaultLight]
|
|
250
|
-
: this.state.error && [
|
|
252
|
+
: !!this.state.error && [
|
|
251
253
|
styles.error,
|
|
252
254
|
light && styles.errorLight,
|
|
253
255
|
],
|
|
256
|
+
// Cast `this.state.error` into boolean since it's being
|
|
257
|
+
// used as a conditional
|
|
258
|
+
!!this.state.error && styles.error,
|
|
254
259
|
style && style,
|
|
255
|
-
]
|
|
260
|
+
]}
|
|
256
261
|
id={id}
|
|
257
262
|
type={type}
|
|
258
263
|
placeholder={placeholder}
|
package/src/util/types.ts
CHANGED