@khanacademy/wonder-blocks-form 4.8.1 → 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/dist/index.js CHANGED
@@ -622,15 +622,14 @@ const RadioGroup = React__namespace.forwardRef(function RadioGroup(props, ref) {
622
622
  })));
623
623
  });
624
624
 
625
- const _excluded$2 = ["id", "type", "value", "name", "disabled", "onKeyDown", "placeholder", "light", "style", "testId", "readOnly", "autoFocus", "autoComplete", "forwardedRef", "onFocus", "onBlur", "onValidate", "validate", "onChange", "required"];
625
+ const _excluded$2 = ["id", "type", "value", "name", "disabled", "onKeyDown", "placeholder", "style", "testId", "readOnly", "autoFocus", "autoComplete", "forwardedRef", "light", "onFocus", "onBlur", "onValidate", "validate", "onChange", "required"];
626
626
  const defaultErrorMessage$1 = "This field is required.";
627
627
  const StyledInput = wonderBlocksCore.addStyle("input");
628
628
  class TextField extends React__namespace.Component {
629
629
  constructor(props) {
630
630
  super(props);
631
631
  this.state = {
632
- error: null,
633
- focused: false
632
+ error: null
634
633
  };
635
634
  this.maybeValidate = newValue => {
636
635
  const {
@@ -671,25 +670,30 @@ class TextField extends React__namespace.Component {
671
670
  const {
672
671
  onFocus
673
672
  } = this.props;
674
- this.setState({
675
- focused: true
676
- }, () => {
677
- if (onFocus) {
678
- onFocus(event);
679
- }
680
- });
673
+ if (onFocus) {
674
+ onFocus(event);
675
+ }
681
676
  };
682
677
  this.handleBlur = event => {
683
678
  const {
684
679
  onBlur
685
680
  } = this.props;
686
- this.setState({
687
- focused: false
688
- }, () => {
689
- if (onBlur) {
690
- onBlur(event);
691
- }
692
- });
681
+ if (onBlur) {
682
+ onBlur(event);
683
+ }
684
+ };
685
+ this.getStyles = () => {
686
+ const {
687
+ disabled,
688
+ light
689
+ } = this.props;
690
+ const {
691
+ error
692
+ } = this.state;
693
+ const baseStyles = [styles$2.input, wonderBlocksTypography.styles.LabelMedium];
694
+ const defaultStyles = [styles$2.default, !disabled && styles$2.defaultFocus, disabled && styles$2.disabled, !!error && styles$2.error];
695
+ const lightStyles = [styles$2.light, !disabled && styles$2.lightFocus, disabled && styles$2.lightDisabled, !!error && styles$2.lightError];
696
+ return [...baseStyles, ...(light ? lightStyles : defaultStyles)];
693
697
  };
694
698
  if (props.validate && props.value !== "") {
695
699
  this.state.error = props.validate(props.value) || null;
@@ -710,7 +714,6 @@ class TextField extends React__namespace.Component {
710
714
  disabled,
711
715
  onKeyDown,
712
716
  placeholder,
713
- light,
714
717
  style,
715
718
  testId,
716
719
  readOnly,
@@ -723,7 +726,7 @@ class TextField extends React__namespace.Component {
723
726
  id: id,
724
727
  scope: "text-field"
725
728
  }, uniqueId => React__namespace.createElement(StyledInput, _extends__default["default"]({
726
- style: [styles$2.input, wonderBlocksTypography.styles.LabelMedium, styles$2.default, disabled ? styles$2.disabled : this.state.focused ? [styles$2.focused, light && styles$2.defaultLight] : !!this.state.error && [styles$2.error, light && styles$2.errorLight], !!this.state.error && styles$2.error, style && style],
729
+ style: [this.getStyles(), style],
727
730
  id: uniqueId,
728
731
  type: type,
729
732
  placeholder: placeholder,
@@ -753,12 +756,10 @@ const styles$2 = aphrodite.StyleSheet.create({
753
756
  input: {
754
757
  width: "100%",
755
758
  height: 40,
756
- borderRadius: 4,
759
+ borderRadius: wonderBlocksTokens.border.radius.medium_4,
757
760
  boxSizing: "border-box",
758
761
  paddingLeft: wonderBlocksTokens.spacing.medium_16,
759
- margin: 0,
760
- outline: "none",
761
- boxShadow: "none"
762
+ margin: 0
762
763
  },
763
764
  default: {
764
765
  background: wonderBlocksTokens.color.white,
@@ -768,12 +769,23 @@ const styles$2 = aphrodite.StyleSheet.create({
768
769
  color: wonderBlocksTokens.color.offBlack64
769
770
  }
770
771
  },
772
+ defaultFocus: {
773
+ ":focus-visible": {
774
+ borderColor: wonderBlocksTokens.color.blue,
775
+ outline: `1px solid ${wonderBlocksTokens.color.blue}`,
776
+ outlineOffset: 0
777
+ }
778
+ },
771
779
  error: {
772
780
  background: wonderBlocksTokens.color.fadedRed8,
773
781
  border: `1px solid ${wonderBlocksTokens.color.red}`,
774
782
  color: wonderBlocksTokens.color.offBlack,
775
783
  "::placeholder": {
776
784
  color: wonderBlocksTokens.color.offBlack64
785
+ },
786
+ ":focus-visible": {
787
+ outlineColor: wonderBlocksTokens.color.red,
788
+ borderColor: wonderBlocksTokens.color.red
777
789
  }
778
790
  },
779
791
  disabled: {
@@ -781,22 +793,57 @@ const styles$2 = aphrodite.StyleSheet.create({
781
793
  border: `1px solid ${wonderBlocksTokens.color.offBlack16}`,
782
794
  color: wonderBlocksTokens.color.offBlack64,
783
795
  "::placeholder": {
784
- color: wonderBlocksTokens.color.offBlack32
796
+ color: wonderBlocksTokens.color.offBlack64
797
+ },
798
+ cursor: "not-allowed",
799
+ ":focus-visible": {
800
+ outline: "none",
801
+ boxShadow: `0 0 0 1px ${wonderBlocksTokens.color.white}, 0 0 0 3px ${wonderBlocksTokens.color.offBlack32}`
785
802
  }
786
803
  },
787
- focused: {
804
+ light: {
788
805
  background: wonderBlocksTokens.color.white,
789
- border: `1px solid ${wonderBlocksTokens.color.blue}`,
806
+ border: `1px solid ${wonderBlocksTokens.color.offBlack16}`,
790
807
  color: wonderBlocksTokens.color.offBlack,
791
808
  "::placeholder": {
792
809
  color: wonderBlocksTokens.color.offBlack64
793
810
  }
794
811
  },
795
- defaultLight: {
796
- boxShadow: `0px 0px 0px 1px ${wonderBlocksTokens.color.blue}, 0px 0px 0px 2px ${wonderBlocksTokens.color.white}`
812
+ lightFocus: {
813
+ ":focus-visible": {
814
+ outline: `1px solid ${wonderBlocksTokens.color.blue}`,
815
+ outlineOffset: 0,
816
+ borderColor: wonderBlocksTokens.color.blue,
817
+ boxShadow: `0px 0px 0px 2px ${wonderBlocksTokens.color.blue}, 0px 0px 0px 3px ${wonderBlocksTokens.color.white}`
818
+ }
819
+ },
820
+ lightDisabled: {
821
+ backgroundColor: "transparent",
822
+ border: `1px solid ${wonderBlocksTokens.color.white32}`,
823
+ color: wonderBlocksTokens.color.white64,
824
+ "::placeholder": {
825
+ color: wonderBlocksTokens.color.white64
826
+ },
827
+ cursor: "not-allowed",
828
+ ":focus-visible": {
829
+ borderColor: wonderBlocksTokens.mix(wonderBlocksTokens.color.white32, wonderBlocksTokens.color.blue),
830
+ outline: "none",
831
+ boxShadow: `0 0 0 1px ${wonderBlocksTokens.color.offBlack32}, 0 0 0 3px ${wonderBlocksTokens.color.fadedBlue}`
832
+ }
797
833
  },
798
- errorLight: {
799
- boxShadow: `0px 0px 0px 1px ${wonderBlocksTokens.color.red}, 0px 0px 0px 2px ${wonderBlocksTokens.color.white}`
834
+ lightError: {
835
+ background: wonderBlocksTokens.color.fadedRed8,
836
+ border: `1px solid ${wonderBlocksTokens.color.red}`,
837
+ boxShadow: `0px 0px 0px 1px ${wonderBlocksTokens.color.red}, 0px 0px 0px 2px ${wonderBlocksTokens.color.white}`,
838
+ color: wonderBlocksTokens.color.offBlack,
839
+ "::placeholder": {
840
+ color: wonderBlocksTokens.color.offBlack64
841
+ },
842
+ ":focus-visible": {
843
+ outlineColor: wonderBlocksTokens.color.red,
844
+ borderColor: wonderBlocksTokens.color.red,
845
+ boxShadow: `0px 0px 0px 2px ${wonderBlocksTokens.color.red}, 0px 0px 0px 3px ${wonderBlocksTokens.color.white}`
846
+ }
800
847
  }
801
848
  });
802
849
  var TextField$1 = React__namespace.forwardRef((props, ref) => React__namespace.createElement(TextField, _extends__default["default"]({}, props, {
@@ -810,14 +857,15 @@ class FieldHeading extends React__namespace.Component {
810
857
  label,
811
858
  id,
812
859
  required,
813
- testId
860
+ testId,
861
+ light
814
862
  } = this.props;
815
863
  const requiredIcon = React__namespace.createElement(StyledSpan, {
816
- style: styles$1.required,
864
+ style: light ? styles$1.lightRequired : styles$1.required,
817
865
  "aria-hidden": true
818
866
  }, " ", "*");
819
867
  return React__namespace.createElement(React__namespace.Fragment, null, React__namespace.createElement(wonderBlocksTypography.LabelMedium, {
820
- style: styles$1.label,
868
+ style: light ? styles$1.lightLabel : styles$1.label,
821
869
  tag: "label",
822
870
  htmlFor: id && `${id}-field`,
823
871
  testId: testId && `${testId}-label`
@@ -828,13 +876,14 @@ class FieldHeading extends React__namespace.Component {
828
876
  maybeRenderDescription() {
829
877
  const {
830
878
  description,
831
- testId
879
+ testId,
880
+ light
832
881
  } = this.props;
833
882
  if (!description) {
834
883
  return null;
835
884
  }
836
885
  return React__namespace.createElement(React__namespace.Fragment, null, React__namespace.createElement(wonderBlocksTypography.LabelSmall, {
837
- style: styles$1.description,
886
+ style: light ? styles$1.lightDescription : styles$1.description,
838
887
  testId: testId && `${testId}-description`
839
888
  }, description), React__namespace.createElement(wonderBlocksLayout.Strut, {
840
889
  size: wonderBlocksTokens.spacing.xxxSmall_4
@@ -844,7 +893,8 @@ class FieldHeading extends React__namespace.Component {
844
893
  const {
845
894
  error,
846
895
  id,
847
- testId
896
+ testId,
897
+ light
848
898
  } = this.props;
849
899
  if (!error) {
850
900
  return null;
@@ -852,7 +902,7 @@ class FieldHeading extends React__namespace.Component {
852
902
  return React__namespace.createElement(React__namespace.Fragment, null, React__namespace.createElement(wonderBlocksLayout.Strut, {
853
903
  size: wonderBlocksTokens.spacing.small_12
854
904
  }), React__namespace.createElement(wonderBlocksTypography.LabelSmall, {
855
- style: styles$1.error,
905
+ style: light ? styles$1.lightError : styles$1.error,
856
906
  role: "alert",
857
907
  id: id && `${id}-error`,
858
908
  testId: testId && `${testId}-error`
@@ -874,14 +924,26 @@ const styles$1 = aphrodite.StyleSheet.create({
874
924
  label: {
875
925
  color: wonderBlocksTokens.color.offBlack
876
926
  },
927
+ lightLabel: {
928
+ color: wonderBlocksTokens.color.white
929
+ },
877
930
  description: {
878
931
  color: wonderBlocksTokens.color.offBlack64
879
932
  },
933
+ lightDescription: {
934
+ color: wonderBlocksTokens.color.white64
935
+ },
880
936
  error: {
881
937
  color: wonderBlocksTokens.color.red
882
938
  },
939
+ lightError: {
940
+ color: wonderBlocksTokens.color.fadedRed
941
+ },
883
942
  required: {
884
943
  color: wonderBlocksTokens.color.red
944
+ },
945
+ lightRequired: {
946
+ color: wonderBlocksTokens.color.fadedRed
885
947
  }
886
948
  });
887
949
 
@@ -960,6 +1022,7 @@ class LabeledTextField extends React__namespace.Component {
960
1022
  id: uniqueId,
961
1023
  testId: testId,
962
1024
  style: style,
1025
+ light: light,
963
1026
  field: React__namespace.createElement(TextField$1, _extends__default["default"]({
964
1027
  id: `${uniqueId}-field`,
965
1028
  "aria-describedby": ariaDescribedby ? ariaDescribedby : `${uniqueId}-error`,
@@ -997,7 +1060,7 @@ var labeledTextField = React__namespace.forwardRef((props, ref) => React__namesp
997
1060
  forwardedRef: ref
998
1061
  })));
999
1062
 
1000
- const _excluded = ["onChange", "value", "placeholder", "disabled", "id", "testId", "style", "readOnly", "autoComplete", "name", "className", "autoFocus", "rows", "spellCheck", "wrap", "minLength", "maxLength", "onClick", "onKeyDown", "onKeyUp", "onFocus", "onBlur", "validate", "onValidate", "required", "resizeType", "light"];
1063
+ const _excluded = ["onChange", "value", "placeholder", "disabled", "id", "testId", "style", "readOnly", "autoComplete", "name", "className", "autoFocus", "rows", "spellCheck", "wrap", "minLength", "maxLength", "onClick", "onKeyDown", "onKeyUp", "onFocus", "onBlur", "validate", "onValidate", "required", "resizeType", "light", "rootStyle"];
1001
1064
  const defaultErrorMessage = "This field is required.";
1002
1065
  const StyledTextArea = wonderBlocksCore.addStyle("textarea");
1003
1066
  const TextArea = React__namespace.forwardRef(function TextArea(props, ref) {
@@ -1028,7 +1091,8 @@ const TextArea = React__namespace.forwardRef(function TextArea(props, ref) {
1028
1091
  onValidate,
1029
1092
  required,
1030
1093
  resizeType,
1031
- light
1094
+ light,
1095
+ rootStyle
1032
1096
  } = props,
1033
1097
  otherProps = _objectWithoutPropertiesLoose__default["default"](props, _excluded);
1034
1098
  const [error, setError] = React__namespace.useState(null);
@@ -1067,15 +1131,15 @@ const TextArea = React__namespace.forwardRef(function TextArea(props, ref) {
1067
1131
  return [...baseStyles, ...(light ? lightStyles : defaultStyles)];
1068
1132
  };
1069
1133
  return React__namespace.createElement(wonderBlocksCore.View, {
1070
- style: {
1134
+ style: [{
1071
1135
  width: "100%"
1072
- }
1136
+ }, rootStyle]
1073
1137
  }, React__namespace.createElement(StyledTextArea, _extends__default["default"]({
1074
1138
  id: uniqueId,
1075
1139
  "data-testid": testId,
1076
1140
  ref: ref,
1077
1141
  className: className,
1078
- style: [...getStyles(), style],
1142
+ style: [getStyles(), style],
1079
1143
  value: value,
1080
1144
  onChange: handleChange,
1081
1145
  placeholder: placeholder,
@@ -1099,12 +1163,13 @@ const TextArea = React__namespace.forwardRef(function TextArea(props, ref) {
1099
1163
  "aria-invalid": !!error
1100
1164
  })));
1101
1165
  });
1166
+ const VERTICAL_SPACING_PX = 10;
1102
1167
  const styles = aphrodite.StyleSheet.create({
1103
1168
  textarea: {
1104
1169
  borderRadius: wonderBlocksTokens.border.radius.medium_4,
1105
1170
  boxSizing: "border-box",
1106
- padding: `10px ${wonderBlocksTokens.spacing.medium_16}px`,
1107
- minHeight: "1em"
1171
+ padding: `${VERTICAL_SPACING_PX}px ${wonderBlocksTokens.spacing.medium_16}px`,
1172
+ minHeight: `${VERTICAL_SPACING_PX * 2 + wonderBlocksTokens.font.lineHeight.medium + 2 * wonderBlocksTokens.border.width.hairline}px`
1108
1173
  },
1109
1174
  default: {
1110
1175
  background: wonderBlocksTokens.color.white,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khanacademy/wonder-blocks-form",
3
- "version": "4.8.1",
3
+ "version": "4.9.1",
4
4
  "design": "v1",
5
5
  "description": "Form components for Wonder Blocks.",
6
6
  "main": "dist/index.js",
@@ -16,12 +16,12 @@
16
16
  },
17
17
  "dependencies": {
18
18
  "@babel/runtime": "^7.18.6",
19
- "@khanacademy/wonder-blocks-clickable": "^4.2.5",
20
- "@khanacademy/wonder-blocks-core": "^6.4.3",
21
- "@khanacademy/wonder-blocks-icon": "^4.1.3",
22
- "@khanacademy/wonder-blocks-layout": "^2.1.2",
23
- "@khanacademy/wonder-blocks-tokens": "^1.3.1",
24
- "@khanacademy/wonder-blocks-typography": "^2.1.14"
19
+ "@khanacademy/wonder-blocks-clickable": "^4.2.7",
20
+ "@khanacademy/wonder-blocks-core": "^7.0.0",
21
+ "@khanacademy/wonder-blocks-icon": "^4.1.4",
22
+ "@khanacademy/wonder-blocks-layout": "^2.2.0",
23
+ "@khanacademy/wonder-blocks-tokens": "^2.0.0",
24
+ "@khanacademy/wonder-blocks-typography": "^2.1.15"
25
25
  },
26
26
  "peerDependencies": {
27
27
  "aphrodite": "^1.2.5",
@@ -3,7 +3,6 @@ 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-tokens";
7
6
  import LabeledTextField from "../labeled-text-field";
8
7
 
9
8
  describe("LabeledTextField", () => {
@@ -382,28 +381,6 @@ describe("LabeledTextField", () => {
382
381
  expect(input).toBeInTheDocument();
383
382
  });
384
383
 
385
- it("light prop is passed to textfield", async () => {
386
- // Arrange
387
-
388
- // Act
389
- render(
390
- <LabeledTextField
391
- label="Label"
392
- value=""
393
- onChange={() => {}}
394
- light={true}
395
- />,
396
- );
397
-
398
- const textField = await screen.findByRole("textbox");
399
- textField.focus();
400
-
401
- // Assert
402
- expect(textField).toHaveStyle({
403
- boxShadow: `0px 0px 0px 1px ${color.blue}, 0px 0px 0px 2px ${color.white}`,
404
- });
405
- });
406
-
407
384
  it("style prop is passed to fieldheading", async () => {
408
385
  // Arrange
409
386
  const styles = StyleSheet.create({
@@ -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,
@@ -9,7 +9,13 @@ import {
9
9
  addStyle,
10
10
  View,
11
11
  } from "@khanacademy/wonder-blocks-core";
12
- import {border, color, mix, spacing} from "@khanacademy/wonder-blocks-tokens";
12
+ import {
13
+ border,
14
+ color,
15
+ font,
16
+ mix,
17
+ spacing,
18
+ } from "@khanacademy/wonder-blocks-tokens";
13
19
  import {styles as typographyStyles} from "@khanacademy/wonder-blocks-typography";
14
20
 
15
21
  type TextAreaProps = AriaProps & {
@@ -31,9 +37,15 @@ type TextAreaProps = AriaProps & {
31
37
  */
32
38
  testId?: string;
33
39
  /**
34
- * Custom styles for the text area.
40
+ * Custom styles for the textarea element.
35
41
  */
36
42
  style?: StyleType;
43
+ /**
44
+ * Custom styles for the root node of the component.
45
+ * If possible, try to use this prop carefully and use the `style` prop
46
+ * instead.
47
+ */
48
+ rootStyle?: StyleType;
37
49
  /**
38
50
  * Provide hints or examples of what to enter.
39
51
  */
@@ -198,6 +210,7 @@ const TextArea = React.forwardRef<HTMLTextAreaElement, TextAreaProps>(
198
210
  required,
199
211
  resizeType,
200
212
  light,
213
+ rootStyle,
201
214
  // Should only include aria related props
202
215
  ...otherProps
203
216
  } = props;
@@ -243,7 +256,7 @@ const TextArea = React.forwardRef<HTMLTextAreaElement, TextAreaProps>(
243
256
  }
244
257
  });
245
258
 
246
- const getStyles = (): (CSSProperties | Falsy)[] => {
259
+ const getStyles = (): StyleType => {
247
260
  // Base styles are the styles that apply regardless of light mode
248
261
  const baseStyles = [
249
262
  styles.textarea,
@@ -265,13 +278,13 @@ const TextArea = React.forwardRef<HTMLTextAreaElement, TextAreaProps>(
265
278
  return [...baseStyles, ...(light ? lightStyles : defaultStyles)];
266
279
  };
267
280
  return (
268
- <View style={{width: "100%"}}>
281
+ <View style={[{width: "100%"}, rootStyle]}>
269
282
  <StyledTextArea
270
283
  id={uniqueId}
271
284
  data-testid={testId}
272
285
  ref={ref}
273
286
  className={className}
274
- style={[...getStyles(), style]}
287
+ style={[getStyles(), style]}
275
288
  value={value}
276
289
  onChange={handleChange}
277
290
  placeholder={placeholder}
@@ -299,12 +312,19 @@ const TextArea = React.forwardRef<HTMLTextAreaElement, TextAreaProps>(
299
312
  },
300
313
  );
301
314
 
315
+ const VERTICAL_SPACING_PX = 10;
316
+
302
317
  const styles = StyleSheet.create({
303
318
  textarea: {
304
319
  borderRadius: border.radius.medium_4,
305
320
  boxSizing: "border-box",
306
- padding: `10px ${spacing.medium_16}px`,
307
- minHeight: "1em",
321
+ padding: `${VERTICAL_SPACING_PX}px ${spacing.medium_16}px`,
322
+ // This minHeight is equivalent to when the textarea has one row
323
+ minHeight: `${
324
+ VERTICAL_SPACING_PX * 2 +
325
+ font.lineHeight.medium +
326
+ 2 * border.width.hairline
327
+ }px`,
308
328
  },
309
329
  default: {
310
330
  background: color.white,