@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.
Files changed (115) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/cjs/components/Button/Button.js +8 -18
  3. package/dist/cjs/components/Button/Button.js.map +1 -1
  4. package/dist/cjs/components/Checkbox/Checkbox.js +8 -5
  5. package/dist/cjs/components/Checkbox/Checkbox.js.map +1 -1
  6. package/dist/cjs/components/Checkbox/CheckboxGroup.js +8 -5
  7. package/dist/cjs/components/Checkbox/CheckboxGroup.js.map +1 -1
  8. package/dist/cjs/components/Modal/Modal.js +8 -8
  9. package/dist/cjs/components/Modal/Modal.js.map +1 -1
  10. package/dist/cjs/components/NumberField/NumberField.js +8 -8
  11. package/dist/cjs/components/NumberField/NumberField.js.map +1 -1
  12. package/dist/cjs/components/Radio/RadioGroup.js +9 -6
  13. package/dist/cjs/components/Radio/RadioGroup.js.map +1 -1
  14. package/dist/cjs/components/Select/Select.js +11 -7
  15. package/dist/cjs/components/Select/Select.js.map +1 -1
  16. package/dist/cjs/components/TextArea/TextArea.js +6 -7
  17. package/dist/cjs/components/TextArea/TextArea.js.map +1 -1
  18. package/dist/cjs/components/TextField/TextField.js +11 -10
  19. package/dist/cjs/components/TextField/TextField.js.map +1 -1
  20. package/dist/cjs/hooks/index.js +2 -0
  21. package/dist/cjs/hooks/index.js.map +1 -1
  22. package/dist/cjs/hooks/useDeprecationWarning/index.js +20 -0
  23. package/dist/cjs/hooks/useDeprecationWarning/index.js.map +1 -0
  24. package/dist/cjs/hooks/useDeprecationWarning/useDeprecationWarning.js +32 -0
  25. package/dist/cjs/hooks/useDeprecationWarning/useDeprecationWarning.js.map +1 -0
  26. package/dist/cjs/hooks/useTextField/useTextField.js +1 -1
  27. package/dist/cjs/hooks/useTextField/useTextField.js.map +1 -1
  28. package/dist/cjs/hooks/useValidationClasses/index.js +20 -0
  29. package/dist/cjs/hooks/useValidationClasses/index.js.map +1 -0
  30. package/dist/cjs/hooks/useValidationClasses/useValidationClasses.js +26 -0
  31. package/dist/cjs/hooks/useValidationClasses/useValidationClasses.js.map +1 -0
  32. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  33. package/dist/esm/components/Button/Button.js +5 -7
  34. package/dist/esm/components/Button/Button.js.map +1 -1
  35. package/dist/esm/components/Checkbox/Checkbox.js +8 -5
  36. package/dist/esm/components/Checkbox/Checkbox.js.map +1 -1
  37. package/dist/esm/components/Checkbox/CheckboxGroup.js +8 -5
  38. package/dist/esm/components/Checkbox/CheckboxGroup.js.map +1 -1
  39. package/dist/esm/components/Checkbox/types.js.map +1 -1
  40. package/dist/esm/components/Modal/Modal.js +8 -8
  41. package/dist/esm/components/Modal/Modal.js.map +1 -1
  42. package/dist/esm/components/NumberField/NumberField.js +9 -9
  43. package/dist/esm/components/NumberField/NumberField.js.map +1 -1
  44. package/dist/esm/components/Radio/RadioGroup.js +9 -6
  45. package/dist/esm/components/Radio/RadioGroup.js.map +1 -1
  46. package/dist/esm/components/Select/Select.js +11 -7
  47. package/dist/esm/components/Select/Select.js.map +1 -1
  48. package/dist/esm/components/TextArea/TextArea.js +7 -8
  49. package/dist/esm/components/TextArea/TextArea.js.map +1 -1
  50. package/dist/esm/components/TextField/TextField.js +12 -11
  51. package/dist/esm/components/TextField/TextField.js.map +1 -1
  52. package/dist/esm/hooks/index.js +2 -0
  53. package/dist/esm/hooks/index.js.map +1 -1
  54. package/dist/esm/hooks/useDeprecationWarning/index.js +3 -0
  55. package/dist/esm/hooks/useDeprecationWarning/index.js.map +1 -0
  56. package/dist/esm/hooks/useDeprecationWarning/useDeprecationWarning.js +22 -0
  57. package/dist/esm/hooks/useDeprecationWarning/useDeprecationWarning.js.map +1 -0
  58. package/dist/esm/hooks/useTextField/types.js.map +1 -1
  59. package/dist/esm/hooks/useTextField/useTextField.js +1 -1
  60. package/dist/esm/hooks/useTextField/useTextField.js.map +1 -1
  61. package/dist/esm/hooks/useValidationClasses/index.js +3 -0
  62. package/dist/esm/hooks/useValidationClasses/index.js.map +1 -0
  63. package/dist/esm/hooks/useValidationClasses/useValidationClasses.js +16 -0
  64. package/dist/esm/hooks/useValidationClasses/useValidationClasses.js.map +1 -0
  65. package/dist/types/components/Button/Button.d.ts +0 -1
  66. package/dist/types/components/Checkbox/types.d.ts +3 -4
  67. package/dist/types/components/NumberField/NumberField.d.ts +2 -2
  68. package/dist/types/components/Radio/RadioGroup.d.ts +2 -2
  69. package/dist/types/components/TextArea/TextArea.d.ts +0 -1
  70. package/dist/types/hooks/index.d.ts +2 -0
  71. package/dist/types/hooks/useDeprecationWarning/index.d.ts +1 -0
  72. package/dist/types/hooks/useDeprecationWarning/useDeprecationWarning.d.ts +2 -0
  73. package/dist/types/hooks/useDeprecationWarning/useDeprecationWarning.test.d.ts +1 -0
  74. package/dist/types/hooks/useTextField/types.d.ts +2 -3
  75. package/dist/types/hooks/useValidationClasses/index.d.ts +1 -0
  76. package/dist/types/hooks/useValidationClasses/useValidationClasses.d.ts +3 -0
  77. package/dist/types/hooks/useValidationClasses/useValidationClasses.test.d.ts +1 -0
  78. package/package.json +18 -18
  79. package/src/components/Accordion/Accordion.stories.tsx +2 -2
  80. package/src/components/Button/Button.test.tsx +4 -2
  81. package/src/components/Button/Button.tsx +5 -13
  82. package/src/components/Checkbox/Checkbox.stories.tsx +3 -11
  83. package/src/components/Checkbox/Checkbox.test.tsx +3 -3
  84. package/src/components/Checkbox/Checkbox.tsx +8 -3
  85. package/src/components/Checkbox/CheckboxGroup.stories.tsx +9 -12
  86. package/src/components/Checkbox/CheckboxGroup.test.tsx +2 -5
  87. package/src/components/Checkbox/CheckboxGroup.tsx +12 -5
  88. package/src/components/Checkbox/types.ts +3 -4
  89. package/src/components/Modal/Modal.tsx +9 -16
  90. package/src/components/NumberField/NumberField.stories.tsx +3 -12
  91. package/src/components/NumberField/NumberField.tsx +26 -14
  92. package/src/components/PasswordField/PasswordField.stories.tsx +1 -10
  93. package/src/components/Radio/Radio.stories.tsx +3 -11
  94. package/src/components/Radio/Radio.test.tsx +3 -3
  95. package/src/components/Radio/RadioGroup.tsx +11 -6
  96. package/src/components/Select/Select.mdx +2 -2
  97. package/src/components/Select/Select.stories.tsx +4 -11
  98. package/src/components/Select/Select.test.tsx +1 -1
  99. package/src/components/Select/Select.tsx +13 -6
  100. package/src/components/TextArea/TextArea.stories.tsx +4 -13
  101. package/src/components/TextArea/TextArea.test.tsx +2 -6
  102. package/src/components/TextArea/TextArea.tsx +16 -10
  103. package/src/components/TextField/TextField.stories.tsx +3 -11
  104. package/src/components/TextField/TextField.test.tsx +5 -13
  105. package/src/components/TextField/TextField.tsx +22 -11
  106. package/src/hooks/index.tsx +2 -0
  107. package/src/hooks/useDeprecationWarning/index.ts +1 -0
  108. package/src/hooks/useDeprecationWarning/useDeprecationWarning.test.ts +51 -0
  109. package/src/hooks/useDeprecationWarning/useDeprecationWarning.ts +33 -0
  110. package/src/hooks/useTextField/types.tsx +2 -2
  111. package/src/hooks/useTextField/useTextField.test.tsx +7 -9
  112. package/src/hooks/useTextField/useTextField.tsx +1 -6
  113. package/src/hooks/useValidationClasses/index.ts +1 -0
  114. package/src/hooks/useValidationClasses/useValidationClasses.test.ts +85 -0
  115. 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
- stateClasses,
74
+ sharedClasses,
68
75
  otherProps.className,
69
76
  );
70
77
  const selectClasses = classNames(
71
78
  "mobius/Select",
72
- stateClasses,
79
+ sharedClasses,
73
80
  otherProps.className,
74
81
  );
75
82
  const labelClasses = classNames(
76
83
  "mobius/Label",
77
- stateClasses,
84
+ sharedClasses,
78
85
  otherProps.className,
79
86
  );
80
- const iconClasses = classNames("mobius/SelectIcon", stateClasses);
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
- validationState: "invalid",
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
- validationState: "valid",
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
- validationState: "invalid",
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 { UseTextFieldProps, useTextField } from "../../hooks";
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 inputClasses = classNames("mobius/TextAreaInput", {
42
- "--is-valid": validationState === "valid",
43
- "--is-invalid": validationState === "invalid",
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
- validationState: "valid",
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
- validationState: "invalid",
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" validationState="valid" />);
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" validationState="invalid" />);
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
- validationState="invalid"
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 { UseTextFieldProps, useTextField } from "../../hooks";
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 labelClasses = classNames({
89
- "--is-disabled": isDisabled,
90
- "--is-valid": validationState === "valid",
91
- "--is-invalid": validationState === "invalid",
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
- textfieldClasses,
108
+ sharedClasses,
98
109
  );
99
110
 
100
111
  const inputClasses = classNames(
101
112
  "mobius",
102
113
  "mobius/TextInput",
103
- textfieldClasses,
114
+ sharedClasses,
104
115
  );
105
116
 
106
117
  const inputWrapperClasses = classNames(
107
118
  "mobius/TextFieldInputWrapper",
108
- textfieldClasses,
119
+ sharedClasses,
109
120
  );
110
121
 
111
122
  return (
@@ -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("validationState", () => {
141
- it("should set aria-invalid to true when validationState is invalid", () => {
142
- render(<WrapperComponent label={TEST_LABEL} validationState="invalid" />);
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 validationState is valid", () => {
150
- render(<WrapperComponent label={TEST_LABEL} validationState="valid" />);
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 validationState is missing", () => {
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
+ };