@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/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # @khanacademy/wonder-blocks-form
2
2
 
3
+ ## 4.9.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 982f6808: Update `TextField` state styling so that it is consistent with other components like `TextArea`, `SingleSelect`, `MultiSelect` (especially the focus styling). The styling also now uses CSS pseudo-classes for easier testing in Chromatic and debugging in browsers.
8
+ - 17f9a337: Improve `LabeledTextField` styling when the `light` prop is `true`. This improves the color contrast of the label, required indicator, description, and error message when the component is used on dark backgrounds.
9
+ - Updated dependencies [07f7f407]
10
+ - @khanacademy/wonder-blocks-core@7.0.0
11
+ - @khanacademy/wonder-blocks-layout@2.2.0
12
+ - @khanacademy/wonder-blocks-clickable@4.2.7
13
+ - @khanacademy/wonder-blocks-icon@4.1.4
14
+ - @khanacademy/wonder-blocks-typography@2.1.15
15
+
16
+ ## 4.9.0
17
+
18
+ ### Minor Changes
19
+
20
+ - f7390d9d: `TextArea`: Adds `rootStyle` prop for styling to the root node
21
+
22
+ ### Patch Changes
23
+
24
+ - f7390d9d: `TextArea`: Updates the `min-height` of the textarea element so that when it is resized vertically using the resize control, the smallest it can get is equivalent to 1 row of the textarea.
25
+ - Updated dependencies [f17dc1ee]
26
+ - Updated dependencies [991eb43f]
27
+ - @khanacademy/wonder-blocks-tokens@2.0.0
28
+ - @khanacademy/wonder-blocks-clickable@4.2.6
29
+ - @khanacademy/wonder-blocks-layout@2.1.3
30
+
3
31
  ## 4.8.1
4
32
 
5
33
  ### Patch Changes
@@ -36,6 +36,10 @@ type Props = {
36
36
  * Optional test ID for e2e testing.
37
37
  */
38
38
  testId?: string;
39
+ /**
40
+ * Change the field’s sub-components to fit a dark background.
41
+ */
42
+ light?: boolean;
39
43
  };
40
44
  /**
41
45
  * A FieldHeading is an element that provides a label, description, and error element
@@ -21,9 +21,15 @@ declare const TextArea: React.ForwardRefExoticComponent<Readonly<import("../../.
21
21
  */
22
22
  testId?: string | undefined;
23
23
  /**
24
- * Custom styles for the text area.
24
+ * Custom styles for the textarea element.
25
25
  */
26
26
  style?: StyleType;
27
+ /**
28
+ * Custom styles for the root node of the component.
29
+ * If possible, try to use this prop carefully and use the `style` prop
30
+ * instead.
31
+ */
32
+ rootStyle?: StyleType;
27
33
  /**
28
34
  * Provide hints or examples of what to enter.
29
35
  */
@@ -133,10 +133,6 @@ type State = {
133
133
  * Displayed when the validation fails.
134
134
  */
135
135
  error: string | null | undefined;
136
- /**
137
- * The user focuses on this field.
138
- */
139
- focused: boolean;
140
136
  };
141
137
  /**
142
138
  * A TextField is an element used to accept a single line of text from the user.
@@ -150,6 +146,7 @@ declare class TextField extends React.Component<PropsWithForwardRef, State> {
150
146
  handleChange: (event: React.ChangeEvent<HTMLInputElement>) => unknown;
151
147
  handleFocus: (event: React.FocusEvent<HTMLInputElement>) => unknown;
152
148
  handleBlur: (event: React.FocusEvent<HTMLInputElement>) => unknown;
149
+ getStyles: () => StyleType;
153
150
  render(): React.ReactNode;
154
151
  }
155
152
  type ExportProps = OmitConstrained<JSX.LibraryManagedAttributes<typeof TextField, React.ComponentProps<typeof TextField>>, "forwardedRef">;
package/dist/es/index.js CHANGED
@@ -4,7 +4,7 @@ import _objectWithoutPropertiesLoose from '@babel/runtime/helpers/objectWithoutP
4
4
  import { StyleSheet } from 'aphrodite';
5
5
  import { addStyle, UniqueIDProvider, View, IDProvider, useUniqueIdWithMock, useOnMountEffect } from '@khanacademy/wonder-blocks-core';
6
6
  import { Strut } from '@khanacademy/wonder-blocks-layout';
7
- import { spacing, mix, color, border } from '@khanacademy/wonder-blocks-tokens';
7
+ import { spacing, mix, color, border, font } from '@khanacademy/wonder-blocks-tokens';
8
8
  import { LabelMedium, LabelSmall, styles as styles$7 } from '@khanacademy/wonder-blocks-typography';
9
9
  import { PhosphorIcon } from '@khanacademy/wonder-blocks-icon';
10
10
  import checkIcon from '@phosphor-icons/core/bold/check-bold.svg';
@@ -592,15 +592,14 @@ const RadioGroup = React.forwardRef(function RadioGroup(props, ref) {
592
592
  })));
593
593
  });
594
594
 
595
- const _excluded$2 = ["id", "type", "value", "name", "disabled", "onKeyDown", "placeholder", "light", "style", "testId", "readOnly", "autoFocus", "autoComplete", "forwardedRef", "onFocus", "onBlur", "onValidate", "validate", "onChange", "required"];
595
+ const _excluded$2 = ["id", "type", "value", "name", "disabled", "onKeyDown", "placeholder", "style", "testId", "readOnly", "autoFocus", "autoComplete", "forwardedRef", "light", "onFocus", "onBlur", "onValidate", "validate", "onChange", "required"];
596
596
  const defaultErrorMessage$1 = "This field is required.";
597
597
  const StyledInput = addStyle("input");
598
598
  class TextField extends React.Component {
599
599
  constructor(props) {
600
600
  super(props);
601
601
  this.state = {
602
- error: null,
603
- focused: false
602
+ error: null
604
603
  };
605
604
  this.maybeValidate = newValue => {
606
605
  const {
@@ -641,25 +640,30 @@ class TextField extends React.Component {
641
640
  const {
642
641
  onFocus
643
642
  } = this.props;
644
- this.setState({
645
- focused: true
646
- }, () => {
647
- if (onFocus) {
648
- onFocus(event);
649
- }
650
- });
643
+ if (onFocus) {
644
+ onFocus(event);
645
+ }
651
646
  };
652
647
  this.handleBlur = event => {
653
648
  const {
654
649
  onBlur
655
650
  } = this.props;
656
- this.setState({
657
- focused: false
658
- }, () => {
659
- if (onBlur) {
660
- onBlur(event);
661
- }
662
- });
651
+ if (onBlur) {
652
+ onBlur(event);
653
+ }
654
+ };
655
+ this.getStyles = () => {
656
+ const {
657
+ disabled,
658
+ light
659
+ } = this.props;
660
+ const {
661
+ error
662
+ } = this.state;
663
+ const baseStyles = [styles$2.input, styles$7.LabelMedium];
664
+ const defaultStyles = [styles$2.default, !disabled && styles$2.defaultFocus, disabled && styles$2.disabled, !!error && styles$2.error];
665
+ const lightStyles = [styles$2.light, !disabled && styles$2.lightFocus, disabled && styles$2.lightDisabled, !!error && styles$2.lightError];
666
+ return [...baseStyles, ...(light ? lightStyles : defaultStyles)];
663
667
  };
664
668
  if (props.validate && props.value !== "") {
665
669
  this.state.error = props.validate(props.value) || null;
@@ -680,7 +684,6 @@ class TextField extends React.Component {
680
684
  disabled,
681
685
  onKeyDown,
682
686
  placeholder,
683
- light,
684
687
  style,
685
688
  testId,
686
689
  readOnly,
@@ -693,7 +696,7 @@ class TextField extends React.Component {
693
696
  id: id,
694
697
  scope: "text-field"
695
698
  }, uniqueId => React.createElement(StyledInput, _extends({
696
- style: [styles$2.input, styles$7.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],
699
+ style: [this.getStyles(), style],
697
700
  id: uniqueId,
698
701
  type: type,
699
702
  placeholder: placeholder,
@@ -723,12 +726,10 @@ const styles$2 = StyleSheet.create({
723
726
  input: {
724
727
  width: "100%",
725
728
  height: 40,
726
- borderRadius: 4,
729
+ borderRadius: border.radius.medium_4,
727
730
  boxSizing: "border-box",
728
731
  paddingLeft: spacing.medium_16,
729
- margin: 0,
730
- outline: "none",
731
- boxShadow: "none"
732
+ margin: 0
732
733
  },
733
734
  default: {
734
735
  background: color.white,
@@ -738,12 +739,23 @@ const styles$2 = StyleSheet.create({
738
739
  color: color.offBlack64
739
740
  }
740
741
  },
742
+ defaultFocus: {
743
+ ":focus-visible": {
744
+ borderColor: color.blue,
745
+ outline: `1px solid ${color.blue}`,
746
+ outlineOffset: 0
747
+ }
748
+ },
741
749
  error: {
742
750
  background: color.fadedRed8,
743
751
  border: `1px solid ${color.red}`,
744
752
  color: color.offBlack,
745
753
  "::placeholder": {
746
754
  color: color.offBlack64
755
+ },
756
+ ":focus-visible": {
757
+ outlineColor: color.red,
758
+ borderColor: color.red
747
759
  }
748
760
  },
749
761
  disabled: {
@@ -751,22 +763,57 @@ const styles$2 = StyleSheet.create({
751
763
  border: `1px solid ${color.offBlack16}`,
752
764
  color: color.offBlack64,
753
765
  "::placeholder": {
754
- color: color.offBlack32
766
+ color: color.offBlack64
767
+ },
768
+ cursor: "not-allowed",
769
+ ":focus-visible": {
770
+ outline: "none",
771
+ boxShadow: `0 0 0 1px ${color.white}, 0 0 0 3px ${color.offBlack32}`
755
772
  }
756
773
  },
757
- focused: {
774
+ light: {
758
775
  background: color.white,
759
- border: `1px solid ${color.blue}`,
776
+ border: `1px solid ${color.offBlack16}`,
760
777
  color: color.offBlack,
761
778
  "::placeholder": {
762
779
  color: color.offBlack64
763
780
  }
764
781
  },
765
- defaultLight: {
766
- boxShadow: `0px 0px 0px 1px ${color.blue}, 0px 0px 0px 2px ${color.white}`
782
+ lightFocus: {
783
+ ":focus-visible": {
784
+ outline: `1px solid ${color.blue}`,
785
+ outlineOffset: 0,
786
+ borderColor: color.blue,
787
+ boxShadow: `0px 0px 0px 2px ${color.blue}, 0px 0px 0px 3px ${color.white}`
788
+ }
789
+ },
790
+ lightDisabled: {
791
+ backgroundColor: "transparent",
792
+ border: `1px solid ${color.white32}`,
793
+ color: color.white64,
794
+ "::placeholder": {
795
+ color: color.white64
796
+ },
797
+ cursor: "not-allowed",
798
+ ":focus-visible": {
799
+ borderColor: mix(color.white32, color.blue),
800
+ outline: "none",
801
+ boxShadow: `0 0 0 1px ${color.offBlack32}, 0 0 0 3px ${color.fadedBlue}`
802
+ }
767
803
  },
768
- errorLight: {
769
- boxShadow: `0px 0px 0px 1px ${color.red}, 0px 0px 0px 2px ${color.white}`
804
+ lightError: {
805
+ background: color.fadedRed8,
806
+ border: `1px solid ${color.red}`,
807
+ boxShadow: `0px 0px 0px 1px ${color.red}, 0px 0px 0px 2px ${color.white}`,
808
+ color: color.offBlack,
809
+ "::placeholder": {
810
+ color: color.offBlack64
811
+ },
812
+ ":focus-visible": {
813
+ outlineColor: color.red,
814
+ borderColor: color.red,
815
+ boxShadow: `0px 0px 0px 2px ${color.red}, 0px 0px 0px 3px ${color.white}`
816
+ }
770
817
  }
771
818
  });
772
819
  var TextField$1 = React.forwardRef((props, ref) => React.createElement(TextField, _extends({}, props, {
@@ -780,14 +827,15 @@ class FieldHeading extends React.Component {
780
827
  label,
781
828
  id,
782
829
  required,
783
- testId
830
+ testId,
831
+ light
784
832
  } = this.props;
785
833
  const requiredIcon = React.createElement(StyledSpan, {
786
- style: styles$1.required,
834
+ style: light ? styles$1.lightRequired : styles$1.required,
787
835
  "aria-hidden": true
788
836
  }, " ", "*");
789
837
  return React.createElement(React.Fragment, null, React.createElement(LabelMedium, {
790
- style: styles$1.label,
838
+ style: light ? styles$1.lightLabel : styles$1.label,
791
839
  tag: "label",
792
840
  htmlFor: id && `${id}-field`,
793
841
  testId: testId && `${testId}-label`
@@ -798,13 +846,14 @@ class FieldHeading extends React.Component {
798
846
  maybeRenderDescription() {
799
847
  const {
800
848
  description,
801
- testId
849
+ testId,
850
+ light
802
851
  } = this.props;
803
852
  if (!description) {
804
853
  return null;
805
854
  }
806
855
  return React.createElement(React.Fragment, null, React.createElement(LabelSmall, {
807
- style: styles$1.description,
856
+ style: light ? styles$1.lightDescription : styles$1.description,
808
857
  testId: testId && `${testId}-description`
809
858
  }, description), React.createElement(Strut, {
810
859
  size: spacing.xxxSmall_4
@@ -814,7 +863,8 @@ class FieldHeading extends React.Component {
814
863
  const {
815
864
  error,
816
865
  id,
817
- testId
866
+ testId,
867
+ light
818
868
  } = this.props;
819
869
  if (!error) {
820
870
  return null;
@@ -822,7 +872,7 @@ class FieldHeading extends React.Component {
822
872
  return React.createElement(React.Fragment, null, React.createElement(Strut, {
823
873
  size: spacing.small_12
824
874
  }), React.createElement(LabelSmall, {
825
- style: styles$1.error,
875
+ style: light ? styles$1.lightError : styles$1.error,
826
876
  role: "alert",
827
877
  id: id && `${id}-error`,
828
878
  testId: testId && `${testId}-error`
@@ -844,14 +894,26 @@ const styles$1 = StyleSheet.create({
844
894
  label: {
845
895
  color: color.offBlack
846
896
  },
897
+ lightLabel: {
898
+ color: color.white
899
+ },
847
900
  description: {
848
901
  color: color.offBlack64
849
902
  },
903
+ lightDescription: {
904
+ color: color.white64
905
+ },
850
906
  error: {
851
907
  color: color.red
852
908
  },
909
+ lightError: {
910
+ color: color.fadedRed
911
+ },
853
912
  required: {
854
913
  color: color.red
914
+ },
915
+ lightRequired: {
916
+ color: color.fadedRed
855
917
  }
856
918
  });
857
919
 
@@ -930,6 +992,7 @@ class LabeledTextField extends React.Component {
930
992
  id: uniqueId,
931
993
  testId: testId,
932
994
  style: style,
995
+ light: light,
933
996
  field: React.createElement(TextField$1, _extends({
934
997
  id: `${uniqueId}-field`,
935
998
  "aria-describedby": ariaDescribedby ? ariaDescribedby : `${uniqueId}-error`,
@@ -967,7 +1030,7 @@ var labeledTextField = React.forwardRef((props, ref) => React.createElement(Labe
967
1030
  forwardedRef: ref
968
1031
  })));
969
1032
 
970
- 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"];
1033
+ 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"];
971
1034
  const defaultErrorMessage = "This field is required.";
972
1035
  const StyledTextArea = addStyle("textarea");
973
1036
  const TextArea = React.forwardRef(function TextArea(props, ref) {
@@ -998,7 +1061,8 @@ const TextArea = React.forwardRef(function TextArea(props, ref) {
998
1061
  onValidate,
999
1062
  required,
1000
1063
  resizeType,
1001
- light
1064
+ light,
1065
+ rootStyle
1002
1066
  } = props,
1003
1067
  otherProps = _objectWithoutPropertiesLoose(props, _excluded);
1004
1068
  const [error, setError] = React.useState(null);
@@ -1037,15 +1101,15 @@ const TextArea = React.forwardRef(function TextArea(props, ref) {
1037
1101
  return [...baseStyles, ...(light ? lightStyles : defaultStyles)];
1038
1102
  };
1039
1103
  return React.createElement(View, {
1040
- style: {
1104
+ style: [{
1041
1105
  width: "100%"
1042
- }
1106
+ }, rootStyle]
1043
1107
  }, React.createElement(StyledTextArea, _extends({
1044
1108
  id: uniqueId,
1045
1109
  "data-testid": testId,
1046
1110
  ref: ref,
1047
1111
  className: className,
1048
- style: [...getStyles(), style],
1112
+ style: [getStyles(), style],
1049
1113
  value: value,
1050
1114
  onChange: handleChange,
1051
1115
  placeholder: placeholder,
@@ -1069,12 +1133,13 @@ const TextArea = React.forwardRef(function TextArea(props, ref) {
1069
1133
  "aria-invalid": !!error
1070
1134
  })));
1071
1135
  });
1136
+ const VERTICAL_SPACING_PX = 10;
1072
1137
  const styles = StyleSheet.create({
1073
1138
  textarea: {
1074
1139
  borderRadius: border.radius.medium_4,
1075
1140
  boxSizing: "border-box",
1076
- padding: `10px ${spacing.medium_16}px`,
1077
- minHeight: "1em"
1141
+ padding: `${VERTICAL_SPACING_PX}px ${spacing.medium_16}px`,
1142
+ minHeight: `${VERTICAL_SPACING_PX * 2 + font.lineHeight.medium + 2 * border.width.hairline}px`
1078
1143
  },
1079
1144
  default: {
1080
1145
  background: color.white,