@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 +28 -0
- package/dist/components/field-heading.d.ts +4 -0
- package/dist/components/text-area.d.ts +7 -1
- package/dist/components/text-field.d.ts +1 -4
- package/dist/es/index.js +110 -45
- package/dist/index.js +109 -44
- package/package.json +7 -7
- 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 +28 -8
- package/src/components/text-field.tsx +82 -44
- package/tsconfig-build.tsbuildinfo +1 -1
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
|
|
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", "
|
|
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
|
-
|
|
645
|
-
|
|
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
|
-
|
|
657
|
-
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
|
|
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: [
|
|
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:
|
|
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.
|
|
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
|
-
|
|
774
|
+
light: {
|
|
758
775
|
background: color.white,
|
|
759
|
-
border: `1px solid ${color.
|
|
776
|
+
border: `1px solid ${color.offBlack16}`,
|
|
760
777
|
color: color.offBlack,
|
|
761
778
|
"::placeholder": {
|
|
762
779
|
color: color.offBlack64
|
|
763
780
|
}
|
|
764
781
|
},
|
|
765
|
-
|
|
766
|
-
|
|
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
|
-
|
|
769
|
-
|
|
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: [
|
|
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:
|
|
1077
|
-
minHeight:
|
|
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,
|