@onewelcome/react-lib-components 5.1.0 → 6.0.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 (152) hide show
  1. package/dist/cjs/Button/BaseButton.cjs.js.map +1 -1
  2. package/dist/cjs/Button/BaseButton.module.cjs.js +1 -1
  3. package/dist/cjs/Button/Button.module.cjs.js +1 -1
  4. package/dist/cjs/Button/IconButton.module.cjs.js +1 -1
  5. package/dist/cjs/DataGrid/DataGridActions/DataGridColumnsToggle.cjs.js +1 -1
  6. package/dist/cjs/DataGrid/DataGridActions/DataGridColumnsToggle.cjs.js.map +1 -1
  7. package/dist/cjs/Form/Radio/Radio.cjs.js.map +1 -1
  8. package/dist/cjs/Form/Select/Option.cjs.js +1 -1
  9. package/dist/cjs/Form/Select/Option.cjs.js.map +1 -1
  10. package/dist/cjs/Icon/Icon.cjs.js +1 -1
  11. package/dist/cjs/Icon/Icon.cjs.js.map +1 -1
  12. package/dist/cjs/Icon/Icon.module.cjs.js +1 -1
  13. package/dist/cjs/Link/Link.cjs.js.map +1 -1
  14. package/dist/cjs/Link/Link.module.cjs.js +1 -1
  15. package/dist/cjs/Notifications/BaseModal/BaseModal.cjs.js +1 -1
  16. package/dist/cjs/Notifications/BaseModal/BaseModal.cjs.js.map +1 -1
  17. package/dist/cjs/Notifications/BaseModal/useRepeatFocus.cjs.js +2 -0
  18. package/dist/cjs/Notifications/BaseModal/useRepeatFocus.cjs.js.map +1 -0
  19. package/dist/cjs/Notifications/Snackbar/SnackbarItem/SnackbarItem.cjs.js +1 -1
  20. package/dist/cjs/Notifications/Snackbar/SnackbarItem/SnackbarItem.cjs.js.map +1 -1
  21. package/dist/cjs/Notifications/Snackbar/SnackbarItem/SnackbarItem.module.cjs.js +1 -1
  22. package/dist/cjs/Notifications/Snackbar/SnackbarProvider/SnackbarProvider.cjs.js +1 -1
  23. package/dist/cjs/Notifications/Snackbar/SnackbarProvider/SnackbarProvider.cjs.js.map +1 -1
  24. package/dist/cjs/Notifications/Snackbar/SnackbarProvider/SnackbarStateProvider.cjs.js.map +1 -1
  25. package/dist/cjs/Notifications/Snackbar/interfaces.cjs.js +2 -0
  26. package/dist/cjs/Notifications/Snackbar/interfaces.cjs.js.map +1 -0
  27. package/dist/cjs/Stepper/Step.cjs.js +1 -1
  28. package/dist/cjs/Stepper/Step.cjs.js.map +1 -1
  29. package/dist/cjs/Stepper/Step.module.cjs.js +1 -1
  30. package/dist/cjs/Stepper/Stepper.cjs.js +1 -1
  31. package/dist/cjs/Stepper/Stepper.cjs.js.map +1 -1
  32. package/dist/cjs/Stepper/Stepper.module.cjs.js +1 -1
  33. package/dist/cjs/Tooltip/Tooltip.cjs.js +1 -1
  34. package/dist/cjs/Tooltip/Tooltip.cjs.js.map +1 -1
  35. package/dist/cjs/Wizard/Wizard.cjs.js.map +1 -1
  36. package/dist/cjs/Wizard/WizardActions/WizardActions.cjs.js.map +1 -1
  37. package/dist/cjs/Wizard/WizardStateProvider.cjs.js.map +1 -1
  38. package/dist/cjs/Wizard/WizardSteps/WizardSteps.cjs.js.map +1 -1
  39. package/dist/cjs/_BaseStyling_/BaseStyling.cjs.js +1 -1
  40. package/dist/cjs/_BaseStyling_/BaseStyling.cjs.js.map +1 -1
  41. package/dist/cjs/src/components/Button/BaseButton.d.ts +1 -1
  42. package/dist/cjs/src/components/Icon/Icon.d.ts +4 -1
  43. package/dist/cjs/src/components/Link/Link.d.ts +1 -1
  44. package/dist/cjs/src/components/Notifications/BaseModal/useRepeatFocus.d.ts +7 -0
  45. package/dist/cjs/src/components/Notifications/Snackbar/SnackbarProvider/SnackbarStateProvider.d.ts +5 -5
  46. package/dist/cjs/src/components/Notifications/Snackbar/interfaces.d.ts +6 -0
  47. package/dist/cjs/src/components/Notifications/Snackbar/useSnackbar.d.ts +4 -4
  48. package/dist/cjs/src/components/Notifications/Snackbar/useSnackbar.test.d.ts +1 -0
  49. package/dist/cjs/src/components/Stepper/Step.d.ts +5 -3
  50. package/dist/cjs/src/components/Stepper/Stepper.d.ts +3 -1
  51. package/dist/cjs/src/components/Wizard/WizardActions/WizardActions.d.ts +3 -0
  52. package/dist/cjs/src/components/Wizard/WizardStateProvider.d.ts +3 -0
  53. package/dist/cjs/src/components/Wizard/WizardSteps/WizardSteps.d.ts +3 -0
  54. package/dist/cjs/src/components/_BaseStyling_/BaseStyling.d.ts +11 -10
  55. package/dist/cjs/src/index.d.ts +1 -0
  56. package/dist/esm/Button/BaseButton.esm.js.map +1 -1
  57. package/dist/esm/Button/BaseButton.module.esm.js +1 -1
  58. package/dist/esm/Button/Button.module.esm.js +1 -1
  59. package/dist/esm/Button/IconButton.module.esm.js +1 -1
  60. package/dist/esm/DataGrid/DataGridActions/DataGridColumnsToggle.esm.js +1 -1
  61. package/dist/esm/DataGrid/DataGridActions/DataGridColumnsToggle.esm.js.map +1 -1
  62. package/dist/esm/Form/Radio/Radio.esm.js.map +1 -1
  63. package/dist/esm/Form/Select/Option.esm.js +1 -1
  64. package/dist/esm/Form/Select/Option.esm.js.map +1 -1
  65. package/dist/esm/Icon/Icon.esm.js +1 -1
  66. package/dist/esm/Icon/Icon.esm.js.map +1 -1
  67. package/dist/esm/Icon/Icon.module.esm.js +1 -1
  68. package/dist/esm/Link/Link.esm.js.map +1 -1
  69. package/dist/esm/Link/Link.module.esm.js +1 -1
  70. package/dist/esm/Notifications/BaseModal/BaseModal.esm.js +1 -1
  71. package/dist/esm/Notifications/BaseModal/BaseModal.esm.js.map +1 -1
  72. package/dist/esm/Notifications/BaseModal/useRepeatFocus.esm.js +2 -0
  73. package/dist/esm/Notifications/BaseModal/useRepeatFocus.esm.js.map +1 -0
  74. package/dist/esm/Notifications/Snackbar/SnackbarItem/SnackbarItem.esm.js +1 -1
  75. package/dist/esm/Notifications/Snackbar/SnackbarItem/SnackbarItem.esm.js.map +1 -1
  76. package/dist/esm/Notifications/Snackbar/SnackbarItem/SnackbarItem.module.esm.js +1 -1
  77. package/dist/esm/Notifications/Snackbar/SnackbarProvider/SnackbarProvider.esm.js +1 -1
  78. package/dist/esm/Notifications/Snackbar/SnackbarProvider/SnackbarProvider.esm.js.map +1 -1
  79. package/dist/esm/Notifications/Snackbar/SnackbarProvider/SnackbarStateProvider.esm.js.map +1 -1
  80. package/dist/esm/Notifications/Snackbar/interfaces.esm.js +2 -0
  81. package/dist/esm/Notifications/Snackbar/interfaces.esm.js.map +1 -0
  82. package/dist/esm/Stepper/Step.esm.js +1 -1
  83. package/dist/esm/Stepper/Step.esm.js.map +1 -1
  84. package/dist/esm/Stepper/Step.module.esm.js +1 -1
  85. package/dist/esm/Stepper/Stepper.esm.js +1 -1
  86. package/dist/esm/Stepper/Stepper.esm.js.map +1 -1
  87. package/dist/esm/Stepper/Stepper.module.esm.js +1 -1
  88. package/dist/esm/Tooltip/Tooltip.esm.js +1 -1
  89. package/dist/esm/Tooltip/Tooltip.esm.js.map +1 -1
  90. package/dist/esm/Wizard/Wizard.esm.js.map +1 -1
  91. package/dist/esm/Wizard/WizardActions/WizardActions.esm.js.map +1 -1
  92. package/dist/esm/Wizard/WizardStateProvider.esm.js.map +1 -1
  93. package/dist/esm/Wizard/WizardSteps/WizardSteps.esm.js.map +1 -1
  94. package/dist/esm/_BaseStyling_/BaseStyling.esm.js +1 -1
  95. package/dist/esm/_BaseStyling_/BaseStyling.esm.js.map +1 -1
  96. package/dist/esm/src/components/Button/BaseButton.d.ts +1 -1
  97. package/dist/esm/src/components/Icon/Icon.d.ts +4 -1
  98. package/dist/esm/src/components/Link/Link.d.ts +1 -1
  99. package/dist/esm/src/components/Notifications/BaseModal/useRepeatFocus.d.ts +7 -0
  100. package/dist/esm/src/components/Notifications/Snackbar/SnackbarProvider/SnackbarStateProvider.d.ts +5 -5
  101. package/dist/esm/src/components/Notifications/Snackbar/interfaces.d.ts +6 -0
  102. package/dist/esm/src/components/Notifications/Snackbar/useSnackbar.d.ts +4 -4
  103. package/dist/esm/src/components/Notifications/Snackbar/useSnackbar.test.d.ts +1 -0
  104. package/dist/esm/src/components/Stepper/Step.d.ts +5 -3
  105. package/dist/esm/src/components/Stepper/Stepper.d.ts +3 -1
  106. package/dist/esm/src/components/Wizard/WizardActions/WizardActions.d.ts +3 -0
  107. package/dist/esm/src/components/Wizard/WizardStateProvider.d.ts +3 -0
  108. package/dist/esm/src/components/Wizard/WizardSteps/WizardSteps.d.ts +3 -0
  109. package/dist/esm/src/components/_BaseStyling_/BaseStyling.d.ts +11 -10
  110. package/dist/esm/src/index.d.ts +1 -0
  111. package/package.json +1 -1
  112. package/src/components/Button/BaseButton.tsx +1 -1
  113. package/src/components/Button/Button.module.scss +2 -10
  114. package/src/components/Button/Button.test.tsx +15 -3
  115. package/src/components/Button/IconButton.test.tsx +0 -16
  116. package/src/components/DataGrid/DataGridActions/DataGridColumnsToggle.tsx +5 -1
  117. package/src/components/Form/Radio/Radio.tsx +3 -1
  118. package/src/components/Form/Select/Option.tsx +1 -1
  119. package/src/components/Icon/Icon.module.scss +12 -0
  120. package/src/components/Icon/Icon.tsx +4 -1
  121. package/src/components/Link/Link.module.scss +12 -4
  122. package/src/components/Link/Link.test.tsx +11 -5
  123. package/src/components/Link/Link.tsx +1 -1
  124. package/src/components/Notifications/BaseModal/BaseModal.test.tsx +36 -1
  125. package/src/components/Notifications/BaseModal/BaseModal.tsx +10 -3
  126. package/src/components/Notifications/BaseModal/useRepeatFocus.tsx +73 -0
  127. package/src/components/Notifications/Snackbar/SnackbarItem/SnackbarItem.module.scss +14 -21
  128. package/src/components/Notifications/Snackbar/SnackbarItem/SnackbarItem.test.tsx +20 -6
  129. package/src/components/Notifications/Snackbar/SnackbarItem/SnackbarItem.tsx +17 -13
  130. package/src/components/Notifications/Snackbar/SnackbarProvider/SnackbarProvider.test.tsx +36 -32
  131. package/src/components/Notifications/Snackbar/SnackbarProvider/SnackbarProvider.tsx +43 -17
  132. package/src/components/Notifications/Snackbar/SnackbarProvider/SnackbarStateProvider.tsx +5 -13
  133. package/src/components/Notifications/Snackbar/interfaces.ts +15 -0
  134. package/src/components/Notifications/Snackbar/useSnackbar.test.tsx +136 -0
  135. package/src/components/Stepper/Step.module.scss +129 -59
  136. package/src/components/Stepper/Step.tsx +57 -54
  137. package/src/components/Stepper/Stepper.module.scss +12 -8
  138. package/src/components/Stepper/Stepper.test.tsx +3 -3
  139. package/src/components/Stepper/Stepper.tsx +17 -7
  140. package/src/components/Tooltip/Tooltip.tsx +2 -2
  141. package/src/components/Wizard/Wizard.tsx +3 -0
  142. package/src/components/Wizard/WizardActions/WizardActions.tsx +3 -0
  143. package/src/components/Wizard/WizardStateProvider.tsx +3 -0
  144. package/src/components/Wizard/WizardSteps/WizardSteps.tsx +3 -0
  145. package/src/components/_BaseStyling_/BaseStyling.tsx +24 -22
  146. package/src/font/icomoon.eot +0 -0
  147. package/src/font/icomoon.svg +5 -2
  148. package/src/font/icomoon.ttf +0 -0
  149. package/src/font/icomoon.woff +0 -0
  150. package/src/font/selection.json +1 -1
  151. package/src/index.ts +1 -0
  152. package/src/mixins.module.scss +59 -57
@@ -19,12 +19,15 @@ import React, {
19
19
  ComponentPropsWithRef,
20
20
  useEffect,
21
21
  useRef,
22
- ReactElement
22
+ ReactElement,
23
+ RefObject,
24
+ createRef
23
25
  } from "react";
24
26
  import { createPortal } from "react-dom";
25
27
  import { useGetDomRoot } from "../../../hooks/useGetDomRoot";
26
28
  import classes from "./BaseModal.module.scss";
27
29
  import { labelId, descriptionId } from "./BaseModalContext";
30
+ import { useRepeatFocus } from "./useRepeatFocus";
28
31
 
29
32
  const SCROLL_PROPERTY_NAME = "overflow";
30
33
  const SCROLL_PROPERTY_VALUE = "hidden";
@@ -90,6 +93,7 @@ const BaseModalComponent: ForwardRefRenderFunction<HTMLDivElement, Props> = (
90
93
  ) => {
91
94
  useSetBodyScroll(open);
92
95
  const wrappingDivRef = useRef<HTMLDivElement>(null);
96
+ const modalRef = (ref as RefObject<HTMLDivElement>) || createRef<HTMLDivElement>();
93
97
  const { root } = useGetDomRoot(domRoot, wrappingDivRef);
94
98
 
95
99
  const handleEscKeyPress = (event: React.KeyboardEvent<HTMLDivElement>) => {
@@ -99,9 +103,11 @@ const BaseModalComponent: ForwardRefRenderFunction<HTMLDivElement, Props> = (
99
103
  }
100
104
  };
101
105
 
106
+ useRepeatFocus(modalRef);
107
+
102
108
  useEffect(() => {
103
109
  if (open) {
104
- wrappingDivRef.current?.focus();
110
+ modalRef.current?.focus();
105
111
  }
106
112
  }, [open]);
107
113
 
@@ -122,7 +128,7 @@ const BaseModalComponent: ForwardRefRenderFunction<HTMLDivElement, Props> = (
122
128
  {createPortal(
123
129
  <div
124
130
  {...rest}
125
- ref={ref}
131
+ ref={modalRef}
126
132
  id={id}
127
133
  className={`${classes["modal"]} ${open ? classes["visible"] : ""} ${className}`}
128
134
  role="dialog"
@@ -137,6 +143,7 @@ const BaseModalComponent: ForwardRefRenderFunction<HTMLDivElement, Props> = (
137
143
  >
138
144
  <div
139
145
  {...backdropProps}
146
+ aria-hidden={true}
140
147
  className={`${classes["backdrop"]} ${backdropProps?.className ?? ""}`}
141
148
  onClick={handleBackdropClick}
142
149
  ></div>
@@ -0,0 +1,73 @@
1
+ /*
2
+ * Copyright 2022 OneWelcome B.V.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import { RefObject, useEffect } from "react";
18
+
19
+ /**
20
+ * @description This is a hook that will make sure that when a modal is open and the user tabs through the it,
21
+ * the focus will be repeated and the user will not lose their entire focusable element to an element in the background
22
+ * that is being blocked by the modal.
23
+ */
24
+
25
+ export const useRepeatFocus = (ref: RefObject<HTMLDivElement>) => {
26
+ const getFocusableElement = (
27
+ element: HTMLElement,
28
+ position: "first" | "last"
29
+ ): HTMLElement | null => {
30
+ const focusableSelectors =
31
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
32
+ const focusableElements = element.querySelectorAll<HTMLElement>(focusableSelectors);
33
+
34
+ if (position === "first") {
35
+ return focusableElements[0] || null;
36
+ } else if (position === "last") {
37
+ return focusableElements[focusableElements.length - 1] || null;
38
+ }
39
+
40
+ return null;
41
+ };
42
+
43
+ useEffect(() => {
44
+ if (!ref.current || !open) return;
45
+
46
+ const lastFocusableElement = getFocusableElement(ref.current, "last");
47
+ const firstFocusableElement = getFocusableElement(ref.current, "first");
48
+
49
+ if (!lastFocusableElement || !firstFocusableElement) return;
50
+
51
+ const handleTabKeyPress = (event: KeyboardEvent) => {
52
+ if (event.key !== "Tab") return;
53
+
54
+ if (event.shiftKey) {
55
+ if (document.activeElement === firstFocusableElement) {
56
+ event.preventDefault();
57
+ lastFocusableElement?.focus();
58
+ }
59
+ } else if (document.activeElement === lastFocusableElement) {
60
+ event.preventDefault();
61
+ firstFocusableElement?.focus();
62
+ }
63
+ };
64
+
65
+ lastFocusableElement.addEventListener("keydown", handleTabKeyPress);
66
+ firstFocusableElement.addEventListener("keydown", handleTabKeyPress);
67
+
68
+ return () => {
69
+ lastFocusableElement.removeEventListener("keydown", handleTabKeyPress);
70
+ firstFocusableElement.removeEventListener("keydown", handleTabKeyPress);
71
+ };
72
+ }, [ref, open]);
73
+ };
@@ -17,7 +17,7 @@
17
17
  @use "../../../../mixins.module.scss";
18
18
 
19
19
  .snackbar {
20
- padding: 0.5rem 1rem;
20
+ padding: 1rem;
21
21
  border-radius: var(--snackbar-border-radius);
22
22
  display: flex;
23
23
  gap: 0.5rem;
@@ -27,11 +27,6 @@
27
27
  @include mixins.transition(height, 0.2s, ease-in-out);
28
28
  flex-grow: 0;
29
29
 
30
- &.has-title {
31
- align-items: flex-start;
32
- padding: 1rem;
33
- }
34
-
35
30
  &.info {
36
31
  background-color: var(--snackbar-info-background-color);
37
32
  }
@@ -57,6 +52,7 @@
57
52
  align-self: center;
58
53
  width: 2.5rem;
59
54
  height: 2.5rem;
55
+ margin: -0.5rem 0;
60
56
  flex-shrink: 0;
61
57
  border: 0;
62
58
 
@@ -77,39 +73,36 @@
77
73
  .icon {
78
74
  color: var(--snackbar-text-color);
79
75
  font-size: 1rem;
80
- line-height: var(--default-line-height);
81
- display: flex;
76
+ height: 1.25rem;
77
+ display: inline-flex;
78
+ align-items: center;
79
+ align-self: flex-start;
82
80
  }
83
81
 
84
82
  .title {
85
83
  color: var(--snackbar-text-color);
86
84
  flex: 1;
87
85
  font-size: 0.875rem;
88
- line-height: var(--default-line-height);
86
+ line-height: 1.25rem;
89
87
  margin-bottom: 0.25rem;
90
- margin-top: -2.5px;
91
88
  display: block;
92
89
  }
93
90
 
94
- .content-wrapper {
91
+ .outer-content-wrapper {
92
+ display: flex;
93
+ gap: 0.5rem;
95
94
  flex-grow: 1;
96
95
  }
97
96
 
98
- &.has-title .content-wrapper {
99
- width: 12.5rem;
100
- }
101
-
102
- @media screen and (min-width: 37.5em) {
103
- &:not(.has-title) .content {
104
- white-space: nowrap;
105
- }
97
+ .content-wrapper {
98
+ flex-grow: 1;
106
99
  }
107
100
 
108
101
  .content {
109
102
  color: var(--snackbar-text-color);
110
103
  font-size: 0.875rem;
104
+ line-height: 1.25rem;
111
105
  margin-bottom: 0;
112
- line-height: var(--default-line-height);
113
106
  }
114
107
 
115
108
  .actions {
@@ -125,7 +118,7 @@
125
118
  font-size: 0.875rem;
126
119
  line-height: var(--button-font-size);
127
120
  font-weight: 400;
128
- margin: 0;
121
+ margin: -0.5rem 0;
129
122
  padding: 0.625rem 1.25rem;
130
123
  cursor: pointer;
131
124
  transition:
@@ -22,25 +22,27 @@ import userEvent from "@testing-library/user-event";
22
22
  const initParams: Props = {
23
23
  id: "id",
24
24
  title: "title",
25
+ content: "content",
25
26
  duration: 1,
26
27
  variant: "success",
27
28
  closeButtonTitle: "close",
28
29
  onClose: jest.fn()
29
30
  };
30
31
 
31
- describe("SnackbarItem", () => {
32
- it("renders without crashing", () => {
32
+ describe("<SnackbarItem />", () => {
33
+ it("should render without crashing", () => {
33
34
  const { container } = render(<SnackbarItem {...initParams} />);
34
35
 
35
- expect(container).toHaveTextContent(initParams.title!);
36
+ const titleDiv = container.querySelector(".title");
37
+ expect(titleDiv).toHaveTextContent(initParams.title!);
36
38
  const contentDiv = container.querySelector(".content");
37
- expect(contentDiv).toBeNull();
39
+ expect(contentDiv).toHaveTextContent(initParams.content!);
38
40
  const actionsDiv = container.querySelector(".actions");
39
41
  expect(actionsDiv).toBeNull();
40
42
  expect(getByRole(container, "button")).toBeDefined();
41
43
  });
42
44
 
43
- it("clicking close button call callback function", async () => {
45
+ it("should clicking close button call callback function", async () => {
44
46
  const { container } = render(<SnackbarItem {...initParams} duration={10000000} />);
45
47
 
46
48
  expect(initParams.onClose).not.toBeCalled();
@@ -51,7 +53,7 @@ describe("SnackbarItem", () => {
51
53
  });
52
54
  });
53
55
 
54
- it("call close callback after provided duration", () => {
56
+ it("should call close callback after provided duration", () => {
55
57
  render(<SnackbarItem {...initParams} />);
56
58
 
57
59
  expect(initParams.onClose).not.toBeCalled();
@@ -60,4 +62,16 @@ describe("SnackbarItem", () => {
60
62
  expect(initParams.onClose).toHaveBeenCalledWith(initParams.id);
61
63
  });
62
64
  });
65
+
66
+ it("should render only content when only title is provided", () => {
67
+ const { container } = render(<SnackbarItem {...initParams} content={undefined} />);
68
+
69
+ const titleDiv = container.querySelector(".title");
70
+ expect(titleDiv).toBeNull();
71
+ const contentDiv = container.querySelector(".content");
72
+ expect(contentDiv).toHaveTextContent(initParams.title!);
73
+ const actionsDiv = container.querySelector(".actions");
74
+ expect(actionsDiv).toBeNull();
75
+ expect(getByRole(container, "button")).toBeDefined();
76
+ });
63
77
  });
@@ -65,6 +65,9 @@ export const SnackbarItem = ({
65
65
  const timerHandler = useRef<ReturnType<typeof setTimeout>>();
66
66
  const onAnimationEnd = () => onClose(id);
67
67
  const { ref, animationStarted, startAnimation } = useAnimation<HTMLDivElement>(onAnimationEnd);
68
+ const hasOnlyTitle = !content && !!title;
69
+ const renderTitle = title && !hasOnlyTitle;
70
+ const renderContentOrTitleOnly = content || hasOnlyTitle;
68
71
 
69
72
  useRegisterSnackbarHeight(ref, id);
70
73
 
@@ -113,7 +116,6 @@ export const SnackbarItem = ({
113
116
  classes["snackbar"],
114
117
  classes[variant],
115
118
  animationStarted ? readyclasses["slide-out"] : readyclasses["slide-in"],
116
- title ? classes["has-title"] : "",
117
119
  className ?? ""
118
120
  ].join(" ");
119
121
 
@@ -125,18 +127,20 @@ export const SnackbarItem = ({
125
127
  onMouseEnter={onMouseEnter}
126
128
  onMouseLeave={onMouseLeave}
127
129
  >
128
- <Icon icon={getVariantIcon()} className={classes["icon"]} />
129
- <div className={classes["content-wrapper"]}>
130
- {title && (
131
- <Typography className={classes["title"]} variant="body-bold" tag="span">
132
- {title}
133
- </Typography>
134
- )}
135
- {content && (
136
- <Typography className={classes["content"]} variant="body">
137
- {content}
138
- </Typography>
139
- )}
130
+ <div className={classes["outer-content-wrapper"]}>
131
+ <Icon icon={getVariantIcon()} className={classes["icon"]} />
132
+ <div className={classes["content-wrapper"]}>
133
+ {renderTitle && (
134
+ <Typography className={classes["title"]} variant="body-bold" tag="span">
135
+ {title}
136
+ </Typography>
137
+ )}
138
+ {renderContentOrTitleOnly && (
139
+ <Typography className={classes["content"]} variant="body">
140
+ {hasOnlyTitle ? title : content}
141
+ </Typography>
142
+ )}
143
+ </div>
140
144
  </div>
141
145
  {actionButtons.length > 0 && <div className={classes["actions"]}>{actionButtons}</div>}
142
146
  <IconButton
@@ -78,7 +78,7 @@ const renderSnackbarProvider = (props?: Partial<Props>) => {
78
78
  <button
79
79
  data-testid="show-success"
80
80
  onClick={() => {
81
- enqueueSuccessSnackbar(successProps.title + index, undefined, successProps.options);
81
+ enqueueSuccessSnackbar({ title: successProps.title + index, ...successProps.options });
82
82
  setIndex(index + 1);
83
83
  }}
84
84
  >
@@ -87,7 +87,7 @@ const renderSnackbarProvider = (props?: Partial<Props>) => {
87
87
  <button
88
88
  data-testid="show-error"
89
89
  onClick={() => {
90
- enqueueErrorSnackbar(errorProps.title + index);
90
+ enqueueErrorSnackbar({ title: errorProps.title + index });
91
91
  setIndex(index + 1);
92
92
  }}
93
93
  >
@@ -96,7 +96,11 @@ const renderSnackbarProvider = (props?: Partial<Props>) => {
96
96
  <button
97
97
  data-testid="show-info"
98
98
  onClick={() => {
99
- enqueueSnackbar(infoProps.title + index, infoProps.content, infoProps.options);
99
+ enqueueSnackbar({
100
+ title: infoProps.title + index,
101
+ content: infoProps.content,
102
+ ...infoProps.options
103
+ });
100
104
  setIndex(index + 1);
101
105
  }}
102
106
  >
@@ -105,7 +109,7 @@ const renderSnackbarProvider = (props?: Partial<Props>) => {
105
109
  <button
106
110
  data-testid="show-warning"
107
111
  onClick={() => {
108
- enqueueWarningSnackbar(warningProps.title + index, undefined, warningProps.options);
112
+ enqueueWarningSnackbar({ title: warningProps.title + index, ...warningProps.options });
109
113
  setIndex(index + 1);
110
114
  }}
111
115
  >
@@ -135,8 +139,8 @@ const renderSnackbarProvider = (props?: Partial<Props>) => {
135
139
  };
136
140
  };
137
141
 
138
- describe("SnackbarProvider", () => {
139
- it("renders without crashing", () => {
142
+ describe("<SnackbarProvider />", () => {
143
+ it("should render without crashing", () => {
140
144
  const { container } = renderSnackbarProvider();
141
145
 
142
146
  expect(container).toHaveTextContent("content");
@@ -176,40 +180,40 @@ describe("SnackbarProvider", () => {
176
180
 
177
181
  expect(infoProps.options.actions[0].onClick).toBeCalledTimes(1);
178
182
  });
179
- });
180
183
 
181
- describe("handlers", () => {
182
- it("should fire onClose", async () => {
183
- const onCloseHandler = jest.fn();
184
- const ExampleComponent = () => {
185
- const { enqueueErrorSnackbar, enqueueSuccessSnackbar } = useSnackbar();
184
+ describe("handlers", () => {
185
+ it("should fire onClose", async () => {
186
+ const onCloseHandler = jest.fn();
187
+ const ExampleComponent = () => {
188
+ const { enqueueErrorSnackbar, enqueueSuccessSnackbar } = useSnackbar();
186
189
 
187
- useEffect(() => {
188
- enqueueErrorSnackbar("error", undefined, { onClose: onCloseHandler, duration: 1 });
189
- enqueueSuccessSnackbar("success", undefined, { onClose: onCloseHandler, duration: 1 });
190
- }, []);
190
+ useEffect(() => {
191
+ enqueueErrorSnackbar("error", undefined, { onClose: onCloseHandler, duration: 1 });
192
+ enqueueSuccessSnackbar("success", undefined, { onClose: onCloseHandler, duration: 1 });
193
+ }, []);
191
194
 
192
- return <div></div>;
193
- };
195
+ return <div></div>;
196
+ };
194
197
 
195
- const queries = render(
196
- <SnackbarProvider closeButtonTitle="close">
197
- <ExampleComponent />
198
- </SnackbarProvider>
199
- );
198
+ const queries = render(
199
+ <SnackbarProvider closeButtonTitle="close">
200
+ <ExampleComponent />
201
+ </SnackbarProvider>
202
+ );
200
203
 
201
- const errorSnackbar = await queries.findByText(/error/i);
202
- const successSnackbar = await queries.findByText(/success/i);
204
+ const errorSnackbar = await queries.findByText(/error/i);
205
+ const successSnackbar = await queries.findByText(/success/i);
203
206
 
204
- expect(errorSnackbar).toBeTruthy();
205
- expect(successSnackbar).toBeTruthy();
207
+ expect(errorSnackbar).toBeTruthy();
208
+ expect(successSnackbar).toBeTruthy();
206
209
 
207
- const parentErrorSnackbar = errorSnackbar.closest(".snackbar")!;
208
- const parentSuccessSnackbar = successSnackbar.closest(".snackbar")!;
210
+ const parentErrorSnackbar = errorSnackbar.closest(".snackbar")!;
211
+ const parentSuccessSnackbar = successSnackbar.closest(".snackbar")!;
209
212
 
210
- fireEvent.animationEnd(parentErrorSnackbar);
211
- fireEvent.animationEnd(parentSuccessSnackbar);
213
+ fireEvent.animationEnd(parentErrorSnackbar);
214
+ fireEvent.animationEnd(parentSuccessSnackbar);
212
215
 
213
- await waitFor(() => expect(onCloseHandler).toHaveBeenCalledTimes(2));
216
+ await waitFor(() => expect(onCloseHandler).toHaveBeenCalledTimes(2));
217
+ });
214
218
  });
215
219
  });
@@ -17,7 +17,13 @@
17
17
  import React, { ReactNode, useRef, useState } from "react";
18
18
  import { createPortal } from "react-dom";
19
19
  import { SnackbarContextProvider } from "./SnackbarStateProvider";
20
- import { Actions, SnackbarOptionsProps, Variant } from "../interfaces";
20
+ import {
21
+ Actions,
22
+ EnqueueSnackbarProps,
23
+ SnackbarOptionsProps,
24
+ Variant,
25
+ isNewEnqueueSnackbarInterface
26
+ } from "../interfaces";
21
27
  import { Placement, SnackbarContainer } from "../SnackbarContainer/SnackbarContainer";
22
28
  import { generateID } from "../../../../util/helper";
23
29
  import { SnackbarItem } from "../SnackbarItem/SnackbarItem";
@@ -92,52 +98,72 @@ export const SnackbarProvider = (
92
98
  };
93
99
 
94
100
  const enqueueSnackbar = (
95
- title?: string,
101
+ propsOrTitle: EnqueueSnackbarProps | string | undefined,
96
102
  content?: string,
97
103
  options: SnackbarOptionsProps = {}
98
104
  ): void => {
105
+ const newInterface = isNewEnqueueSnackbarInterface(propsOrTitle);
106
+ const props = newInterface ? propsOrTitle : mapToNewInterface(propsOrTitle, content, options);
99
107
  const {
100
108
  variant = "info",
101
109
  actions,
102
- duration = getDuration(variant, actions, content),
110
+ duration = getDuration(variant, actions, props.content),
103
111
  onClose
104
- } = options;
112
+ } = props;
105
113
  const item: Item = {
106
- title,
107
- content,
114
+ title: props.title,
115
+ content: props.content,
108
116
  variant,
109
- className: options.className,
117
+ className: props.className,
110
118
  actions,
111
119
  duration,
112
120
  height: 0,
113
- id: generateID(15, title),
121
+ id: generateID(15, props.title),
114
122
  onClose
115
123
  };
116
124
  addSnackbar(item);
117
125
  };
118
126
 
119
- const enqueueSuccessSnackbar = (
127
+ const mapToNewInterface = (
120
128
  title?: string,
121
129
  content?: string,
122
- options?: SnackbarOptionsProps
130
+ options: SnackbarOptionsProps = {}
131
+ ): EnqueueSnackbarProps => {
132
+ return {
133
+ title,
134
+ content,
135
+ ...options
136
+ };
137
+ };
138
+
139
+ const enqueueSuccessSnackbar = (
140
+ propsOrTitle: EnqueueSnackbarProps | string | undefined,
141
+ content?: string,
142
+ options: SnackbarOptionsProps = {}
123
143
  ): void => {
124
- enqueueSnackbar(title, content, { ...options, variant: "success" });
144
+ const newInterface = isNewEnqueueSnackbarInterface(propsOrTitle);
145
+ const props = newInterface ? propsOrTitle : mapToNewInterface(propsOrTitle, content, options);
146
+ enqueueSnackbar({ ...props, variant: "success" });
125
147
  };
126
148
 
127
149
  const enqueueErrorSnackbar = (
128
- title?: string,
150
+ propsOrTitle: EnqueueSnackbarProps | string | undefined,
129
151
  content?: string,
130
- options?: SnackbarOptionsProps
152
+ options: SnackbarOptionsProps = {}
131
153
  ): void => {
132
- enqueueSnackbar(title, content, { ...options, variant: "error" });
154
+ const newInterface = isNewEnqueueSnackbarInterface(propsOrTitle);
155
+ const props = newInterface ? propsOrTitle : mapToNewInterface(propsOrTitle, content, options);
156
+ enqueueSnackbar({ ...props, variant: "error" });
133
157
  };
134
158
 
135
159
  const enqueueWarningSnackbar = (
136
- title?: string,
160
+ propsOrTitle: EnqueueSnackbarProps | string | undefined,
137
161
  content?: string,
138
- options?: SnackbarOptionsProps
162
+ options: SnackbarOptionsProps = {}
139
163
  ): void => {
140
- enqueueSnackbar(title, content, { ...options, variant: "warning" });
164
+ const newInterface = isNewEnqueueSnackbarInterface(propsOrTitle);
165
+ const props = newInterface ? propsOrTitle : mapToNewInterface(propsOrTitle, content, options);
166
+ enqueueSnackbar({ ...props, variant: "warning" });
141
167
  };
142
168
 
143
169
  const onItemClosed = (id: string) => {
@@ -15,22 +15,14 @@
15
15
  */
16
16
 
17
17
  import React, { createContext } from "react";
18
- import { SnackbarOptionsProps } from "../interfaces";
18
+ import { DeprecatedEnqueueSnackbarType } from "../interfaces";
19
19
  import { Item } from "./SnackbarProvider";
20
20
 
21
21
  interface SnackbarContextProps {
22
- enqueueSnackbar: (title?: string, content?: string, options?: SnackbarOptionsProps) => void;
23
- enqueueSuccessSnackbar: (
24
- title?: string,
25
- content?: string,
26
- options?: SnackbarOptionsProps
27
- ) => void;
28
- enqueueWarningSnackbar: (
29
- title?: string,
30
- content?: string,
31
- options?: SnackbarOptionsProps
32
- ) => void;
33
- enqueueErrorSnackbar: (title?: string, content?: string, options?: SnackbarOptionsProps) => void;
22
+ enqueueSnackbar: DeprecatedEnqueueSnackbarType;
23
+ enqueueSuccessSnackbar: DeprecatedEnqueueSnackbarType;
24
+ enqueueWarningSnackbar: DeprecatedEnqueueSnackbarType;
25
+ enqueueErrorSnackbar: DeprecatedEnqueueSnackbarType;
34
26
  setSnackbarHeight: (id: string, height: number) => void;
35
27
  snackbars: Item[];
36
28
  }
@@ -27,3 +27,18 @@ export interface SnackbarOptionsProps {
27
27
  duration?: number;
28
28
  onClose?: () => void;
29
29
  }
30
+
31
+ export interface EnqueueSnackbarProps extends SnackbarOptionsProps {
32
+ title?: string;
33
+ content?: string;
34
+ }
35
+
36
+ export type DeprecatedEnqueueSnackbarType = (
37
+ propsOrTitle: EnqueueSnackbarProps | string | undefined,
38
+ content?: string,
39
+ options?: SnackbarOptionsProps
40
+ ) => void;
41
+
42
+ export function isNewEnqueueSnackbarInterface(args: unknown): args is EnqueueSnackbarProps {
43
+ return args !== null && typeof args === "object" && ("title" in args || "content" in args);
44
+ }