@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.
- package/dist/cjs/Button/BaseButton.cjs.js.map +1 -1
- package/dist/cjs/Button/BaseButton.module.cjs.js +1 -1
- package/dist/cjs/Button/Button.module.cjs.js +1 -1
- package/dist/cjs/Button/IconButton.module.cjs.js +1 -1
- package/dist/cjs/DataGrid/DataGridActions/DataGridColumnsToggle.cjs.js +1 -1
- package/dist/cjs/DataGrid/DataGridActions/DataGridColumnsToggle.cjs.js.map +1 -1
- package/dist/cjs/Form/Radio/Radio.cjs.js.map +1 -1
- package/dist/cjs/Form/Select/Option.cjs.js +1 -1
- package/dist/cjs/Form/Select/Option.cjs.js.map +1 -1
- package/dist/cjs/Icon/Icon.cjs.js +1 -1
- package/dist/cjs/Icon/Icon.cjs.js.map +1 -1
- package/dist/cjs/Icon/Icon.module.cjs.js +1 -1
- package/dist/cjs/Link/Link.cjs.js.map +1 -1
- package/dist/cjs/Link/Link.module.cjs.js +1 -1
- package/dist/cjs/Notifications/BaseModal/BaseModal.cjs.js +1 -1
- package/dist/cjs/Notifications/BaseModal/BaseModal.cjs.js.map +1 -1
- package/dist/cjs/Notifications/BaseModal/useRepeatFocus.cjs.js +2 -0
- package/dist/cjs/Notifications/BaseModal/useRepeatFocus.cjs.js.map +1 -0
- package/dist/cjs/Notifications/Snackbar/SnackbarItem/SnackbarItem.cjs.js +1 -1
- package/dist/cjs/Notifications/Snackbar/SnackbarItem/SnackbarItem.cjs.js.map +1 -1
- package/dist/cjs/Notifications/Snackbar/SnackbarItem/SnackbarItem.module.cjs.js +1 -1
- package/dist/cjs/Notifications/Snackbar/SnackbarProvider/SnackbarProvider.cjs.js +1 -1
- package/dist/cjs/Notifications/Snackbar/SnackbarProvider/SnackbarProvider.cjs.js.map +1 -1
- package/dist/cjs/Notifications/Snackbar/SnackbarProvider/SnackbarStateProvider.cjs.js.map +1 -1
- package/dist/cjs/Notifications/Snackbar/interfaces.cjs.js +2 -0
- package/dist/cjs/Notifications/Snackbar/interfaces.cjs.js.map +1 -0
- package/dist/cjs/Stepper/Step.cjs.js +1 -1
- package/dist/cjs/Stepper/Step.cjs.js.map +1 -1
- package/dist/cjs/Stepper/Step.module.cjs.js +1 -1
- package/dist/cjs/Stepper/Stepper.cjs.js +1 -1
- package/dist/cjs/Stepper/Stepper.cjs.js.map +1 -1
- package/dist/cjs/Stepper/Stepper.module.cjs.js +1 -1
- package/dist/cjs/Tooltip/Tooltip.cjs.js +1 -1
- package/dist/cjs/Tooltip/Tooltip.cjs.js.map +1 -1
- package/dist/cjs/Wizard/Wizard.cjs.js.map +1 -1
- package/dist/cjs/Wizard/WizardActions/WizardActions.cjs.js.map +1 -1
- package/dist/cjs/Wizard/WizardStateProvider.cjs.js.map +1 -1
- package/dist/cjs/Wizard/WizardSteps/WizardSteps.cjs.js.map +1 -1
- package/dist/cjs/_BaseStyling_/BaseStyling.cjs.js +1 -1
- package/dist/cjs/_BaseStyling_/BaseStyling.cjs.js.map +1 -1
- package/dist/cjs/src/components/Button/BaseButton.d.ts +1 -1
- package/dist/cjs/src/components/Icon/Icon.d.ts +4 -1
- package/dist/cjs/src/components/Link/Link.d.ts +1 -1
- package/dist/cjs/src/components/Notifications/BaseModal/useRepeatFocus.d.ts +7 -0
- package/dist/cjs/src/components/Notifications/Snackbar/SnackbarProvider/SnackbarStateProvider.d.ts +5 -5
- package/dist/cjs/src/components/Notifications/Snackbar/interfaces.d.ts +6 -0
- package/dist/cjs/src/components/Notifications/Snackbar/useSnackbar.d.ts +4 -4
- package/dist/cjs/src/components/Notifications/Snackbar/useSnackbar.test.d.ts +1 -0
- package/dist/cjs/src/components/Stepper/Step.d.ts +5 -3
- package/dist/cjs/src/components/Stepper/Stepper.d.ts +3 -1
- package/dist/cjs/src/components/Wizard/WizardActions/WizardActions.d.ts +3 -0
- package/dist/cjs/src/components/Wizard/WizardStateProvider.d.ts +3 -0
- package/dist/cjs/src/components/Wizard/WizardSteps/WizardSteps.d.ts +3 -0
- package/dist/cjs/src/components/_BaseStyling_/BaseStyling.d.ts +11 -10
- package/dist/cjs/src/index.d.ts +1 -0
- package/dist/esm/Button/BaseButton.esm.js.map +1 -1
- package/dist/esm/Button/BaseButton.module.esm.js +1 -1
- package/dist/esm/Button/Button.module.esm.js +1 -1
- package/dist/esm/Button/IconButton.module.esm.js +1 -1
- package/dist/esm/DataGrid/DataGridActions/DataGridColumnsToggle.esm.js +1 -1
- package/dist/esm/DataGrid/DataGridActions/DataGridColumnsToggle.esm.js.map +1 -1
- package/dist/esm/Form/Radio/Radio.esm.js.map +1 -1
- package/dist/esm/Form/Select/Option.esm.js +1 -1
- package/dist/esm/Form/Select/Option.esm.js.map +1 -1
- package/dist/esm/Icon/Icon.esm.js +1 -1
- package/dist/esm/Icon/Icon.esm.js.map +1 -1
- package/dist/esm/Icon/Icon.module.esm.js +1 -1
- package/dist/esm/Link/Link.esm.js.map +1 -1
- package/dist/esm/Link/Link.module.esm.js +1 -1
- package/dist/esm/Notifications/BaseModal/BaseModal.esm.js +1 -1
- package/dist/esm/Notifications/BaseModal/BaseModal.esm.js.map +1 -1
- package/dist/esm/Notifications/BaseModal/useRepeatFocus.esm.js +2 -0
- package/dist/esm/Notifications/BaseModal/useRepeatFocus.esm.js.map +1 -0
- package/dist/esm/Notifications/Snackbar/SnackbarItem/SnackbarItem.esm.js +1 -1
- package/dist/esm/Notifications/Snackbar/SnackbarItem/SnackbarItem.esm.js.map +1 -1
- package/dist/esm/Notifications/Snackbar/SnackbarItem/SnackbarItem.module.esm.js +1 -1
- package/dist/esm/Notifications/Snackbar/SnackbarProvider/SnackbarProvider.esm.js +1 -1
- package/dist/esm/Notifications/Snackbar/SnackbarProvider/SnackbarProvider.esm.js.map +1 -1
- package/dist/esm/Notifications/Snackbar/SnackbarProvider/SnackbarStateProvider.esm.js.map +1 -1
- package/dist/esm/Notifications/Snackbar/interfaces.esm.js +2 -0
- package/dist/esm/Notifications/Snackbar/interfaces.esm.js.map +1 -0
- package/dist/esm/Stepper/Step.esm.js +1 -1
- package/dist/esm/Stepper/Step.esm.js.map +1 -1
- package/dist/esm/Stepper/Step.module.esm.js +1 -1
- package/dist/esm/Stepper/Stepper.esm.js +1 -1
- package/dist/esm/Stepper/Stepper.esm.js.map +1 -1
- package/dist/esm/Stepper/Stepper.module.esm.js +1 -1
- package/dist/esm/Tooltip/Tooltip.esm.js +1 -1
- package/dist/esm/Tooltip/Tooltip.esm.js.map +1 -1
- package/dist/esm/Wizard/Wizard.esm.js.map +1 -1
- package/dist/esm/Wizard/WizardActions/WizardActions.esm.js.map +1 -1
- package/dist/esm/Wizard/WizardStateProvider.esm.js.map +1 -1
- package/dist/esm/Wizard/WizardSteps/WizardSteps.esm.js.map +1 -1
- package/dist/esm/_BaseStyling_/BaseStyling.esm.js +1 -1
- package/dist/esm/_BaseStyling_/BaseStyling.esm.js.map +1 -1
- package/dist/esm/src/components/Button/BaseButton.d.ts +1 -1
- package/dist/esm/src/components/Icon/Icon.d.ts +4 -1
- package/dist/esm/src/components/Link/Link.d.ts +1 -1
- package/dist/esm/src/components/Notifications/BaseModal/useRepeatFocus.d.ts +7 -0
- package/dist/esm/src/components/Notifications/Snackbar/SnackbarProvider/SnackbarStateProvider.d.ts +5 -5
- package/dist/esm/src/components/Notifications/Snackbar/interfaces.d.ts +6 -0
- package/dist/esm/src/components/Notifications/Snackbar/useSnackbar.d.ts +4 -4
- package/dist/esm/src/components/Notifications/Snackbar/useSnackbar.test.d.ts +1 -0
- package/dist/esm/src/components/Stepper/Step.d.ts +5 -3
- package/dist/esm/src/components/Stepper/Stepper.d.ts +3 -1
- package/dist/esm/src/components/Wizard/WizardActions/WizardActions.d.ts +3 -0
- package/dist/esm/src/components/Wizard/WizardStateProvider.d.ts +3 -0
- package/dist/esm/src/components/Wizard/WizardSteps/WizardSteps.d.ts +3 -0
- package/dist/esm/src/components/_BaseStyling_/BaseStyling.d.ts +11 -10
- package/dist/esm/src/index.d.ts +1 -0
- package/package.json +1 -1
- package/src/components/Button/BaseButton.tsx +1 -1
- package/src/components/Button/Button.module.scss +2 -10
- package/src/components/Button/Button.test.tsx +15 -3
- package/src/components/Button/IconButton.test.tsx +0 -16
- package/src/components/DataGrid/DataGridActions/DataGridColumnsToggle.tsx +5 -1
- package/src/components/Form/Radio/Radio.tsx +3 -1
- package/src/components/Form/Select/Option.tsx +1 -1
- package/src/components/Icon/Icon.module.scss +12 -0
- package/src/components/Icon/Icon.tsx +4 -1
- package/src/components/Link/Link.module.scss +12 -4
- package/src/components/Link/Link.test.tsx +11 -5
- package/src/components/Link/Link.tsx +1 -1
- package/src/components/Notifications/BaseModal/BaseModal.test.tsx +36 -1
- package/src/components/Notifications/BaseModal/BaseModal.tsx +10 -3
- package/src/components/Notifications/BaseModal/useRepeatFocus.tsx +73 -0
- package/src/components/Notifications/Snackbar/SnackbarItem/SnackbarItem.module.scss +14 -21
- package/src/components/Notifications/Snackbar/SnackbarItem/SnackbarItem.test.tsx +20 -6
- package/src/components/Notifications/Snackbar/SnackbarItem/SnackbarItem.tsx +17 -13
- package/src/components/Notifications/Snackbar/SnackbarProvider/SnackbarProvider.test.tsx +36 -32
- package/src/components/Notifications/Snackbar/SnackbarProvider/SnackbarProvider.tsx +43 -17
- package/src/components/Notifications/Snackbar/SnackbarProvider/SnackbarStateProvider.tsx +5 -13
- package/src/components/Notifications/Snackbar/interfaces.ts +15 -0
- package/src/components/Notifications/Snackbar/useSnackbar.test.tsx +136 -0
- package/src/components/Stepper/Step.module.scss +129 -59
- package/src/components/Stepper/Step.tsx +57 -54
- package/src/components/Stepper/Stepper.module.scss +12 -8
- package/src/components/Stepper/Stepper.test.tsx +3 -3
- package/src/components/Stepper/Stepper.tsx +17 -7
- package/src/components/Tooltip/Tooltip.tsx +2 -2
- package/src/components/Wizard/Wizard.tsx +3 -0
- package/src/components/Wizard/WizardActions/WizardActions.tsx +3 -0
- package/src/components/Wizard/WizardStateProvider.tsx +3 -0
- package/src/components/Wizard/WizardSteps/WizardSteps.tsx +3 -0
- package/src/components/_BaseStyling_/BaseStyling.tsx +24 -22
- package/src/font/icomoon.eot +0 -0
- package/src/font/icomoon.svg +5 -2
- package/src/font/icomoon.ttf +0 -0
- package/src/font/icomoon.woff +0 -0
- package/src/font/selection.json +1 -1
- package/src/index.ts +1 -0
- 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
|
-
|
|
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={
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
99
|
-
|
|
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("
|
|
32
|
+
describe("<SnackbarItem />", () => {
|
|
33
|
+
it("should render without crashing", () => {
|
|
33
34
|
const { container } = render(<SnackbarItem {...initParams} />);
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
const titleDiv = container.querySelector(".title");
|
|
37
|
+
expect(titleDiv).toHaveTextContent(initParams.title!);
|
|
36
38
|
const contentDiv = container.querySelector(".content");
|
|
37
|
-
expect(contentDiv).
|
|
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
|
-
<
|
|
129
|
-
|
|
130
|
-
{
|
|
131
|
-
|
|
132
|
-
{title}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
{content}
|
|
138
|
-
|
|
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,
|
|
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(
|
|
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,
|
|
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("
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
190
|
+
useEffect(() => {
|
|
191
|
+
enqueueErrorSnackbar("error", undefined, { onClose: onCloseHandler, duration: 1 });
|
|
192
|
+
enqueueSuccessSnackbar("success", undefined, { onClose: onCloseHandler, duration: 1 });
|
|
193
|
+
}, []);
|
|
191
194
|
|
|
192
|
-
|
|
193
|
-
|
|
195
|
+
return <div></div>;
|
|
196
|
+
};
|
|
194
197
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
198
|
+
const queries = render(
|
|
199
|
+
<SnackbarProvider closeButtonTitle="close">
|
|
200
|
+
<ExampleComponent />
|
|
201
|
+
</SnackbarProvider>
|
|
202
|
+
);
|
|
200
203
|
|
|
201
|
-
|
|
202
|
-
|
|
204
|
+
const errorSnackbar = await queries.findByText(/error/i);
|
|
205
|
+
const successSnackbar = await queries.findByText(/success/i);
|
|
203
206
|
|
|
204
|
-
|
|
205
|
-
|
|
207
|
+
expect(errorSnackbar).toBeTruthy();
|
|
208
|
+
expect(successSnackbar).toBeTruthy();
|
|
206
209
|
|
|
207
|
-
|
|
208
|
-
|
|
210
|
+
const parentErrorSnackbar = errorSnackbar.closest(".snackbar")!;
|
|
211
|
+
const parentSuccessSnackbar = successSnackbar.closest(".snackbar")!;
|
|
209
212
|
|
|
210
|
-
|
|
211
|
-
|
|
213
|
+
fireEvent.animationEnd(parentErrorSnackbar);
|
|
214
|
+
fireEvent.animationEnd(parentSuccessSnackbar);
|
|
212
215
|
|
|
213
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
} =
|
|
112
|
+
} = props;
|
|
105
113
|
const item: Item = {
|
|
106
|
-
title,
|
|
107
|
-
content,
|
|
114
|
+
title: props.title,
|
|
115
|
+
content: props.content,
|
|
108
116
|
variant,
|
|
109
|
-
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
|
|
127
|
+
const mapToNewInterface = (
|
|
120
128
|
title?: string,
|
|
121
129
|
content?: string,
|
|
122
|
-
options
|
|
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
|
-
|
|
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
|
-
|
|
150
|
+
propsOrTitle: EnqueueSnackbarProps | string | undefined,
|
|
129
151
|
content?: string,
|
|
130
|
-
options
|
|
152
|
+
options: SnackbarOptionsProps = {}
|
|
131
153
|
): void => {
|
|
132
|
-
|
|
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
|
-
|
|
160
|
+
propsOrTitle: EnqueueSnackbarProps | string | undefined,
|
|
137
161
|
content?: string,
|
|
138
|
-
options
|
|
162
|
+
options: SnackbarOptionsProps = {}
|
|
139
163
|
): void => {
|
|
140
|
-
|
|
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 {
|
|
18
|
+
import { DeprecatedEnqueueSnackbarType } from "../interfaces";
|
|
19
19
|
import { Item } from "./SnackbarProvider";
|
|
20
20
|
|
|
21
21
|
interface SnackbarContextProps {
|
|
22
|
-
enqueueSnackbar:
|
|
23
|
-
enqueueSuccessSnackbar:
|
|
24
|
-
|
|
25
|
-
|
|
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
|
+
}
|