@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.
@@ -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 style={styles.required} aria-hidden={true}>
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
  });
@@ -241,6 +241,7 @@ class LabeledTextField extends React.Component<PropsWithForwardRef, State> {
241
241
  id={uniqueId}
242
242
  testId={testId}
243
243
  style={style}
244
+ light={light}
244
245
  field={
245
246
  <TextField
246
247
  id={`${uniqueId}-field`}
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- import {CSSProperties, Falsy, StyleSheet} from "aphrodite";
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 = (): (CSSProperties | Falsy)[] => {
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={[...getStyles(), 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
- this.setState({focused: true}, () => {
226
- if (onFocus) {
227
- onFocus(event);
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
- this.setState({focused: false}, () => {
237
- if (onBlur) {
238
- onBlur(event);
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: 4,
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.offBlack32,
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
- focused: {
362
+ light: {
355
363
  background: color.white,
356
- border: `1px solid ${color.blue}`,
364
+ border: `1px solid ${color.offBlack16}`,
357
365
  color: color.offBlack,
358
366
  "::placeholder": {
359
367
  color: color.offBlack64,
360
368
  },
361
369
  },
362
- defaultLight: {
363
- boxShadow: `0px 0px 0px 1px ${color.blue}, 0px 0px 0px 2px ${color.white}`,
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
- errorLight: {
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