@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
@@ -9,15 +9,6 @@ const meta: Meta<typeof Checkbox> = {
9
9
  title: "Forms/Checkbox",
10
10
  component: Checkbox,
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
12
  label: {
22
13
  control: { type: "text" },
23
14
  },
@@ -29,6 +20,7 @@ const meta: Meta<typeof Checkbox> = {
29
20
  "defaultSelected",
30
21
  "value",
31
22
  "groupDisabled",
23
+ "validationState",
32
24
  ),
33
25
  },
34
26
  decorators: [
@@ -71,7 +63,7 @@ export const Valid: StoryType = {
71
63
  value: "agree",
72
64
  isDisabled: false,
73
65
  errorMessage: "",
74
- validationState: "valid",
66
+ isInvalid: false,
75
67
  defaultSelected: false,
76
68
  isRequired: false,
77
69
  },
@@ -83,7 +75,7 @@ export const Invalid: StoryType = {
83
75
  label: "I agree",
84
76
  value: "agree",
85
77
  isDisabled: false,
86
- validationState: "invalid",
78
+ isInvalid: true,
87
79
  errorMessage: "This is an error message",
88
80
  defaultSelected: false,
89
81
  isRequired: false,
@@ -167,7 +167,7 @@ describe("Checkbox", () => {
167
167
  const optionText = "Agree";
168
168
 
169
169
  const { getByLabelText } = render(
170
- <Checkbox label={optionText} validationState="valid" value="value" />,
170
+ <Checkbox label={optionText} isInvalid={false} value="value" />,
171
171
  );
172
172
 
173
173
  const option = getByLabelText(optionText);
@@ -181,7 +181,7 @@ describe("Checkbox", () => {
181
181
  const { getByLabelText } = render(
182
182
  <Checkbox
183
183
  label={optionText}
184
- validationState="invalid"
184
+ isInvalid
185
185
  errorMessage="There is a problem"
186
186
  value="value"
187
187
  />,
@@ -260,7 +260,7 @@ describe("Checkbox", () => {
260
260
  const optionText = "Agree";
261
261
 
262
262
  const { getByLabelText } = render(
263
- <Checkbox label={optionText} value="value" validationState="invalid" />,
263
+ <Checkbox label={optionText} value="value" isInvalid />,
264
264
  );
265
265
 
266
266
  const option = getByLabelText(optionText);
@@ -14,6 +14,7 @@ import { ErrorMessage } from "../ErrorMessage";
14
14
  import { ForwardedRefComponent } from "../../types/components";
15
15
  import { CheckboxElementType, CheckboxProps, CheckboxRef } from "./types";
16
16
  import { spaceDelimitedList } from "../../utils/spaceDelimitedList";
17
+ import { useValidationClasses } from "../../hooks";
17
18
 
18
19
  export const Checkbox: ForwardedRefComponent<
19
20
  CheckboxProps,
@@ -25,6 +26,7 @@ export const Checkbox: ForwardedRefComponent<
25
26
  isDisabled,
26
27
  isRequired,
27
28
  validationState,
29
+ isInvalid,
28
30
  onChange,
29
31
  className,
30
32
  errorMessage,
@@ -38,15 +40,18 @@ export const Checkbox: ForwardedRefComponent<
38
40
  const fallbackRef = useRef<HTMLInputElement>(null);
39
41
  const refObj = ref || fallbackRef;
40
42
  const inputId = useId();
43
+ const validationClasses = useValidationClasses({
44
+ validationState,
45
+ isInvalid,
46
+ });
41
47
  const checkboxClasses = classNames(
42
48
  {
43
49
  "--is-disabled": isDisabled,
44
50
  "--is-selected": selected,
45
- "--is-valid": validationState === "valid",
46
- "--is-invalid": validationState === "invalid",
47
51
  "--is-required": typeof isRequired === "boolean" && isRequired,
48
52
  "--is-optional": typeof isRequired === "boolean" && !isRequired,
49
53
  },
54
+ validationClasses,
50
55
  className,
51
56
  );
52
57
  // Append an additional wrapper class name to differenitate from input
@@ -94,7 +99,7 @@ export const Checkbox: ForwardedRefComponent<
94
99
  <input
95
100
  aria-describedby={describedBy}
96
101
  aria-errormessage={shouldErrorMessageShow}
97
- aria-invalid={validationState === "invalid"}
102
+ aria-invalid={isInvalid}
98
103
  readOnly={isReadOnly}
99
104
  disabled={isDisabled}
100
105
  ref={refObj}
@@ -9,16 +9,13 @@ const meta: Meta<typeof CheckboxGroup> = {
9
9
  title: "Forms/CheckboxGroup",
10
10
  component: CheckboxGroup,
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("isRequired", "isReadOnly", "defaultValue", "value"),
12
+ ...excludeControls(
13
+ "isRequired",
14
+ "isReadOnly",
15
+ "defaultValue",
16
+ "value",
17
+ "validationState",
18
+ ),
22
19
  },
23
20
  decorators: [
24
21
  Story => (
@@ -96,7 +93,7 @@ export const Valid: StoryType = {
96
93
  label: "Value",
97
94
  errorMessage: "",
98
95
  isDisabled: false,
99
- validationState: "valid",
96
+ isInvalid: false,
100
97
  orientation: "vertical",
101
98
  isRequired: false,
102
99
  },
@@ -114,7 +111,7 @@ export const Invalid: StoryType = {
114
111
  label: "Value",
115
112
  errorMessage: "This field is required",
116
113
  isDisabled: false,
117
- validationState: "invalid",
114
+ isInvalid: true,
118
115
  orientation: "vertical",
119
116
  isRequired: false,
120
117
  },
@@ -379,7 +379,7 @@ describe("CheckboxGroup", () => {
379
379
  const optionText = "Agree";
380
380
 
381
381
  const { getByLabelText } = render(
382
- <CheckboxGroup validationState="valid">
382
+ <CheckboxGroup isInvalid={false}>
383
383
  <Checkbox label={optionText} value="value" />
384
384
  </CheckboxGroup>,
385
385
  );
@@ -393,10 +393,7 @@ describe("CheckboxGroup", () => {
393
393
  const optionText = "Agree";
394
394
 
395
395
  const { getByLabelText } = render(
396
- <CheckboxGroup
397
- validationState="invalid"
398
- errorMessage="There is a problem"
399
- >
396
+ <CheckboxGroup isInvalid errorMessage="There is a problem">
400
397
  <Checkbox label={optionText} value="value" />,
401
398
  </CheckboxGroup>,
402
399
  );
@@ -22,6 +22,7 @@ import {
22
22
  import { Label } from "../Label";
23
23
  import { ErrorMessage } from "../ErrorMessage";
24
24
  import { spaceDelimitedList } from "../../utils/spaceDelimitedList";
25
+ import { useValidationClasses } from "../../hooks";
25
26
 
26
27
  export const CheckboxGroup: ForwardedRefComponent<
27
28
  CheckboxGroupProps,
@@ -32,6 +33,7 @@ export const CheckboxGroup: ForwardedRefComponent<
32
33
  isDisabled = false,
33
34
  isRequired,
34
35
  validationState,
36
+ isInvalid,
35
37
  orientation = "vertical",
36
38
  onChange,
37
39
  className,
@@ -54,11 +56,16 @@ export const CheckboxGroup: ForwardedRefComponent<
54
56
  "--is-optional": typeof isRequired === "boolean" && !isRequired,
55
57
  },
56
58
  );
57
- const labelClasses = classNames({
58
- "--is-valid": validationState === "valid",
59
- "--is-invalid": validationState === "invalid",
60
- "--is-disabled": isDisabled,
59
+ const validationClasses = useValidationClasses({
60
+ validationState,
61
+ isInvalid,
61
62
  });
63
+ const labelClasses = classNames(
64
+ {
65
+ "--is-disabled": isDisabled,
66
+ },
67
+ validationClasses,
68
+ );
62
69
  const errorMessageId = useId();
63
70
  const shouldErrorMessageShow = errorMessage ? errorMessageId : undefined;
64
71
  const describedBy = spaceDelimitedList([
@@ -112,7 +119,7 @@ export const CheckboxGroup: ForwardedRefComponent<
112
119
  isDisabled,
113
120
  isRequired,
114
121
  isReadOnly,
115
- validationState,
122
+ isInvalid,
116
123
  defaultSelected: selected.includes(child.props.value),
117
124
  onChange: handleChange,
118
125
  "aria-describedby": describedBy,
@@ -1,11 +1,13 @@
1
1
  import { ChangeEvent, ReactNode, Ref, RefAttributes } from "react";
2
2
  import { DOMProps } from "../../types/dom";
3
+ import { Validation } from "../../types";
3
4
 
4
5
  export type CheckboxElementType = HTMLInputElement;
5
6
  export type CheckboxRef = Ref<CheckboxElementType>;
6
7
 
7
8
  export interface CheckboxProps
8
9
  extends DOMProps,
10
+ Validation,
9
11
  RefAttributes<CheckboxElementType> {
10
12
  className?: string;
11
13
  // The content to display as the label.
@@ -18,8 +20,6 @@ export interface CheckboxProps
18
20
  onChange?: (event: ChangeEvent<CheckboxElementType>) => void;
19
21
  // The default value (uncontrolled).
20
22
  defaultSelected?: boolean;
21
- // Whether the input should display its "valid" or "invalid" visual styling.
22
- validationState?: "valid" | "invalid";
23
23
  // Whether the input can be selected but not changed by the user.
24
24
  isReadOnly?: boolean;
25
25
  // Whether user input is required on the input before form submission.
@@ -47,6 +47,7 @@ export interface CheckboxProps
47
47
  export type CheckboxGroupElementType = HTMLDivElement;
48
48
  interface CheckboxGroupPropsInternal
49
49
  extends DOMProps,
50
+ Validation,
50
51
  RefAttributes<CheckboxGroupElementType> {
51
52
  children: ReactNode;
52
53
  className?: string;
@@ -63,8 +64,6 @@ interface CheckboxGroupPropsInternal
63
64
  // "aria-errormessage"?: string;
64
65
  // Identifies the element (or elements) that describes the object.
65
66
  "aria-describedby"?: string;
66
- // Whether the input should display its "valid" or "invalid" visual styling.
67
- validationState?: "valid" | "invalid";
68
67
  // Whether user input is required on the input before form submission.
69
68
  isRequired?: boolean;
70
69
  // Whether the input is disabled.
@@ -7,6 +7,7 @@ import { ModalProps } from "./types";
7
7
  import { mergeRefs } from "../../utils";
8
8
  import { ModalContext } from "./ModalContext";
9
9
  import { useDialog } from "../../hooks";
10
+ import { useDeprecationWarning } from "../../hooks/useDeprecationWarning";
10
11
 
11
12
  export type ModalElementType = HTMLDialogElement;
12
13
  export type ModalRef = Ref<ModalElementType>;
@@ -30,22 +31,14 @@ const Modal = forwardRef((props: ModalProps, ref: ModalRef) => {
30
31
  shouldFocusAfterRender,
31
32
  parentSelector,
32
33
  } = props;
33
- const hasWarnedAboutMissingLabels = useRef<boolean>(false);
34
- // Handle deprecated props
35
- if (!hasWarnedAboutMissingLabels.current) {
36
- if (
37
- size ||
38
- appElement ||
39
- preventCloseOnEsc ||
40
- shouldFocusAfterRender ||
41
- parentSelector
42
- ) {
43
- console.warn(
44
- `Deprecation warning: Mobius Modal no longer supports the following props: size, appElement, preventCloseOnEsc, shouldFocusAfterRender and parentSelector.`,
45
- );
46
- hasWarnedAboutMissingLabels.current = true;
47
- }
48
- }
34
+
35
+ useDeprecationWarning({
36
+ size,
37
+ appElement,
38
+ preventCloseOnEsc,
39
+ shouldFocusAfterRender,
40
+ parentSelector,
41
+ });
49
42
 
50
43
  const shouldTransition = supportsDialog();
51
44
  const dialogRef = useRef<HTMLDialogElement | null>(null);
@@ -9,15 +9,6 @@ const meta: Meta<typeof NumberField> = {
9
9
  title: "Forms/NumberField",
10
10
  component: NumberField,
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
12
  minValue: {
22
13
  control: {
23
14
  control: { type: "range" },
@@ -34,7 +25,7 @@ const meta: Meta<typeof NumberField> = {
34
25
  step: 1,
35
26
  },
36
27
  },
37
- ...excludeControls("value"),
28
+ ...excludeControls("value", "validationState"),
38
29
  },
39
30
  args: {
40
31
  minValue: 0,
@@ -80,7 +71,7 @@ export const Valid: StoryType = {
80
71
  render: (args: NumberFieldProps) => <NumberField {...args} />,
81
72
  args: {
82
73
  label: "Number field",
83
- validationState: "valid",
74
+ isInvalid: false,
84
75
  defaultValue: 0,
85
76
  errorMessage: "",
86
77
  step: 1,
@@ -92,7 +83,7 @@ export const Invalid: StoryType = {
92
83
  render: (args: NumberFieldProps) => <NumberField {...args} />,
93
84
  args: {
94
85
  label: "Number field",
95
- validationState: "invalid",
86
+ isInvalid: true,
96
87
  errorMessage: "Error message",
97
88
  defaultValue: 0,
98
89
  step: 1,
@@ -12,22 +12,23 @@ import {
12
12
  useState,
13
13
  } from "react";
14
14
  import { DOMProps } from "../../types/dom";
15
- import { UseLabelProps, useLabel } from "../../hooks";
15
+ import { UseLabelProps, useLabel, useValidationClasses } from "../../hooks";
16
16
  import { ForwardedRefComponent } from "../../types/components";
17
17
  import { spaceDelimitedList } from "../../utils/spaceDelimitedList";
18
18
  import { ErrorMessage } from "../ErrorMessage";
19
19
  import { Label } from "../Label";
20
+ import { Validation } from "../../types";
20
21
 
21
22
  export type NumberFieldElementType = HTMLInputElement;
22
23
 
23
24
  export interface NumberFieldProps
24
25
  extends DOMProps,
26
+ Validation,
25
27
  UseLabelProps,
26
28
  RefAttributes<NumberFieldElementType> {
27
29
  id?: string;
28
30
  /** Custom class name for setting specific CSS */
29
31
  className?: string;
30
- validationState?: "valid" | "invalid" | undefined;
31
32
  /** The smallest value allowed for the input. */
32
33
  minValue?: number;
33
34
  /** The largest value allowed for the input. */
@@ -69,6 +70,7 @@ export const NumberField: ForwardedRefComponent<
69
70
  className,
70
71
  errorMessage,
71
72
  validationState,
73
+ isInvalid,
72
74
  isReadOnly,
73
75
  minValue,
74
76
  maxValue,
@@ -94,19 +96,29 @@ export const NumberField: ForwardedRefComponent<
94
96
  "--is-optional": optional,
95
97
  });
96
98
 
97
- const inputClasses = classNames("mobius/NumberFieldInput", className, {
98
- "--is-required": required,
99
- "--is-optional": optional,
100
- "--is-valid": validationState === "valid",
101
- "--is-invalid": validationState === "invalid",
102
- "--is-disabled": isDisabled,
99
+ const validationClasses = useValidationClasses({
100
+ validationState,
101
+ isInvalid,
103
102
  });
104
103
 
105
- const labelClasses = classNames("mobius/NumberFieldLabel", {
106
- "--is-valid": validationState === "valid",
107
- "--is-invalid": validationState === "invalid",
108
- "--is-disabled": isDisabled,
109
- });
104
+ const inputClasses = classNames(
105
+ "mobius/NumberFieldInput",
106
+ className,
107
+ {
108
+ "--is-required": required,
109
+ "--is-optional": optional,
110
+ "--is-disabled": isDisabled,
111
+ },
112
+ validationClasses,
113
+ );
114
+
115
+ const labelClasses = classNames(
116
+ "mobius/NumberFieldLabel",
117
+ {
118
+ "--is-disabled": isDisabled,
119
+ },
120
+ validationClasses,
121
+ );
110
122
 
111
123
  const errorMessageId = useId();
112
124
  const shouldErrorMessageShow = errorMessage ? errorMessageId : undefined;
@@ -165,7 +177,7 @@ export const NumberField: ForwardedRefComponent<
165
177
  required={required}
166
178
  aria-describedby={describedBy}
167
179
  aria-errormessage={shouldErrorMessageShow}
168
- aria-invalid={validationState === "invalid"}
180
+ aria-invalid={isInvalid}
169
181
  readOnly={isReadOnly}
170
182
  disabled={isDisabled}
171
183
  autoComplete="off"
@@ -9,20 +9,11 @@ const meta: Meta<typeof PasswordField> = {
9
9
  title: "Forms/PasswordField",
10
10
  component: PasswordField,
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
12
  type: {
22
13
  control: { type: "radio" },
23
14
  options: ["text", "password"],
24
15
  },
25
- ...excludeControls("description"),
16
+ ...excludeControls("description", "validationState"),
26
17
  },
27
18
  args: {
28
19
  isReadOnly: false,
@@ -12,15 +12,6 @@ const meta: Meta<typeof RadioGroup> = {
12
12
  title: "Forms/Radio",
13
13
  component: RadioGroup,
14
14
  argTypes: {
15
- validationState: {
16
- options: ["valid", "invalid", "neither"],
17
- control: { type: "radio" },
18
- mapping: {
19
- valid: "valid",
20
- invalid: "invalid",
21
- neither: "",
22
- },
23
- },
24
15
  orientation: {
25
16
  control: { type: "radio" },
26
17
  options: ["horizontal", "vertical"],
@@ -35,6 +26,7 @@ const meta: Meta<typeof RadioGroup> = {
35
26
  "setSelected",
36
27
  "defaultValue",
37
28
  "value",
29
+ "validationState",
38
30
  ),
39
31
  },
40
32
  decorators: [
@@ -107,7 +99,7 @@ export const Valid: StoryType = {
107
99
  isDisabled: false,
108
100
  isRequired: false,
109
101
  errorMessage: "",
110
- validationState: "valid",
102
+ isInvalid: false,
111
103
  orientation: "vertical",
112
104
  },
113
105
  };
@@ -124,7 +116,7 @@ export const Invalid: StoryType = {
124
116
  isDisabled: false,
125
117
  isRequired: false,
126
118
  errorMessage: "Please select a color",
127
- validationState: "invalid",
119
+ isInvalid: true,
128
120
  orientation: "vertical",
129
121
  },
130
122
  };
@@ -375,7 +375,7 @@ describe("Radio", () => {
375
375
 
376
376
  it("sets classes for valid state", () => {
377
377
  const { container } = render(
378
- <RadioGroup label="Color" validationState="valid">
378
+ <RadioGroup label="Color" isInvalid={false}>
379
379
  <Radio value="red">Red</Radio>
380
380
  <Radio value="blue">Blue</Radio>
381
381
  </RadioGroup>,
@@ -386,7 +386,7 @@ describe("Radio", () => {
386
386
 
387
387
  it("sets classes for invalid state", () => {
388
388
  const { container } = render(
389
- <RadioGroup label="Color" validationState="invalid">
389
+ <RadioGroup label="Color" isInvalid>
390
390
  <Radio value="red">Red</Radio>
391
391
  <Radio value="blue">Blue</Radio>
392
392
  </RadioGroup>,
@@ -448,7 +448,7 @@ describe("Radio", () => {
448
448
  describe("given an invalid state", () => {
449
449
  it("renders aria-invalid attribute on container", () => {
450
450
  const { container } = render(
451
- <RadioGroup label="Color" validationState="invalid">
451
+ <RadioGroup label="Color" isInvalid>
452
452
  <Radio value="red">Red</Radio>
453
453
  <Radio value="blue">Blue</Radio>
454
454
  </RadioGroup>,
@@ -19,11 +19,14 @@ import { HTMLElementEvent } from "../../types/events";
19
19
  import { spaceDelimitedList } from "../../utils/spaceDelimitedList";
20
20
  import { ErrorMessage } from "../ErrorMessage";
21
21
  import { Label } from "../Label";
22
+ import { Validation } from "../../types";
23
+ import { useValidationClasses } from "../../hooks";
22
24
 
23
25
  export type RadioGroupElementType = HTMLDivElement;
24
26
 
25
27
  export interface RadioGroupProps
26
28
  extends DOMProps,
29
+ Validation,
27
30
  RefAttributes<RadioGroupElementType> {
28
31
  children: ReactNode;
29
32
  className?: string;
@@ -39,8 +42,6 @@ export interface RadioGroupProps
39
42
  "aria-errormessage"?: string;
40
43
  // Identifies the element (or elements) that describes the object.
41
44
  "aria-describedby"?: string;
42
- // Whether the input should display its "valid" or "invalid" visual styling.
43
- validationState?: "valid" | "invalid";
44
45
  // Whether user input is required on the input before form submission.
45
46
  isRequired?: boolean;
46
47
  // Whether the input is disabled.
@@ -82,6 +83,7 @@ const RadioGroup: ForwardedRefComponent<
82
83
  isDisabled = false,
83
84
  isRequired,
84
85
  validationState,
86
+ isInvalid,
85
87
  orientation = "vertical",
86
88
  className,
87
89
  errorMessage,
@@ -94,10 +96,12 @@ const RadioGroup: ForwardedRefComponent<
94
96
  } = props;
95
97
  const defaultSelected = getDefaultVal(children, defaultValue);
96
98
  const [selected, setSelected] = useState<string>(defaultSelected);
99
+ const validationClasses = useValidationClasses({
100
+ validationState,
101
+ isInvalid,
102
+ });
97
103
  const radioClasses = {
98
104
  "--is-disabled": isDisabled,
99
- "--is-valid": validationState === "valid",
100
- "--is-invalid": validationState === "invalid",
101
105
  "--is-required": typeof isRequired === "boolean" && isRequired,
102
106
  "--is-optional": typeof isRequired === "boolean" && !isRequired,
103
107
  [`--is-${orientation}`]: true,
@@ -107,11 +111,12 @@ const RadioGroup: ForwardedRefComponent<
107
111
  "mobius",
108
112
  "mobius/RadioGroup",
109
113
  radioClasses,
114
+ validationClasses,
110
115
  );
111
116
  const radioWrapperClasses = classNames("mobius/RadioWrapper", {
112
117
  [`--is-${orientation}`]: true,
113
118
  });
114
- const labelClasses = classNames(radioClasses);
119
+ const labelClasses = classNames(radioClasses, validationClasses);
115
120
  const errorMessageId = useId();
116
121
  const defaultNameAttrId = useId();
117
122
  const nameAttribute = name || defaultNameAttrId;
@@ -128,7 +133,7 @@ const RadioGroup: ForwardedRefComponent<
128
133
  aria-describedby={props["aria-describedby"]}
129
134
  aria-disabled={isDisabled}
130
135
  aria-errormessage={shouldErrorMessageShow}
131
- aria-invalid={validationState === "invalid"}
136
+ aria-invalid={isInvalid}
132
137
  aria-label={props["aria-label"]}
133
138
  aria-labelledby={props["aria-labelledby"] || labelId}
134
139
  aria-orientation={orientation}
@@ -106,7 +106,7 @@ import { Select, Option } from "@simplybusiness/mobius";
106
106
  name="color"
107
107
  label="Colors"
108
108
  defaultValue="green"
109
- validationState="valid"
109
+ isInvalid={false}
110
110
  >
111
111
  <Option key="none" value="" isDisabled>
112
112
  Please select a color
@@ -135,7 +135,7 @@ import { Select, Option } from "@simplybusiness/mobius";
135
135
  name="color"
136
136
  label="Colors"
137
137
  defaultValue="green"
138
- validationState="invalid"
138
+ isInvalid
139
139
  errorMessage="Unacceptable color"
140
140
  >
141
141
  <Option key="none" value="" isDisabled>
@@ -2,6 +2,7 @@ import type { Meta, StoryObj } from "@storybook/react";
2
2
  import { StoryContainer } from "../../utils/StoryContainer";
3
3
  import { Option } from "../Option";
4
4
  import { Select, SelectProps } from "./Select";
5
+ import { excludeControls } from "../../utils";
5
6
 
6
7
  type StoryType = StoryObj<typeof Select>;
7
8
 
@@ -9,19 +10,11 @@ const meta: Meta<typeof Select> = {
9
10
  title: "Forms/Select",
10
11
  component: Select,
11
12
  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
13
  value: {
22
14
  control: { type: "select" },
23
15
  options: ["", "red", "blue", "green"],
24
16
  },
17
+ ...excludeControls("validationState"),
25
18
  },
26
19
  decorators: [
27
20
  Story => (
@@ -135,7 +128,7 @@ export const Valid: StoryType = {
135
128
  name: "color",
136
129
  label: "Colors",
137
130
  defaultValue: "green",
138
- validationState: "valid",
131
+ isInvalid: false,
139
132
  errorMessage: "",
140
133
  isDisabled: false,
141
134
  isRequired: false,
@@ -163,7 +156,7 @@ export const Invalid: StoryType = {
163
156
  name: "color",
164
157
  label: "Colors",
165
158
  defaultValue: "green",
166
- validationState: "invalid",
159
+ isInvalid: true,
167
160
  errorMessage: "Unacceptable color",
168
161
  isDisabled: false,
169
162
  isRequired: false,
@@ -204,7 +204,7 @@ describe("Select", () => {
204
204
 
205
205
  it("includes valid state class name", () => {
206
206
  render(
207
- <Select label="Color" data-testid="test" validationState="valid">
207
+ <Select label="Color" data-testid="test" isInvalid={false}>
208
208
  <Option>Red</Option>
209
209
  <Option>Blue</Option>
210
210
  </Select>,