@simplybusiness/mobius 4.8.7 → 4.9.0
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 +18 -0
- package/dist/cjs/components/Button/Button.js +8 -18
- package/dist/cjs/components/Button/Button.js.map +1 -1
- package/dist/cjs/components/Checkbox/Checkbox.js +8 -5
- package/dist/cjs/components/Checkbox/Checkbox.js.map +1 -1
- package/dist/cjs/components/Checkbox/CheckboxGroup.js +8 -5
- package/dist/cjs/components/Checkbox/CheckboxGroup.js.map +1 -1
- package/dist/cjs/components/Modal/Modal.js +8 -8
- package/dist/cjs/components/Modal/Modal.js.map +1 -1
- package/dist/cjs/components/NumberField/NumberField.js +8 -8
- package/dist/cjs/components/NumberField/NumberField.js.map +1 -1
- package/dist/cjs/components/Radio/RadioGroup.js +9 -6
- package/dist/cjs/components/Radio/RadioGroup.js.map +1 -1
- package/dist/cjs/components/Select/Select.js +11 -7
- package/dist/cjs/components/Select/Select.js.map +1 -1
- package/dist/cjs/components/TextArea/TextArea.js +6 -7
- package/dist/cjs/components/TextArea/TextArea.js.map +1 -1
- package/dist/cjs/components/TextField/TextField.js +11 -10
- package/dist/cjs/components/TextField/TextField.js.map +1 -1
- package/dist/cjs/hooks/index.js +2 -0
- package/dist/cjs/hooks/index.js.map +1 -1
- package/dist/cjs/hooks/useDeprecationWarning/index.js +20 -0
- package/dist/cjs/hooks/useDeprecationWarning/index.js.map +1 -0
- package/dist/cjs/hooks/useDeprecationWarning/useDeprecationWarning.js +32 -0
- package/dist/cjs/hooks/useDeprecationWarning/useDeprecationWarning.js.map +1 -0
- package/dist/cjs/hooks/useTextField/useTextField.js +1 -1
- package/dist/cjs/hooks/useTextField/useTextField.js.map +1 -1
- package/dist/cjs/hooks/useValidationClasses/index.js +20 -0
- package/dist/cjs/hooks/useValidationClasses/index.js.map +1 -0
- package/dist/cjs/hooks/useValidationClasses/useValidationClasses.js +26 -0
- package/dist/cjs/hooks/useValidationClasses/useValidationClasses.js.map +1 -0
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/components/Button/Button.js +5 -7
- package/dist/esm/components/Button/Button.js.map +1 -1
- package/dist/esm/components/Checkbox/Checkbox.js +8 -5
- package/dist/esm/components/Checkbox/Checkbox.js.map +1 -1
- package/dist/esm/components/Checkbox/CheckboxGroup.js +8 -5
- package/dist/esm/components/Checkbox/CheckboxGroup.js.map +1 -1
- package/dist/esm/components/Checkbox/types.js.map +1 -1
- package/dist/esm/components/Modal/Modal.js +8 -8
- package/dist/esm/components/Modal/Modal.js.map +1 -1
- package/dist/esm/components/NumberField/NumberField.js +9 -9
- package/dist/esm/components/NumberField/NumberField.js.map +1 -1
- package/dist/esm/components/Radio/RadioGroup.js +9 -6
- package/dist/esm/components/Radio/RadioGroup.js.map +1 -1
- package/dist/esm/components/Select/Select.js +11 -7
- package/dist/esm/components/Select/Select.js.map +1 -1
- package/dist/esm/components/TextArea/TextArea.js +7 -8
- package/dist/esm/components/TextArea/TextArea.js.map +1 -1
- package/dist/esm/components/TextField/TextField.js +12 -11
- package/dist/esm/components/TextField/TextField.js.map +1 -1
- package/dist/esm/hooks/index.js +2 -0
- package/dist/esm/hooks/index.js.map +1 -1
- package/dist/esm/hooks/useDeprecationWarning/index.js +3 -0
- package/dist/esm/hooks/useDeprecationWarning/index.js.map +1 -0
- package/dist/esm/hooks/useDeprecationWarning/useDeprecationWarning.js +22 -0
- package/dist/esm/hooks/useDeprecationWarning/useDeprecationWarning.js.map +1 -0
- package/dist/esm/hooks/useTextField/types.js.map +1 -1
- package/dist/esm/hooks/useTextField/useTextField.js +1 -1
- package/dist/esm/hooks/useTextField/useTextField.js.map +1 -1
- package/dist/esm/hooks/useValidationClasses/index.js +3 -0
- package/dist/esm/hooks/useValidationClasses/index.js.map +1 -0
- package/dist/esm/hooks/useValidationClasses/useValidationClasses.js +16 -0
- package/dist/esm/hooks/useValidationClasses/useValidationClasses.js.map +1 -0
- package/dist/types/components/Button/Button.d.ts +0 -1
- package/dist/types/components/Checkbox/types.d.ts +3 -4
- package/dist/types/components/NumberField/NumberField.d.ts +2 -2
- package/dist/types/components/Radio/RadioGroup.d.ts +2 -2
- package/dist/types/components/TextArea/TextArea.d.ts +0 -1
- package/dist/types/hooks/index.d.ts +2 -0
- package/dist/types/hooks/useDeprecationWarning/index.d.ts +1 -0
- package/dist/types/hooks/useDeprecationWarning/useDeprecationWarning.d.ts +2 -0
- package/dist/types/hooks/useDeprecationWarning/useDeprecationWarning.test.d.ts +1 -0
- package/dist/types/hooks/useTextField/types.d.ts +2 -3
- package/dist/types/hooks/useValidationClasses/index.d.ts +1 -0
- package/dist/types/hooks/useValidationClasses/useValidationClasses.d.ts +3 -0
- package/dist/types/hooks/useValidationClasses/useValidationClasses.test.d.ts +1 -0
- package/package.json +18 -18
- package/src/components/Accordion/Accordion.stories.tsx +2 -2
- package/src/components/Button/Button.test.tsx +4 -2
- package/src/components/Button/Button.tsx +5 -13
- package/src/components/Checkbox/Checkbox.stories.tsx +3 -11
- package/src/components/Checkbox/Checkbox.test.tsx +3 -3
- package/src/components/Checkbox/Checkbox.tsx +8 -3
- package/src/components/Checkbox/CheckboxGroup.stories.tsx +9 -12
- package/src/components/Checkbox/CheckboxGroup.test.tsx +2 -5
- package/src/components/Checkbox/CheckboxGroup.tsx +12 -5
- package/src/components/Checkbox/types.ts +3 -4
- package/src/components/Modal/Modal.tsx +9 -16
- package/src/components/NumberField/NumberField.stories.tsx +3 -12
- package/src/components/NumberField/NumberField.tsx +26 -14
- package/src/components/PasswordField/PasswordField.stories.tsx +1 -10
- package/src/components/Radio/Radio.stories.tsx +3 -11
- package/src/components/Radio/Radio.test.tsx +3 -3
- package/src/components/Radio/RadioGroup.tsx +11 -6
- package/src/components/Select/Select.mdx +2 -2
- package/src/components/Select/Select.stories.tsx +4 -11
- package/src/components/Select/Select.test.tsx +1 -1
- package/src/components/Select/Select.tsx +13 -6
- package/src/components/TextArea/TextArea.stories.tsx +4 -13
- package/src/components/TextArea/TextArea.test.tsx +2 -6
- package/src/components/TextArea/TextArea.tsx +16 -10
- package/src/components/TextField/TextField.stories.tsx +3 -11
- package/src/components/TextField/TextField.test.tsx +5 -13
- package/src/components/TextField/TextField.tsx +22 -11
- package/src/hooks/index.tsx +2 -0
- package/src/hooks/useDeprecationWarning/index.ts +1 -0
- package/src/hooks/useDeprecationWarning/useDeprecationWarning.test.ts +51 -0
- package/src/hooks/useDeprecationWarning/useDeprecationWarning.ts +33 -0
- package/src/hooks/useTextField/types.tsx +2 -2
- package/src/hooks/useTextField/useTextField.test.tsx +7 -9
- package/src/hooks/useTextField/useTextField.tsx +1 -6
- package/src/hooks/useValidationClasses/index.ts +1 -0
- package/src/hooks/useValidationClasses/useValidationClasses.test.ts +85 -0
- package/src/hooks/useValidationClasses/useValidationClasses.ts +23 -0
|
@@ -17,6 +17,7 @@ import { ErrorMessage } from "../ErrorMessage";
|
|
|
17
17
|
import { Flex } from "../Flex";
|
|
18
18
|
import { Label } from "../Label";
|
|
19
19
|
import { OptionProps } from "../Option";
|
|
20
|
+
import { useValidationClasses } from "../../hooks";
|
|
20
21
|
|
|
21
22
|
export type SelectElementType = HTMLSelectElement;
|
|
22
23
|
export interface SelectProps
|
|
@@ -43,6 +44,7 @@ const Select: ForwardedRefComponent<SelectProps, SelectElementType> =
|
|
|
43
44
|
label,
|
|
44
45
|
onChange,
|
|
45
46
|
validationState,
|
|
47
|
+
isInvalid,
|
|
46
48
|
errorMessage,
|
|
47
49
|
isDisabled = false,
|
|
48
50
|
isRequired,
|
|
@@ -54,30 +56,35 @@ const Select: ForwardedRefComponent<SelectProps, SelectElementType> =
|
|
|
54
56
|
...otherProps,
|
|
55
57
|
});
|
|
56
58
|
|
|
59
|
+
const validationClasses = useValidationClasses({
|
|
60
|
+
validationState,
|
|
61
|
+
isInvalid,
|
|
62
|
+
});
|
|
63
|
+
|
|
57
64
|
const stateClasses = {
|
|
58
65
|
"--is-disabled": isDisabled,
|
|
59
|
-
"--is-valid": validationState === "valid",
|
|
60
|
-
"--is-invalid": validationState === "invalid",
|
|
61
66
|
"--is-required": typeof isRequired === "boolean" && isRequired,
|
|
62
67
|
"--is-optional": typeof isRequired === "boolean" && !isRequired,
|
|
63
68
|
};
|
|
64
69
|
|
|
70
|
+
const sharedClasses = classNames(validationClasses, stateClasses);
|
|
71
|
+
|
|
65
72
|
const wrapperClasses = classNames(
|
|
66
73
|
"mobius/SelectWrapper",
|
|
67
|
-
|
|
74
|
+
sharedClasses,
|
|
68
75
|
otherProps.className,
|
|
69
76
|
);
|
|
70
77
|
const selectClasses = classNames(
|
|
71
78
|
"mobius/Select",
|
|
72
|
-
|
|
79
|
+
sharedClasses,
|
|
73
80
|
otherProps.className,
|
|
74
81
|
);
|
|
75
82
|
const labelClasses = classNames(
|
|
76
83
|
"mobius/Label",
|
|
77
|
-
|
|
84
|
+
sharedClasses,
|
|
78
85
|
otherProps.className,
|
|
79
86
|
);
|
|
80
|
-
const iconClasses = classNames("mobius/SelectIcon",
|
|
87
|
+
const iconClasses = classNames("mobius/SelectIcon", sharedClasses);
|
|
81
88
|
const errorMessageId = useId();
|
|
82
89
|
const shouldErrorMessageShow = errorMessage ? errorMessageId : undefined;
|
|
83
90
|
const describedBy = spaceDelimitedList([
|
|
@@ -9,16 +9,7 @@ const meta: Meta<typeof TextArea> = {
|
|
|
9
9
|
title: "Forms/TextArea",
|
|
10
10
|
component: TextArea,
|
|
11
11
|
argTypes: {
|
|
12
|
-
validationState
|
|
13
|
-
options: ["valid", "invalid", "neither"],
|
|
14
|
-
control: { type: "radio" },
|
|
15
|
-
mapping: {
|
|
16
|
-
valid: "valid",
|
|
17
|
-
invalid: "invalid",
|
|
18
|
-
neither: "",
|
|
19
|
-
},
|
|
20
|
-
},
|
|
21
|
-
...excludeControls("description"),
|
|
12
|
+
...excludeControls("description", "validationState"),
|
|
22
13
|
},
|
|
23
14
|
args: {
|
|
24
15
|
isReadOnly: false,
|
|
@@ -75,7 +66,7 @@ export const WithError: StoryType = {
|
|
|
75
66
|
defaultValue: "John Doe",
|
|
76
67
|
isDisabled: false,
|
|
77
68
|
errorMessage: "This is an error message",
|
|
78
|
-
|
|
69
|
+
isInvalid: true,
|
|
79
70
|
},
|
|
80
71
|
};
|
|
81
72
|
|
|
@@ -87,7 +78,7 @@ export const Valid: StoryType = {
|
|
|
87
78
|
defaultValue: "John Doe",
|
|
88
79
|
isDisabled: false,
|
|
89
80
|
errorMessage: "",
|
|
90
|
-
|
|
81
|
+
isInvalid: false,
|
|
91
82
|
},
|
|
92
83
|
};
|
|
93
84
|
|
|
@@ -99,7 +90,7 @@ export const Invalid: StoryType = {
|
|
|
99
90
|
defaultValue: "John Doe",
|
|
100
91
|
isDisabled: false,
|
|
101
92
|
errorMessage: "",
|
|
102
|
-
|
|
93
|
+
isInvalid: true,
|
|
103
94
|
},
|
|
104
95
|
};
|
|
105
96
|
|
|
@@ -102,16 +102,12 @@ describe("TextArea", () => {
|
|
|
102
102
|
});
|
|
103
103
|
|
|
104
104
|
it("includes valid class name", () => {
|
|
105
|
-
render(
|
|
106
|
-
<TextArea label="Test" data-testid="test" validationState="valid" />,
|
|
107
|
-
);
|
|
105
|
+
render(<TextArea label="Test" data-testid="test" isInvalid={false} />);
|
|
108
106
|
expect(screen.getByTestId("test")).toHaveClass("--is-valid");
|
|
109
107
|
});
|
|
110
108
|
|
|
111
109
|
it("includes invalid class name", () => {
|
|
112
|
-
render(
|
|
113
|
-
<TextArea label="Test" data-testid="test" validationState="invalid" />,
|
|
114
|
-
);
|
|
110
|
+
render(<TextArea label="Test" data-testid="test" isInvalid />);
|
|
115
111
|
expect(screen.getByTestId("test")).toHaveClass("--is-invalid");
|
|
116
112
|
});
|
|
117
113
|
});
|
|
@@ -8,7 +8,11 @@ import { Label } from "../Label";
|
|
|
8
8
|
import { ForwardedRefComponent } from "../../types/components";
|
|
9
9
|
import { Flex } from "../Flex";
|
|
10
10
|
import { ErrorMessage } from "../ErrorMessage";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
UseTextFieldProps,
|
|
13
|
+
useTextField,
|
|
14
|
+
useValidationClasses,
|
|
15
|
+
} from "../../hooks";
|
|
12
16
|
|
|
13
17
|
export type TextAreaElementType = HTMLTextAreaElement;
|
|
14
18
|
|
|
@@ -18,7 +22,6 @@ export interface TextAreaProps
|
|
|
18
22
|
RefAttributes<TextAreaElementType> {
|
|
19
23
|
className?: string;
|
|
20
24
|
errorMessage?: string;
|
|
21
|
-
validationState?: "valid" | "invalid";
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
export type TextAreaRef = Ref<TextAreaElementType>;
|
|
@@ -31,6 +34,7 @@ const TextArea: ForwardedRefComponent<TextAreaProps, TextAreaElementType> =
|
|
|
31
34
|
errorMessage,
|
|
32
35
|
label,
|
|
33
36
|
validationState,
|
|
37
|
+
isInvalid,
|
|
34
38
|
...otherProps
|
|
35
39
|
} = props;
|
|
36
40
|
|
|
@@ -38,15 +42,17 @@ const TextArea: ForwardedRefComponent<TextAreaProps, TextAreaElementType> =
|
|
|
38
42
|
|
|
39
43
|
// Set class name for atom, including interaction state
|
|
40
44
|
const classes = classNames("mobius", "mobius/TextArea", className);
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
});
|
|
45
|
-
const labelClasses = classNames({
|
|
46
|
-
"--is-valid": validationState === "valid",
|
|
47
|
-
"--is-invalid": validationState === "invalid",
|
|
48
|
-
"--is-disabled": isDisabled,
|
|
45
|
+
const validationClasses = useValidationClasses({
|
|
46
|
+
validationState,
|
|
47
|
+
isInvalid,
|
|
49
48
|
});
|
|
49
|
+
const inputClasses = classNames("mobius/TextAreaInput", validationClasses);
|
|
50
|
+
const labelClasses = classNames(
|
|
51
|
+
{
|
|
52
|
+
"--is-disabled": isDisabled,
|
|
53
|
+
},
|
|
54
|
+
validationClasses,
|
|
55
|
+
);
|
|
50
56
|
|
|
51
57
|
return (
|
|
52
58
|
<Flex flexDirection="column" className={classes}>
|
|
@@ -16,15 +16,6 @@ const meta: Meta<typeof TextField> = {
|
|
|
16
16
|
component: TextField,
|
|
17
17
|
excludeStories: ["ExampleHorizontal"],
|
|
18
18
|
argTypes: {
|
|
19
|
-
validationState: {
|
|
20
|
-
options: ["valid", "invalid", "neither"],
|
|
21
|
-
control: { type: "radio" },
|
|
22
|
-
mapping: {
|
|
23
|
-
valid: "valid",
|
|
24
|
-
invalid: "invalid",
|
|
25
|
-
neither: "",
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
19
|
type: {
|
|
29
20
|
options: ["text", "password", "email", "tel", "url", "hidden"],
|
|
30
21
|
control: { type: "radio" },
|
|
@@ -35,6 +26,7 @@ const meta: Meta<typeof TextField> = {
|
|
|
35
26
|
"prefixOutside",
|
|
36
27
|
"suffixInside",
|
|
37
28
|
"suffixOutside",
|
|
29
|
+
"validationState",
|
|
38
30
|
),
|
|
39
31
|
},
|
|
40
32
|
args: {
|
|
@@ -104,7 +96,7 @@ export const Valid: StoryType = {
|
|
|
104
96
|
defaultValue: "john.doe@email.com",
|
|
105
97
|
type: "text",
|
|
106
98
|
isDisabled: false,
|
|
107
|
-
|
|
99
|
+
isInvalid: false,
|
|
108
100
|
errorMessage: "",
|
|
109
101
|
},
|
|
110
102
|
};
|
|
@@ -129,7 +121,7 @@ export const Invalid: StoryType = {
|
|
|
129
121
|
defaultValue: "john.doe@email.com",
|
|
130
122
|
type: "text",
|
|
131
123
|
isDisabled: false,
|
|
132
|
-
|
|
124
|
+
isInvalid: true,
|
|
133
125
|
errorMessage: "This is an error message",
|
|
134
126
|
},
|
|
135
127
|
};
|
|
@@ -96,22 +96,14 @@ describe("Test for TextField", () => {
|
|
|
96
96
|
|
|
97
97
|
it("includes valid class name", () => {
|
|
98
98
|
const { container } = render(
|
|
99
|
-
<TextField
|
|
100
|
-
label="TextField"
|
|
101
|
-
data-testid="test"
|
|
102
|
-
validationState="valid"
|
|
103
|
-
/>,
|
|
99
|
+
<TextField label="TextField" data-testid="test" isInvalid={false} />,
|
|
104
100
|
);
|
|
105
101
|
expect(container.firstChild).toHaveClass("--is-valid");
|
|
106
102
|
});
|
|
107
103
|
|
|
108
104
|
it("includes invalid class name", () => {
|
|
109
105
|
const { container } = render(
|
|
110
|
-
<TextField
|
|
111
|
-
label="TextField"
|
|
112
|
-
data-testid="test"
|
|
113
|
-
validationState="invalid"
|
|
114
|
-
/>,
|
|
106
|
+
<TextField label="TextField" data-testid="test" isInvalid />,
|
|
115
107
|
);
|
|
116
108
|
expect(container.firstChild).toHaveClass("--is-invalid");
|
|
117
109
|
});
|
|
@@ -149,12 +141,12 @@ describe("Test for TextField", () => {
|
|
|
149
141
|
});
|
|
150
142
|
|
|
151
143
|
it("includes valid class name", () => {
|
|
152
|
-
render(<TextField label="TextField"
|
|
144
|
+
render(<TextField label="TextField" isInvalid={false} />);
|
|
153
145
|
expect(screen.getByLabelText("TextField")).toHaveClass("--is-valid");
|
|
154
146
|
});
|
|
155
147
|
|
|
156
148
|
it("includes invalid class name", () => {
|
|
157
|
-
render(<TextField label="TextField"
|
|
149
|
+
render(<TextField label="TextField" isInvalid />);
|
|
158
150
|
expect(screen.getByLabelText("TextField")).toHaveClass("--is-invalid");
|
|
159
151
|
});
|
|
160
152
|
|
|
@@ -185,7 +177,7 @@ describe("Test for TextField", () => {
|
|
|
185
177
|
label="Name"
|
|
186
178
|
name="name"
|
|
187
179
|
errorMessage="Required"
|
|
188
|
-
|
|
180
|
+
isInvalid
|
|
189
181
|
/>,
|
|
190
182
|
);
|
|
191
183
|
expect(getByLabelText("Name").getAttribute("aria-invalid")).toBeTruthy();
|
|
@@ -9,7 +9,11 @@ import {
|
|
|
9
9
|
RefAttributes,
|
|
10
10
|
forwardRef,
|
|
11
11
|
} from "react";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
UseTextFieldProps,
|
|
14
|
+
useTextField,
|
|
15
|
+
useValidationClasses,
|
|
16
|
+
} from "../../hooks";
|
|
13
17
|
import { DOMProps, FocusEvents } from "../../types";
|
|
14
18
|
import { ForwardedRefComponent } from "../../types/components";
|
|
15
19
|
import { ErrorMessage } from "../ErrorMessage";
|
|
@@ -56,6 +60,7 @@ const TextField: ForwardedRefComponent<TextFieldProps, TextFieldElementType> =
|
|
|
56
60
|
isDisabled,
|
|
57
61
|
type = "text",
|
|
58
62
|
validationState,
|
|
63
|
+
isInvalid,
|
|
59
64
|
className,
|
|
60
65
|
label,
|
|
61
66
|
errorMessage,
|
|
@@ -75,37 +80,43 @@ const TextField: ForwardedRefComponent<TextFieldProps, TextFieldElementType> =
|
|
|
75
80
|
|
|
76
81
|
const hidden = type === "hidden";
|
|
77
82
|
|
|
83
|
+
const validationClasses = useValidationClasses({
|
|
84
|
+
validationState,
|
|
85
|
+
isInvalid,
|
|
86
|
+
});
|
|
87
|
+
|
|
78
88
|
const textfieldClasses = {
|
|
79
89
|
"--is-disabled": isDisabled,
|
|
80
|
-
"--is-valid": validationState === "valid",
|
|
81
|
-
"--is-invalid": validationState === "invalid",
|
|
82
90
|
"--is-required": typeof isRequired === "boolean" && isRequired,
|
|
83
91
|
"--is-optional": typeof isRequired === "boolean" && !isRequired,
|
|
84
92
|
"--is-hidden": hidden,
|
|
85
93
|
[className || ""]: true,
|
|
86
94
|
};
|
|
87
95
|
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
96
|
+
const sharedClasses = classNames(validationClasses, textfieldClasses);
|
|
97
|
+
|
|
98
|
+
const labelClasses = classNames(
|
|
99
|
+
{
|
|
100
|
+
"--is-disabled": isDisabled,
|
|
101
|
+
},
|
|
102
|
+
validationClasses,
|
|
103
|
+
);
|
|
93
104
|
|
|
94
105
|
const containerClasses = classNames(
|
|
95
106
|
"mobius",
|
|
96
107
|
"mobius/TextField",
|
|
97
|
-
|
|
108
|
+
sharedClasses,
|
|
98
109
|
);
|
|
99
110
|
|
|
100
111
|
const inputClasses = classNames(
|
|
101
112
|
"mobius",
|
|
102
113
|
"mobius/TextInput",
|
|
103
|
-
|
|
114
|
+
sharedClasses,
|
|
104
115
|
);
|
|
105
116
|
|
|
106
117
|
const inputWrapperClasses = classNames(
|
|
107
118
|
"mobius/TextFieldInputWrapper",
|
|
108
|
-
|
|
119
|
+
sharedClasses,
|
|
109
120
|
);
|
|
110
121
|
|
|
111
122
|
return (
|
package/src/hooks/index.tsx
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
export * from "./useBodyScrollLock";
|
|
2
2
|
export * from "./useBreakpoint";
|
|
3
3
|
export * from "./useButton";
|
|
4
|
+
export * from "./useDeprecationWarning";
|
|
4
5
|
export * from "./useDialog";
|
|
5
6
|
export * from "./useDialogPolyfill";
|
|
6
7
|
export * from "./useLabel";
|
|
7
8
|
export * from "./useOnClickOutside";
|
|
8
9
|
export * from "./useTextField";
|
|
10
|
+
export * from "./useValidationClasses";
|
|
9
11
|
export * from "./useWindowEvent";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./useDeprecationWarning";
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { renderHook } from "@testing-library/react";
|
|
2
|
+
import { useDeprecationWarning } from "./useDeprecationWarning";
|
|
3
|
+
|
|
4
|
+
const deprecationWarning = (prop: string) =>
|
|
5
|
+
`Deprecation warning: Mobius no longer supports the prop '${prop}'. Please refer to the documentation for more information.`;
|
|
6
|
+
|
|
7
|
+
describe("useDeprecationWarning", () => {
|
|
8
|
+
describe("given there are no deprecated props", () => {
|
|
9
|
+
it("should not log a warning", () => {
|
|
10
|
+
const warnSpy = jest.spyOn(console, "warn").mockImplementation();
|
|
11
|
+
|
|
12
|
+
renderHook(() => useDeprecationWarning({}));
|
|
13
|
+
|
|
14
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe("given deprecated props are passed", () => {
|
|
19
|
+
it("should log a warning for each deprecated prop", () => {
|
|
20
|
+
const warnSpy = jest.spyOn(console, "warn").mockImplementation();
|
|
21
|
+
|
|
22
|
+
renderHook(() =>
|
|
23
|
+
useDeprecationWarning({
|
|
24
|
+
size: "large",
|
|
25
|
+
appElement: "body",
|
|
26
|
+
preventCloseOnEsc: true,
|
|
27
|
+
shouldFocusAfterRender: true,
|
|
28
|
+
parentSelector: "#modal-parent",
|
|
29
|
+
}),
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
expect(warnSpy).toHaveBeenCalledTimes(5);
|
|
33
|
+
|
|
34
|
+
const warningSize = deprecationWarning("size");
|
|
35
|
+
const warningAppElement = deprecationWarning("appElement");
|
|
36
|
+
const warningPreventCloseOnEsc = deprecationWarning("preventCloseOnEsc");
|
|
37
|
+
const warningShouldFocusAfterRender = deprecationWarning(
|
|
38
|
+
"shouldFocusAfterRender",
|
|
39
|
+
);
|
|
40
|
+
const warningParentSelector = deprecationWarning("parentSelector");
|
|
41
|
+
|
|
42
|
+
expect(warnSpy).toHaveBeenCalledWith(warningSize);
|
|
43
|
+
expect(warnSpy).toHaveBeenCalledWith(warningAppElement);
|
|
44
|
+
expect(warnSpy).toHaveBeenCalledWith(warningPreventCloseOnEsc);
|
|
45
|
+
expect(warnSpy).toHaveBeenCalledWith(warningShouldFocusAfterRender);
|
|
46
|
+
expect(warnSpy).toHaveBeenCalledWith(warningParentSelector);
|
|
47
|
+
|
|
48
|
+
warnSpy.mockRestore();
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { useRef } from "react";
|
|
2
|
+
|
|
3
|
+
export type UseDeprecationWarningProps = Record<string, unknown>;
|
|
4
|
+
|
|
5
|
+
export const useDeprecationWarning = (props: UseDeprecationWarningProps) => {
|
|
6
|
+
const hasWarned = useRef<boolean>(false);
|
|
7
|
+
const deprecatedProps = Object.entries(props).reduce<string[]>(
|
|
8
|
+
(acc, [key, value]) => {
|
|
9
|
+
if (value) {
|
|
10
|
+
acc.push(key);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return acc;
|
|
14
|
+
},
|
|
15
|
+
[],
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
const noDeprecatedProps = deprecatedProps.length === 0;
|
|
19
|
+
|
|
20
|
+
if (noDeprecatedProps) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!hasWarned.current) {
|
|
25
|
+
deprecatedProps.forEach(prop => {
|
|
26
|
+
console.warn(
|
|
27
|
+
`Deprecation warning: Mobius no longer supports the prop '${prop}'. Please refer to the documentation for more information.`,
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
hasWarned.current = true;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { InputHTMLAttributes } from "react";
|
|
2
2
|
import { UseLabelProps, UseLabelReturn } from "../useLabel/useLabel";
|
|
3
|
-
import { FocusEvents } from "../../types";
|
|
3
|
+
import { FocusEvents, Validation } from "../../types";
|
|
4
4
|
|
|
5
5
|
export type UseTextFieldProps = UseLabelProps &
|
|
6
6
|
FocusEvents &
|
|
7
|
+
Validation &
|
|
7
8
|
Pick<
|
|
8
9
|
InputHTMLAttributes<HTMLInputElement>,
|
|
9
10
|
| "defaultValue"
|
|
@@ -41,7 +42,6 @@ export type UseTextFieldProps = UseLabelProps &
|
|
|
41
42
|
isDisabled?: boolean | undefined;
|
|
42
43
|
isReadOnly?: boolean | undefined;
|
|
43
44
|
isRequired?: boolean | undefined;
|
|
44
|
-
validationState?: "valid" | "invalid" | undefined;
|
|
45
45
|
description?: string | undefined;
|
|
46
46
|
errorMessage?: string | undefined;
|
|
47
47
|
"aria-describedby"?: string | undefined;
|
|
@@ -137,27 +137,25 @@ describe("useTextField", () => {
|
|
|
137
137
|
});
|
|
138
138
|
});
|
|
139
139
|
|
|
140
|
-
describe("
|
|
141
|
-
it("should set aria-invalid to true when
|
|
142
|
-
render(<WrapperComponent label={TEST_LABEL}
|
|
140
|
+
describe("validation", () => {
|
|
141
|
+
it("should set aria-invalid to true when isInvalid is set to true", () => {
|
|
142
|
+
render(<WrapperComponent label={TEST_LABEL} isInvalid />);
|
|
143
143
|
expect(screen.getByLabelText(TEST_LABEL)).toHaveAttribute(
|
|
144
144
|
"aria-invalid",
|
|
145
145
|
"true",
|
|
146
146
|
);
|
|
147
147
|
});
|
|
148
148
|
|
|
149
|
-
it("should set aria-invalid to false when
|
|
150
|
-
render(<WrapperComponent label={TEST_LABEL}
|
|
149
|
+
it("should set aria-invalid to false when isInvalid is set to false", () => {
|
|
150
|
+
render(<WrapperComponent label={TEST_LABEL} isInvalid={false} />);
|
|
151
151
|
expect(screen.getByLabelText(TEST_LABEL)).toHaveAttribute(
|
|
152
152
|
"aria-invalid",
|
|
153
153
|
"false",
|
|
154
154
|
);
|
|
155
155
|
});
|
|
156
156
|
|
|
157
|
-
it("should not set aria-invalid when
|
|
158
|
-
render(
|
|
159
|
-
<WrapperComponent label={TEST_LABEL} validationState={undefined} />,
|
|
160
|
-
);
|
|
157
|
+
it("should not set aria-invalid when isInvalid is not provided", () => {
|
|
158
|
+
render(<WrapperComponent label={TEST_LABEL} />);
|
|
161
159
|
expect(screen.getByLabelText(TEST_LABEL)).not.toHaveAttribute(
|
|
162
160
|
"aria-invalid",
|
|
163
161
|
);
|
|
@@ -35,12 +35,7 @@ export function useTextField(props: UseTextFieldProps): UseTextFieldReturn {
|
|
|
35
35
|
disabled: isDisabled,
|
|
36
36
|
readOnly: isReadOnly,
|
|
37
37
|
"aria-required": isRequired === true ? true : undefined,
|
|
38
|
-
"aria-invalid":
|
|
39
|
-
props.validationState === "invalid"
|
|
40
|
-
? true
|
|
41
|
-
: props.validationState === "valid"
|
|
42
|
-
? false
|
|
43
|
-
: undefined,
|
|
38
|
+
"aria-invalid": props.isInvalid,
|
|
44
39
|
"aria-describedby": ariaDescribedBy,
|
|
45
40
|
"aria-errormessage": props["aria-errormessage"],
|
|
46
41
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./useValidationClasses";
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { renderHook } from "@testing-library/react";
|
|
2
|
+
import { useValidationClasses } from "./useValidationClasses";
|
|
3
|
+
|
|
4
|
+
describe("useValidationClasses", () => {
|
|
5
|
+
describe("given validationState prop is used", () => {
|
|
6
|
+
it("should warn about deprecation", () => {
|
|
7
|
+
const warnSpy = jest.spyOn(console, "warn").mockImplementation(() => {});
|
|
8
|
+
const validationState = "valid";
|
|
9
|
+
|
|
10
|
+
renderHook(() => useValidationClasses({ validationState }));
|
|
11
|
+
|
|
12
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
13
|
+
"Deprecation warning: Mobius no longer supports the prop 'validationState'. Please refer to the documentation for more information.",
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
warnSpy.mockRestore();
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe("validationState", () => {
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
jest.spyOn(console, "warn").mockImplementation(() => {});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe("given validationState is set to invalid", () => {
|
|
26
|
+
it("should return '--is-invalid'", () => {
|
|
27
|
+
const { result } = renderHook(() =>
|
|
28
|
+
useValidationClasses({ validationState: "invalid" }),
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
expect(result.current).toBe("--is-invalid");
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe("given validationState is set to valid", () => {
|
|
36
|
+
it("should return '--is-valid'", () => {
|
|
37
|
+
const { result } = renderHook(() =>
|
|
38
|
+
useValidationClasses({ validationState: "valid" }),
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
expect(result.current).toBe("--is-valid");
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("given validationState is set to undefined", () => {
|
|
46
|
+
it("should return an empty string", () => {
|
|
47
|
+
const { result } = renderHook(() =>
|
|
48
|
+
useValidationClasses({ validationState: undefined }),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
expect(result.current).toBe("");
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe("given isInvalid is set to true", () => {
|
|
57
|
+
it("should return '--is-invalid'", () => {
|
|
58
|
+
const { result } = renderHook(() =>
|
|
59
|
+
useValidationClasses({ isInvalid: true }),
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
expect(result.current).toBe("--is-invalid");
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe("given isInvalid is set to false", () => {
|
|
67
|
+
it("should return '--is-valid'", () => {
|
|
68
|
+
const { result } = renderHook(() =>
|
|
69
|
+
useValidationClasses({ isInvalid: false }),
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
expect(result.current).toBe("--is-valid");
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe("given isInvalid is set to undefined", () => {
|
|
77
|
+
it("should return an empty string", () => {
|
|
78
|
+
const { result } = renderHook(() =>
|
|
79
|
+
useValidationClasses({ isInvalid: undefined }),
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
expect(result.current).toBe("");
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Validation } from "../../types";
|
|
2
|
+
import { useDeprecationWarning } from "../useDeprecationWarning";
|
|
3
|
+
|
|
4
|
+
export type GetValidationClassesProps = Pick<
|
|
5
|
+
Validation,
|
|
6
|
+
"validationState" | "isInvalid"
|
|
7
|
+
>;
|
|
8
|
+
|
|
9
|
+
export const useValidationClasses = (props: GetValidationClassesProps) => {
|
|
10
|
+
const { validationState, isInvalid } = props;
|
|
11
|
+
|
|
12
|
+
useDeprecationWarning({ validationState });
|
|
13
|
+
|
|
14
|
+
if (isInvalid || validationState === "invalid") {
|
|
15
|
+
return "--is-invalid";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (isInvalid === false || validationState === "valid") {
|
|
19
|
+
return "--is-valid";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return "";
|
|
23
|
+
};
|