@khanacademy/wonder-blocks-form 4.9.0 → 4.9.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/CHANGELOG.md +13 -0
- package/dist/components/field-heading.d.ts +4 -0
- package/dist/components/text-field.d.ts +1 -4
- package/dist/es/index.js +101 -38
- package/dist/index.js +101 -38
- package/package.json +6 -6
- package/src/components/__tests__/labeled-text-field.test.tsx +0 -23
- package/src/components/field-heading.tsx +26 -7
- package/src/components/labeled-text-field.tsx +1 -0
- package/src/components/text-area.tsx +3 -3
- package/src/components/text-field.tsx +82 -44
- package/tsconfig-build.tsbuildinfo +1 -1
|
@@ -42,6 +42,10 @@ type Props = {
|
|
|
42
42
|
* Optional test ID for e2e testing.
|
|
43
43
|
*/
|
|
44
44
|
testId?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Change the field’s sub-components to fit a dark background.
|
|
47
|
+
*/
|
|
48
|
+
light?: boolean;
|
|
45
49
|
};
|
|
46
50
|
|
|
47
51
|
const StyledSpan = addStyle("span");
|
|
@@ -52,10 +56,13 @@ const StyledSpan = addStyle("span");
|
|
|
52
56
|
*/
|
|
53
57
|
export default class FieldHeading extends React.Component<Props> {
|
|
54
58
|
renderLabel(): React.ReactNode {
|
|
55
|
-
const {label, id, required, testId} = this.props;
|
|
59
|
+
const {label, id, required, testId, light} = this.props;
|
|
56
60
|
|
|
57
61
|
const requiredIcon = (
|
|
58
|
-
<StyledSpan
|
|
62
|
+
<StyledSpan
|
|
63
|
+
style={light ? styles.lightRequired : styles.required}
|
|
64
|
+
aria-hidden={true}
|
|
65
|
+
>
|
|
59
66
|
{" "}
|
|
60
67
|
*
|
|
61
68
|
</StyledSpan>
|
|
@@ -64,7 +71,7 @@ export default class FieldHeading extends React.Component<Props> {
|
|
|
64
71
|
return (
|
|
65
72
|
<React.Fragment>
|
|
66
73
|
<LabelMedium
|
|
67
|
-
style={styles.label}
|
|
74
|
+
style={light ? styles.lightLabel : styles.label}
|
|
68
75
|
tag="label"
|
|
69
76
|
htmlFor={id && `${id}-field`}
|
|
70
77
|
testId={testId && `${testId}-label`}
|
|
@@ -78,7 +85,7 @@ export default class FieldHeading extends React.Component<Props> {
|
|
|
78
85
|
}
|
|
79
86
|
|
|
80
87
|
maybeRenderDescription(): React.ReactNode | null | undefined {
|
|
81
|
-
const {description, testId} = this.props;
|
|
88
|
+
const {description, testId, light} = this.props;
|
|
82
89
|
|
|
83
90
|
if (!description) {
|
|
84
91
|
return null;
|
|
@@ -87,7 +94,7 @@ export default class FieldHeading extends React.Component<Props> {
|
|
|
87
94
|
return (
|
|
88
95
|
<React.Fragment>
|
|
89
96
|
<LabelSmall
|
|
90
|
-
style={styles.description}
|
|
97
|
+
style={light ? styles.lightDescription : styles.description}
|
|
91
98
|
testId={testId && `${testId}-description`}
|
|
92
99
|
>
|
|
93
100
|
{description}
|
|
@@ -98,7 +105,7 @@ export default class FieldHeading extends React.Component<Props> {
|
|
|
98
105
|
}
|
|
99
106
|
|
|
100
107
|
maybeRenderError(): React.ReactNode | null | undefined {
|
|
101
|
-
const {error, id, testId} = this.props;
|
|
108
|
+
const {error, id, testId, light} = this.props;
|
|
102
109
|
|
|
103
110
|
if (!error) {
|
|
104
111
|
return null;
|
|
@@ -108,7 +115,7 @@ export default class FieldHeading extends React.Component<Props> {
|
|
|
108
115
|
<React.Fragment>
|
|
109
116
|
<Strut size={spacing.small_12} />
|
|
110
117
|
<LabelSmall
|
|
111
|
-
style={styles.error}
|
|
118
|
+
style={light ? styles.lightError : styles.error}
|
|
112
119
|
role="alert"
|
|
113
120
|
id={id && `${id}-error`}
|
|
114
121
|
testId={testId && `${testId}-error`}
|
|
@@ -138,13 +145,25 @@ const styles = StyleSheet.create({
|
|
|
138
145
|
label: {
|
|
139
146
|
color: color.offBlack,
|
|
140
147
|
},
|
|
148
|
+
lightLabel: {
|
|
149
|
+
color: color.white,
|
|
150
|
+
},
|
|
141
151
|
description: {
|
|
142
152
|
color: color.offBlack64,
|
|
143
153
|
},
|
|
154
|
+
lightDescription: {
|
|
155
|
+
color: color.white64,
|
|
156
|
+
},
|
|
144
157
|
error: {
|
|
145
158
|
color: color.red,
|
|
146
159
|
},
|
|
160
|
+
lightError: {
|
|
161
|
+
color: color.fadedRed,
|
|
162
|
+
},
|
|
147
163
|
required: {
|
|
148
164
|
color: color.red,
|
|
149
165
|
},
|
|
166
|
+
lightRequired: {
|
|
167
|
+
color: color.fadedRed,
|
|
168
|
+
},
|
|
150
169
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import {
|
|
2
|
+
import {StyleSheet} from "aphrodite";
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
AriaProps,
|
|
@@ -256,7 +256,7 @@ const TextArea = React.forwardRef<HTMLTextAreaElement, TextAreaProps>(
|
|
|
256
256
|
}
|
|
257
257
|
});
|
|
258
258
|
|
|
259
|
-
const getStyles = ():
|
|
259
|
+
const getStyles = (): StyleType => {
|
|
260
260
|
// Base styles are the styles that apply regardless of light mode
|
|
261
261
|
const baseStyles = [
|
|
262
262
|
styles.textarea,
|
|
@@ -284,7 +284,7 @@ const TextArea = React.forwardRef<HTMLTextAreaElement, TextAreaProps>(
|
|
|
284
284
|
data-testid={testId}
|
|
285
285
|
ref={ref}
|
|
286
286
|
className={className}
|
|
287
|
-
style={[
|
|
287
|
+
style={[getStyles(), style]}
|
|
288
288
|
value={value}
|
|
289
289
|
onChange={handleChange}
|
|
290
290
|
placeholder={placeholder}
|
|
@@ -2,7 +2,7 @@ import * as React from "react";
|
|
|
2
2
|
import {StyleSheet} from "aphrodite";
|
|
3
3
|
|
|
4
4
|
import {IDProvider, addStyle} from "@khanacademy/wonder-blocks-core";
|
|
5
|
-
import {color, spacing} from "@khanacademy/wonder-blocks-tokens";
|
|
5
|
+
import {border, color, mix, spacing} from "@khanacademy/wonder-blocks-tokens";
|
|
6
6
|
import {styles as typographyStyles} from "@khanacademy/wonder-blocks-typography";
|
|
7
7
|
|
|
8
8
|
import type {StyleType, AriaProps} from "@khanacademy/wonder-blocks-core";
|
|
@@ -152,10 +152,6 @@ type State = {
|
|
|
152
152
|
* Displayed when the validation fails.
|
|
153
153
|
*/
|
|
154
154
|
error: string | null | undefined;
|
|
155
|
-
/**
|
|
156
|
-
* The user focuses on this field.
|
|
157
|
-
*/
|
|
158
|
-
focused: boolean;
|
|
159
155
|
};
|
|
160
156
|
|
|
161
157
|
/**
|
|
@@ -178,7 +174,6 @@ class TextField extends React.Component<PropsWithForwardRef, State> {
|
|
|
178
174
|
|
|
179
175
|
state: State = {
|
|
180
176
|
error: null,
|
|
181
|
-
focused: false,
|
|
182
177
|
};
|
|
183
178
|
|
|
184
179
|
componentDidMount() {
|
|
@@ -222,22 +217,38 @@ class TextField extends React.Component<PropsWithForwardRef, State> {
|
|
|
222
217
|
event,
|
|
223
218
|
) => {
|
|
224
219
|
const {onFocus} = this.props;
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
}
|
|
229
|
-
});
|
|
220
|
+
if (onFocus) {
|
|
221
|
+
onFocus(event);
|
|
222
|
+
}
|
|
230
223
|
};
|
|
231
224
|
|
|
232
225
|
handleBlur: (event: React.FocusEvent<HTMLInputElement>) => unknown = (
|
|
233
226
|
event,
|
|
234
227
|
) => {
|
|
235
228
|
const {onBlur} = this.props;
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
229
|
+
if (onBlur) {
|
|
230
|
+
onBlur(event);
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
getStyles = (): StyleType => {
|
|
235
|
+
const {disabled, light} = this.props;
|
|
236
|
+
const {error} = this.state;
|
|
237
|
+
// Base styles are the styles that apply regardless of light mode
|
|
238
|
+
const baseStyles = [styles.input, typographyStyles.LabelMedium];
|
|
239
|
+
const defaultStyles = [
|
|
240
|
+
styles.default,
|
|
241
|
+
!disabled && styles.defaultFocus,
|
|
242
|
+
disabled && styles.disabled,
|
|
243
|
+
!!error && styles.error,
|
|
244
|
+
];
|
|
245
|
+
const lightStyles = [
|
|
246
|
+
styles.light,
|
|
247
|
+
!disabled && styles.lightFocus,
|
|
248
|
+
disabled && styles.lightDisabled,
|
|
249
|
+
!!error && styles.lightError,
|
|
250
|
+
];
|
|
251
|
+
return [...baseStyles, ...(light ? lightStyles : defaultStyles)];
|
|
241
252
|
};
|
|
242
253
|
|
|
243
254
|
render(): React.ReactNode {
|
|
@@ -249,7 +260,6 @@ class TextField extends React.Component<PropsWithForwardRef, State> {
|
|
|
249
260
|
disabled,
|
|
250
261
|
onKeyDown,
|
|
251
262
|
placeholder,
|
|
252
|
-
light,
|
|
253
263
|
style,
|
|
254
264
|
testId,
|
|
255
265
|
readOnly,
|
|
@@ -259,6 +269,7 @@ class TextField extends React.Component<PropsWithForwardRef, State> {
|
|
|
259
269
|
// The following props are being included here to avoid
|
|
260
270
|
// passing them down to the otherProps spread
|
|
261
271
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
272
|
+
light,
|
|
262
273
|
onFocus,
|
|
263
274
|
onBlur,
|
|
264
275
|
onValidate,
|
|
@@ -274,24 +285,7 @@ class TextField extends React.Component<PropsWithForwardRef, State> {
|
|
|
274
285
|
<IDProvider id={id} scope="text-field">
|
|
275
286
|
{(uniqueId) => (
|
|
276
287
|
<StyledInput
|
|
277
|
-
style={[
|
|
278
|
-
styles.input,
|
|
279
|
-
typographyStyles.LabelMedium,
|
|
280
|
-
styles.default,
|
|
281
|
-
// Prioritizes disabled, then focused, then error (if any)
|
|
282
|
-
disabled
|
|
283
|
-
? styles.disabled
|
|
284
|
-
: this.state.focused
|
|
285
|
-
? [styles.focused, light && styles.defaultLight]
|
|
286
|
-
: !!this.state.error && [
|
|
287
|
-
styles.error,
|
|
288
|
-
light && styles.errorLight,
|
|
289
|
-
],
|
|
290
|
-
// Cast `this.state.error` into boolean since it's being
|
|
291
|
-
// used as a conditional
|
|
292
|
-
!!this.state.error && styles.error,
|
|
293
|
-
style && style,
|
|
294
|
-
]}
|
|
288
|
+
style={[this.getStyles(), style]}
|
|
295
289
|
id={uniqueId}
|
|
296
290
|
type={type}
|
|
297
291
|
placeholder={placeholder}
|
|
@@ -320,12 +314,10 @@ const styles = StyleSheet.create({
|
|
|
320
314
|
input: {
|
|
321
315
|
width: "100%",
|
|
322
316
|
height: 40,
|
|
323
|
-
borderRadius:
|
|
317
|
+
borderRadius: border.radius.medium_4,
|
|
324
318
|
boxSizing: "border-box",
|
|
325
319
|
paddingLeft: spacing.medium_16,
|
|
326
320
|
margin: 0,
|
|
327
|
-
outline: "none",
|
|
328
|
-
boxShadow: "none",
|
|
329
321
|
},
|
|
330
322
|
default: {
|
|
331
323
|
background: color.white,
|
|
@@ -335,6 +327,13 @@ const styles = StyleSheet.create({
|
|
|
335
327
|
color: color.offBlack64,
|
|
336
328
|
},
|
|
337
329
|
},
|
|
330
|
+
defaultFocus: {
|
|
331
|
+
":focus-visible": {
|
|
332
|
+
borderColor: color.blue,
|
|
333
|
+
outline: `1px solid ${color.blue}`,
|
|
334
|
+
outlineOffset: 0, // Explicitly set outline offset to 0 because Safari sets a default offset
|
|
335
|
+
},
|
|
336
|
+
},
|
|
338
337
|
error: {
|
|
339
338
|
background: color.fadedRed8,
|
|
340
339
|
border: `1px solid ${color.red}`,
|
|
@@ -342,28 +341,67 @@ const styles = StyleSheet.create({
|
|
|
342
341
|
"::placeholder": {
|
|
343
342
|
color: color.offBlack64,
|
|
344
343
|
},
|
|
344
|
+
":focus-visible": {
|
|
345
|
+
outlineColor: color.red,
|
|
346
|
+
borderColor: color.red,
|
|
347
|
+
},
|
|
345
348
|
},
|
|
346
349
|
disabled: {
|
|
347
350
|
background: color.offWhite,
|
|
348
351
|
border: `1px solid ${color.offBlack16}`,
|
|
349
352
|
color: color.offBlack64,
|
|
350
353
|
"::placeholder": {
|
|
351
|
-
color: color.
|
|
354
|
+
color: color.offBlack64,
|
|
355
|
+
},
|
|
356
|
+
cursor: "not-allowed",
|
|
357
|
+
":focus-visible": {
|
|
358
|
+
outline: "none",
|
|
359
|
+
boxShadow: `0 0 0 1px ${color.white}, 0 0 0 3px ${color.offBlack32}`,
|
|
352
360
|
},
|
|
353
361
|
},
|
|
354
|
-
|
|
362
|
+
light: {
|
|
355
363
|
background: color.white,
|
|
356
|
-
border: `1px solid ${color.
|
|
364
|
+
border: `1px solid ${color.offBlack16}`,
|
|
357
365
|
color: color.offBlack,
|
|
358
366
|
"::placeholder": {
|
|
359
367
|
color: color.offBlack64,
|
|
360
368
|
},
|
|
361
369
|
},
|
|
362
|
-
|
|
363
|
-
|
|
370
|
+
lightFocus: {
|
|
371
|
+
":focus-visible": {
|
|
372
|
+
outline: `1px solid ${color.blue}`,
|
|
373
|
+
outlineOffset: 0, // Explicitly set outline offset to 0 because Safari sets a default offset
|
|
374
|
+
borderColor: color.blue,
|
|
375
|
+
boxShadow: `0px 0px 0px 2px ${color.blue}, 0px 0px 0px 3px ${color.white}`,
|
|
376
|
+
},
|
|
364
377
|
},
|
|
365
|
-
|
|
378
|
+
lightDisabled: {
|
|
379
|
+
backgroundColor: "transparent",
|
|
380
|
+
border: `1px solid ${color.white32}`,
|
|
381
|
+
color: color.white64,
|
|
382
|
+
"::placeholder": {
|
|
383
|
+
color: color.white64,
|
|
384
|
+
},
|
|
385
|
+
cursor: "not-allowed",
|
|
386
|
+
":focus-visible": {
|
|
387
|
+
borderColor: mix(color.white32, color.blue),
|
|
388
|
+
outline: "none",
|
|
389
|
+
boxShadow: `0 0 0 1px ${color.offBlack32}, 0 0 0 3px ${color.fadedBlue}`,
|
|
390
|
+
},
|
|
391
|
+
},
|
|
392
|
+
lightError: {
|
|
393
|
+
background: color.fadedRed8,
|
|
394
|
+
border: `1px solid ${color.red}`,
|
|
366
395
|
boxShadow: `0px 0px 0px 1px ${color.red}, 0px 0px 0px 2px ${color.white}`,
|
|
396
|
+
color: color.offBlack,
|
|
397
|
+
"::placeholder": {
|
|
398
|
+
color: color.offBlack64,
|
|
399
|
+
},
|
|
400
|
+
":focus-visible": {
|
|
401
|
+
outlineColor: color.red,
|
|
402
|
+
borderColor: color.red,
|
|
403
|
+
boxShadow: `0px 0px 0px 2px ${color.red}, 0px 0px 0px 3px ${color.white}`,
|
|
404
|
+
},
|
|
367
405
|
},
|
|
368
406
|
});
|
|
369
407
|
|