@canonical/react-components 2.2.4 → 2.3.1
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/components/NotificationProvider/NotificationProvider.js +3 -2
- package/dist/components/NotificationProvider/messageBuilder.js +4 -4
- package/dist/components/NotificationProvider/types.d.ts +1 -1
- package/dist/components/{Notification → Notifications/Notification}/Notification.d.ts +1 -1
- package/dist/components/{Notification → Notifications/Notification}/Notification.js +2 -2
- package/dist/components/Notifications/ToastNotification/Animate.d.ts +11 -0
- package/dist/components/Notifications/ToastNotification/Animate.js +59 -0
- package/dist/components/Notifications/ToastNotification/Toast.scss +107 -0
- package/dist/components/Notifications/ToastNotification/ToastNotification.d.ts +10 -0
- package/dist/components/Notifications/ToastNotification/ToastNotification.js +63 -0
- package/dist/components/Notifications/ToastNotification/ToastNotification.stories.d.ts +6 -0
- package/dist/components/Notifications/ToastNotification/ToastNotification.stories.js +64 -0
- package/dist/components/Notifications/ToastNotification/ToastNotificationList.d.ts +21 -0
- package/dist/components/Notifications/ToastNotification/ToastNotificationList.js +215 -0
- package/dist/components/Notifications/ToastNotification/ToastNotificationProvider.d.ts +74 -0
- package/dist/components/Notifications/ToastNotification/ToastNotificationProvider.js +207 -0
- package/dist/components/Notifications/ToastNotification/index.d.ts +5 -0
- package/dist/components/Notifications/ToastNotification/index.js +35 -0
- package/dist/components/Notifications/index.d.ts +4 -0
- package/dist/components/Notifications/index.js +45 -0
- package/dist/esm/components/NotificationProvider/NotificationProvider.js +2 -1
- package/dist/esm/components/NotificationProvider/messageBuilder.js +1 -1
- package/dist/esm/components/NotificationProvider/types.d.ts +1 -1
- package/dist/esm/components/{Notification → Notifications/Notification}/Notification.d.ts +1 -1
- package/dist/esm/components/{Notification → Notifications/Notification}/Notification.js +2 -2
- package/dist/esm/components/Notifications/Notification/Notification.test.d.ts +1 -0
- package/dist/esm/components/Notifications/ToastNotification/Animate.d.ts +11 -0
- package/dist/esm/components/Notifications/ToastNotification/Animate.js +52 -0
- package/dist/esm/components/Notifications/ToastNotification/Toast.scss +107 -0
- package/dist/esm/components/Notifications/ToastNotification/ToastNotification.d.ts +10 -0
- package/dist/esm/components/Notifications/ToastNotification/ToastNotification.js +56 -0
- package/dist/esm/components/Notifications/ToastNotification/ToastNotification.stories.d.ts +6 -0
- package/dist/esm/components/Notifications/ToastNotification/ToastNotification.stories.js +55 -0
- package/dist/esm/components/Notifications/ToastNotification/ToastNotification.test.d.ts +1 -0
- package/dist/esm/components/Notifications/ToastNotification/ToastNotificationList.d.ts +21 -0
- package/dist/esm/components/Notifications/ToastNotification/ToastNotificationList.js +208 -0
- package/dist/esm/components/Notifications/ToastNotification/ToastNotificationProvider.d.ts +74 -0
- package/dist/esm/components/Notifications/ToastNotification/ToastNotificationProvider.js +202 -0
- package/dist/esm/components/Notifications/ToastNotification/index.d.ts +5 -0
- package/dist/esm/components/Notifications/ToastNotification/index.js +4 -0
- package/dist/esm/components/Notifications/index.d.ts +4 -0
- package/dist/esm/components/Notifications/index.js +2 -0
- package/dist/esm/hooks/index.d.ts +1 -0
- package/dist/esm/hooks/index.js +1 -0
- package/dist/esm/hooks/usePrefersReducedMotion.d.ts +6 -0
- package/dist/esm/hooks/usePrefersReducedMotion.js +23 -0
- package/dist/esm/hooks/usePrefersReducedMotion.test.d.ts +1 -0
- package/dist/esm/index.d.ts +5 -3
- package/dist/esm/index.js +3 -2
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +7 -0
- package/dist/hooks/usePrefersReducedMotion.d.ts +6 -0
- package/dist/hooks/usePrefersReducedMotion.js +30 -0
- package/dist/hooks/usePrefersReducedMotion.test.d.ts +1 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.js +38 -3
- package/package.json +1 -1
- /package/dist/components/{Notification → Notifications/Notification}/Notification.stories.d.ts +0 -0
- /package/dist/components/{Notification → Notifications/Notification}/Notification.stories.js +0 -0
- /package/dist/components/{Notification → Notifications/Notification}/Notification.test.d.ts +0 -0
- /package/dist/components/{Notification → Notifications/Notification}/index.d.ts +0 -0
- /package/dist/components/{Notification → Notifications/Notification}/index.js +0 -0
- /package/dist/{esm/components/Notification/Notification.test.d.ts → components/Notifications/ToastNotification/ToastNotification.test.d.ts} +0 -0
- /package/dist/esm/components/{Notification → Notifications/Notification}/Notification.stories.d.ts +0 -0
- /package/dist/esm/components/{Notification → Notifications/Notification}/Notification.stories.js +0 -0
- /package/dist/esm/components/{Notification → Notifications/Notification}/index.d.ts +0 -0
- /package/dist/esm/components/{Notification → Notifications/Notification}/index.js +0 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { ToastNotificationProvider } from "./index";
|
|
3
|
+
declare const meta: Meta<typeof ToastNotificationProvider>;
|
|
4
|
+
export default meta;
|
|
5
|
+
type Story = StoryObj<typeof ToastNotificationProvider>;
|
|
6
|
+
export declare const Default: Story;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import React, { useEffect } from "react";
|
|
2
|
+
import { ToastNotificationProvider, useToastNotification } from "./index";
|
|
3
|
+
import Button from "../../Button";
|
|
4
|
+
var meta = {
|
|
5
|
+
component: ToastNotificationProvider,
|
|
6
|
+
tags: ["autodocs"]
|
|
7
|
+
};
|
|
8
|
+
export default meta;
|
|
9
|
+
export var Default = {
|
|
10
|
+
name: "Default",
|
|
11
|
+
render: () => /*#__PURE__*/React.createElement(ToastNotificationStoryWrapper, null)
|
|
12
|
+
};
|
|
13
|
+
var ToastNotificationStoryWrapper = () => {
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
var root = document.getElementById("storybook-root");
|
|
16
|
+
if (root) {
|
|
17
|
+
root.style.height = "90vh";
|
|
18
|
+
}
|
|
19
|
+
}, []);
|
|
20
|
+
return /*#__PURE__*/React.createElement(ToastNotificationProvider, null, /*#__PURE__*/React.createElement(PreloadedList, null));
|
|
21
|
+
};
|
|
22
|
+
var PreloadedList = () => {
|
|
23
|
+
var toastNotify = useToastNotification();
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
toastNotify.success("Settings saved successfully");
|
|
26
|
+
toastNotify.info("Your changes are syncing in the background");
|
|
27
|
+
toastNotify.failure("Save failed", new Error("500 Internal Server Error"), "Please try again.");
|
|
28
|
+
toastNotify.toggleListView();
|
|
29
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
30
|
+
}, []);
|
|
31
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
32
|
+
style: {
|
|
33
|
+
margin: "2rem auto",
|
|
34
|
+
display: "flex",
|
|
35
|
+
flexDirection: "column",
|
|
36
|
+
flexWrap: "wrap",
|
|
37
|
+
alignContent: "flex-start",
|
|
38
|
+
gap: "0.75rem"
|
|
39
|
+
}
|
|
40
|
+
}, /*#__PURE__*/React.createElement(Button, {
|
|
41
|
+
onClick: () => toastNotify.success("Your changes have been saved.", [{
|
|
42
|
+
label: "Undo",
|
|
43
|
+
onClick: () => console.log("Undo clicked")
|
|
44
|
+
}])
|
|
45
|
+
}, "Add Success"), /*#__PURE__*/React.createElement(Button, {
|
|
46
|
+
onClick: () => toastNotify.info("Your changes are syncing in the background.", "Syncing")
|
|
47
|
+
}, "Add Info"), /*#__PURE__*/React.createElement(Button, {
|
|
48
|
+
onClick: () => toastNotify.failure("Save failed", new Error("500 Internal Server Error"), "Please try again.", [{
|
|
49
|
+
label: "Retry",
|
|
50
|
+
onClick: () => console.log("Retry clicked")
|
|
51
|
+
}])
|
|
52
|
+
}, "Add Error"), /*#__PURE__*/React.createElement(Button, {
|
|
53
|
+
onClick: toastNotify.toggleListView
|
|
54
|
+
}, "Toggle List View"));
|
|
55
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ValueOf } from "../../../types";
|
|
2
|
+
import { GroupedNotificationCount, ToastNotificationType } from "./ToastNotificationProvider";
|
|
3
|
+
import type { FC } from "react";
|
|
4
|
+
import type { NotificationSeverity } from "../../Notifications/Notification";
|
|
5
|
+
import "./Toast.scss";
|
|
6
|
+
export type FilterTypes = ValueOf<typeof NotificationSeverity>;
|
|
7
|
+
export declare const severityOrder: readonly ["positive", "caution", "negative", "information"];
|
|
8
|
+
export declare const iconLookup: {
|
|
9
|
+
readonly positive: "success";
|
|
10
|
+
readonly information: "information";
|
|
11
|
+
readonly caution: "warning";
|
|
12
|
+
readonly negative: "error";
|
|
13
|
+
};
|
|
14
|
+
interface Props {
|
|
15
|
+
notifications: ToastNotificationType[];
|
|
16
|
+
onDismiss: (notification?: ToastNotificationType[]) => void;
|
|
17
|
+
groupedCount: GroupedNotificationCount;
|
|
18
|
+
show: boolean;
|
|
19
|
+
}
|
|
20
|
+
declare const ToastNotificationList: FC<Props>;
|
|
21
|
+
export default ToastNotificationList;
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import _pt from "prop-types";
|
|
2
|
+
import Button from "../../Button";
|
|
3
|
+
import Icon from "../../Icon";
|
|
4
|
+
import Notification from "../Notification";
|
|
5
|
+
import { DefaultTitles } from "../Notification/Notification";
|
|
6
|
+
import { useLayoutEffect, useRef, useState } from "react";
|
|
7
|
+
import { createPortal } from "react-dom";
|
|
8
|
+
import Animate from "./Animate";
|
|
9
|
+
import { usePrefersReducedMotion } from "../../../hooks";
|
|
10
|
+
import React from "react";
|
|
11
|
+
import { ICONS } from "../../Icon";
|
|
12
|
+
import "./Toast.scss";
|
|
13
|
+
export var severityOrder = ["positive", "caution", "negative", "information"];
|
|
14
|
+
export var iconLookup = {
|
|
15
|
+
positive: ICONS.success,
|
|
16
|
+
information: ICONS.information,
|
|
17
|
+
caution: ICONS.warning,
|
|
18
|
+
negative: ICONS.error
|
|
19
|
+
};
|
|
20
|
+
var ToastNotificationList = _ref => {
|
|
21
|
+
var {
|
|
22
|
+
notifications,
|
|
23
|
+
onDismiss,
|
|
24
|
+
groupedCount = {},
|
|
25
|
+
show
|
|
26
|
+
} = _ref;
|
|
27
|
+
var [filters, setFilters] = useState(new Set());
|
|
28
|
+
var prevNotificationsSize = useRef(notifications.length);
|
|
29
|
+
var containerRef = useRef(null);
|
|
30
|
+
var hasFilters = !!filters.size;
|
|
31
|
+
var preferReducedMotion = usePrefersReducedMotion();
|
|
32
|
+
useLayoutEffect(() => {
|
|
33
|
+
adjustScrollPosition();
|
|
34
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
35
|
+
}, [notifications]);
|
|
36
|
+
|
|
37
|
+
// this layout effect is used to maintain scroll position of the
|
|
38
|
+
// notification list when new notifications are added to the list
|
|
39
|
+
// for only when the scroll is at the top
|
|
40
|
+
var adjustScrollPosition = () => {
|
|
41
|
+
var notificationsRemoved = notifications.length < prevNotificationsSize.current;
|
|
42
|
+
prevNotificationsSize.current = notifications.length;
|
|
43
|
+
if (!notifications.length || notificationsRemoved) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
var container = containerRef.current;
|
|
47
|
+
var lastNotification = notifications[notifications.length - 1];
|
|
48
|
+
var notificationEl = document.getElementById(lastNotification.id);
|
|
49
|
+
if (container && notificationEl) {
|
|
50
|
+
var currentScrollY = container.scrollTop;
|
|
51
|
+
var offsetHeight = notificationEl.getBoundingClientRect().height + parseFloat(window.getComputedStyle(notificationEl).marginTop) + parseFloat(window.getComputedStyle(notificationEl).marginBottom);
|
|
52
|
+
// only adjust the scroll height if the scroll is at the top
|
|
53
|
+
if (currentScrollY === 0) {
|
|
54
|
+
container.scrollTop = currentScrollY + offsetHeight;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
var handleFilterSelect = filter => {
|
|
59
|
+
setFilters(prevFilters => {
|
|
60
|
+
var newFilters = new Set(prevFilters);
|
|
61
|
+
if (!newFilters.has(filter)) {
|
|
62
|
+
newFilters.add(filter);
|
|
63
|
+
} else {
|
|
64
|
+
newFilters.delete(filter);
|
|
65
|
+
}
|
|
66
|
+
return newFilters;
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
var handleGroupedDismiss = () => {
|
|
70
|
+
if (hasFilters) {
|
|
71
|
+
var notificationsToClear = notifications.filter(notification => filters.has(notification.type));
|
|
72
|
+
onDismiss(notificationsToClear);
|
|
73
|
+
setFilters(new Set());
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
onDismiss();
|
|
77
|
+
};
|
|
78
|
+
var getSeverityFilters = () => {
|
|
79
|
+
var filterButtons = severityOrder.map(severity => {
|
|
80
|
+
if (groupedCount[severity]) {
|
|
81
|
+
return /*#__PURE__*/React.createElement("button", {
|
|
82
|
+
"aria-label": "Filter ".concat(severity, " notifications"),
|
|
83
|
+
"aria-pressed": filters.has(severity),
|
|
84
|
+
key: severity,
|
|
85
|
+
className: "u-no-margin u-no-border filter-button",
|
|
86
|
+
onClick: () => {
|
|
87
|
+
handleFilterSelect(severity);
|
|
88
|
+
}
|
|
89
|
+
}, /*#__PURE__*/React.createElement(Icon, {
|
|
90
|
+
name: iconLookup[severity]
|
|
91
|
+
}), /*#__PURE__*/React.createElement("span", null, groupedCount[severity]));
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
});
|
|
95
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
96
|
+
className: "filters"
|
|
97
|
+
}, filterButtons, hasFilters && /*#__PURE__*/React.createElement("button", {
|
|
98
|
+
className: "u-no-margin--bottom u-no-border",
|
|
99
|
+
onClick: () => {
|
|
100
|
+
setFilters(new Set());
|
|
101
|
+
}
|
|
102
|
+
}, "Clear filters"));
|
|
103
|
+
};
|
|
104
|
+
var getDismissText = () => {
|
|
105
|
+
if (hasFilters) {
|
|
106
|
+
var validFilters = Object.keys(groupedCount);
|
|
107
|
+
var totalCount = 0;
|
|
108
|
+
for (var filter of validFilters) {
|
|
109
|
+
if (filters.has(filter)) {
|
|
110
|
+
totalCount += groupedCount[filter] || 0;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
var dismissText = /*#__PURE__*/React.createElement("span", {
|
|
114
|
+
className: "dismiss-text"
|
|
115
|
+
}, "Dismiss ", totalCount);
|
|
116
|
+
return dismissText;
|
|
117
|
+
}
|
|
118
|
+
return /*#__PURE__*/React.createElement("span", null, "Dismiss all");
|
|
119
|
+
};
|
|
120
|
+
var handleDismissNotification = notification => {
|
|
121
|
+
if (preferReducedMotion) {
|
|
122
|
+
onDismiss([notification]);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// animate the notification dismissal before updating states to delay unmounting
|
|
127
|
+
var element = document.getElementById("li-".concat(notification.id));
|
|
128
|
+
if (element) {
|
|
129
|
+
element.style.transformOrigin = "center";
|
|
130
|
+
element.style.overflow = "hidden";
|
|
131
|
+
var animation = element.animate([{
|
|
132
|
+
height: "".concat(element.scrollHeight, "px"),
|
|
133
|
+
opacity: 1
|
|
134
|
+
}, {
|
|
135
|
+
height: "0px",
|
|
136
|
+
opacity: 0
|
|
137
|
+
}], {
|
|
138
|
+
duration: 200,
|
|
139
|
+
easing: "linear",
|
|
140
|
+
fill: "forwards"
|
|
141
|
+
});
|
|
142
|
+
animation.onfinish = () => {
|
|
143
|
+
element.style.display = "none";
|
|
144
|
+
onDismiss([notification]);
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// Only filter input data if there are filters present
|
|
150
|
+
var filteredNotifications = hasFilters ? notifications.filter(notification => filters.has(notification.type)) : notifications;
|
|
151
|
+
|
|
152
|
+
// Don't assign alert role for notifications when expanded since we don't want
|
|
153
|
+
// screen readers to announce every existing notification
|
|
154
|
+
var notificationEls = filteredNotifications.map((_, index, array) => {
|
|
155
|
+
var _notification$title;
|
|
156
|
+
var lastNotificationIndex = array.length - 1;
|
|
157
|
+
// This will map notifications in reverse order
|
|
158
|
+
var notification = array[lastNotificationIndex - index];
|
|
159
|
+
return /*#__PURE__*/React.createElement("li", {
|
|
160
|
+
key: notification.id,
|
|
161
|
+
id: "li-".concat(notification.id)
|
|
162
|
+
}, /*#__PURE__*/React.createElement(Notification, {
|
|
163
|
+
id: notification.id,
|
|
164
|
+
title: (_notification$title = notification.title) !== null && _notification$title !== void 0 ? _notification$title : DefaultTitles[notification.type],
|
|
165
|
+
actions: notification.actions,
|
|
166
|
+
severity: notification.type,
|
|
167
|
+
onDismiss: () => {
|
|
168
|
+
handleDismissNotification(notification);
|
|
169
|
+
},
|
|
170
|
+
className: "u-no-margin--bottom individual-notification",
|
|
171
|
+
timestamp: notification.timestamp,
|
|
172
|
+
titleElement: "div"
|
|
173
|
+
}, notification.message));
|
|
174
|
+
});
|
|
175
|
+
return /*#__PURE__*/createPortal( /*#__PURE__*/React.createElement(Animate, {
|
|
176
|
+
show: show,
|
|
177
|
+
from: {
|
|
178
|
+
opacity: 0,
|
|
179
|
+
transform: "translateY(5vh)"
|
|
180
|
+
},
|
|
181
|
+
to: {
|
|
182
|
+
opacity: 1,
|
|
183
|
+
transform: "translateY(0)"
|
|
184
|
+
},
|
|
185
|
+
options: {
|
|
186
|
+
duration: 100
|
|
187
|
+
},
|
|
188
|
+
className: "toast-animate"
|
|
189
|
+
}, /*#__PURE__*/React.createElement("ul", {
|
|
190
|
+
className: "toast-notification-list",
|
|
191
|
+
"aria-label": "Notifications list",
|
|
192
|
+
ref: containerRef
|
|
193
|
+
}, notificationEls, /*#__PURE__*/React.createElement("li", {
|
|
194
|
+
className: "dismiss"
|
|
195
|
+
}, getSeverityFilters(), /*#__PURE__*/React.createElement(Button, {
|
|
196
|
+
className: "u-no-margin--bottom dismiss-button",
|
|
197
|
+
onClick: handleGroupedDismiss,
|
|
198
|
+
hasIcon: true
|
|
199
|
+
}, /*#__PURE__*/React.createElement(Icon, {
|
|
200
|
+
name: "tidy"
|
|
201
|
+
}), getDismissText())))), document.body);
|
|
202
|
+
};
|
|
203
|
+
ToastNotificationList.propTypes = {
|
|
204
|
+
notifications: _pt.array.isRequired,
|
|
205
|
+
onDismiss: _pt.func.isRequired,
|
|
206
|
+
show: _pt.bool.isRequired
|
|
207
|
+
};
|
|
208
|
+
export default ToastNotificationList;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { NotificationAction, NotificationType } from "../../NotificationProvider";
|
|
2
|
+
import type { ValueOf } from "../../../types";
|
|
3
|
+
import { NotificationSeverity } from "../../Notifications";
|
|
4
|
+
import type { FC, PropsWithChildren, ReactNode } from "react";
|
|
5
|
+
export type ToastNotificationType = NotificationType & {
|
|
6
|
+
timestamp?: ReactNode;
|
|
7
|
+
id: string;
|
|
8
|
+
};
|
|
9
|
+
interface ToastNotificationHelper {
|
|
10
|
+
notifications: ToastNotificationType[];
|
|
11
|
+
success: (message: ReactNode, actions?: NotificationAction[]) => ToastNotificationType;
|
|
12
|
+
info: (message: ReactNode, title?: string) => ToastNotificationType;
|
|
13
|
+
failure: (title: string, error: unknown, message?: ReactNode, actions?: NotificationAction[]) => ToastNotificationType;
|
|
14
|
+
clear: (notification?: ToastNotificationType[]) => void;
|
|
15
|
+
toggleListView: () => void;
|
|
16
|
+
isListView: boolean;
|
|
17
|
+
countBySeverity: GroupedNotificationCount;
|
|
18
|
+
}
|
|
19
|
+
export type GroupedNotificationCount = {
|
|
20
|
+
[key in ValueOf<typeof NotificationSeverity>]?: number;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* The `ToastNotificationProvider` can be used to manage toast notifications.
|
|
24
|
+
|
|
25
|
+
Wrap your application with this provider, and in any child component you can get the helper with `const toastNotify = useToastNotification()` to trigger notifications.
|
|
26
|
+
Notifications automatically dismiss after a delay unless manually dismissed or expanded.
|
|
27
|
+
|
|
28
|
+
| **Values** | **Description** |
|
|
29
|
+
|----------------------------------|---------------------------------------------------------------------------------|
|
|
30
|
+
| `toastNotify.success()` | Displays a success toast. Optionally accepts actions. |
|
|
31
|
+
| `toastNotify.info()` | Displays an info toast. Optionally accepts a custom title. |
|
|
32
|
+
| `toastNotify.failure()` | Displays a failure toast with an error and optional message or actions. |
|
|
33
|
+
| `toastNotify.clear()` | Clears specific toasts, or all toasts if none are specified. |
|
|
34
|
+
| `toastNotify.toggleListView()` | Toggles the notification list view open or closed. |
|
|
35
|
+
| `toastNotify.countBySeverity` | Returns the count of notifications grouped by severity (e.g., success, info). |
|
|
36
|
+
|
|
37
|
+
Some example usages:
|
|
38
|
+
|
|
39
|
+
1. **Show a success toast:**
|
|
40
|
+
```
|
|
41
|
+
toastNotify.success("Your changes have been saved.");
|
|
42
|
+
toastNotify.success("Your changes have been saved.", [{label: "Undo", onClick: () => console.log("Undo clicked")}]);
|
|
43
|
+
```
|
|
44
|
+
2. **Show an info toast:**
|
|
45
|
+
```
|
|
46
|
+
toastNotify.info("Your changes are syncing in the background.");
|
|
47
|
+
toastNotify.info("Your changes are syncing in the background.", "Syncing");
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
3. **Show a failure toast:**
|
|
51
|
+
```
|
|
52
|
+
toastNotify.failure("Save failed", new Error("500 Internal Server Error"), "Please try again.");
|
|
53
|
+
toastNotify.failure("Save failed", new Error("500 Internal Server Error"), "Please try again.", [{label: "Retry", onClick: () => console.log("Retry clicked")}]);
|
|
54
|
+
```
|
|
55
|
+
4. **Clear notifications:**
|
|
56
|
+
```
|
|
57
|
+
toastNotify.clear(); // clears all toast notifications
|
|
58
|
+
toastNotify.clear([notificationId]); // clears specific toast notifications
|
|
59
|
+
```
|
|
60
|
+
5. **Toggle the notification list view:**
|
|
61
|
+
```
|
|
62
|
+
toastNotify.toggleListView();
|
|
63
|
+
```
|
|
64
|
+
6. **Get the count of notifications by severity:**
|
|
65
|
+
```
|
|
66
|
+
const count = toastNotify.countBySeverity;
|
|
67
|
+
console.log(count.positive);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Alternatively, you can use the `ToastNotification` and `ToastNotificationList` components directly, without using the provider.
|
|
71
|
+
*/
|
|
72
|
+
declare const ToastNotificationProvider: FC<PropsWithChildren>;
|
|
73
|
+
export default ToastNotificationProvider;
|
|
74
|
+
export declare const useToastNotification: () => ToastNotificationHelper;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
2
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
3
|
+
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
4
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : String(i); }
|
|
5
|
+
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
6
|
+
import { failure as _failure, info as _info } from "../../NotificationProvider";
|
|
7
|
+
import { NotificationSeverity } from "./..";
|
|
8
|
+
import ToastNotification from "./ToastNotification";
|
|
9
|
+
import ToastNotificationList from "./ToastNotificationList";
|
|
10
|
+
import { createContext, useContext, useEffect, useState } from "react";
|
|
11
|
+
import React from "react";
|
|
12
|
+
var HIDE_NOTIFICATION_DELAY = 5000;
|
|
13
|
+
var initialNotification = {
|
|
14
|
+
id: "",
|
|
15
|
+
message: "",
|
|
16
|
+
type: "positive"
|
|
17
|
+
};
|
|
18
|
+
var ToastNotificationContext = /*#__PURE__*/createContext({
|
|
19
|
+
/** List of all active toast notifications */
|
|
20
|
+
notifications: [],
|
|
21
|
+
/** Show a success toast. Optionally pass actions. */
|
|
22
|
+
success: () => initialNotification,
|
|
23
|
+
/** Show an info toast. Optionally pass a custom title. */
|
|
24
|
+
info: () => initialNotification,
|
|
25
|
+
/** Show a failure toast with an error and optional message/actions. */
|
|
26
|
+
failure: () => initialNotification,
|
|
27
|
+
/** Clear one or more specific toasts, or all if none provided. */
|
|
28
|
+
clear: () => null,
|
|
29
|
+
/** Toggle between single toast view and list view. */
|
|
30
|
+
toggleListView: () => null,
|
|
31
|
+
/** Whether the notification list view is currently open. */
|
|
32
|
+
isListView: false,
|
|
33
|
+
/** Grouped count of notifications by severity (positive, info, etc.). */
|
|
34
|
+
countBySeverity: {}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The `ToastNotificationProvider` can be used to manage toast notifications.
|
|
39
|
+
|
|
40
|
+
Wrap your application with this provider, and in any child component you can get the helper with `const toastNotify = useToastNotification()` to trigger notifications.
|
|
41
|
+
Notifications automatically dismiss after a delay unless manually dismissed or expanded.
|
|
42
|
+
|
|
43
|
+
| **Values** | **Description** |
|
|
44
|
+
|----------------------------------|---------------------------------------------------------------------------------|
|
|
45
|
+
| `toastNotify.success()` | Displays a success toast. Optionally accepts actions. |
|
|
46
|
+
| `toastNotify.info()` | Displays an info toast. Optionally accepts a custom title. |
|
|
47
|
+
| `toastNotify.failure()` | Displays a failure toast with an error and optional message or actions. |
|
|
48
|
+
| `toastNotify.clear()` | Clears specific toasts, or all toasts if none are specified. |
|
|
49
|
+
| `toastNotify.toggleListView()` | Toggles the notification list view open or closed. |
|
|
50
|
+
| `toastNotify.countBySeverity` | Returns the count of notifications grouped by severity (e.g., success, info). |
|
|
51
|
+
|
|
52
|
+
Some example usages:
|
|
53
|
+
|
|
54
|
+
1. **Show a success toast:**
|
|
55
|
+
```
|
|
56
|
+
toastNotify.success("Your changes have been saved.");
|
|
57
|
+
toastNotify.success("Your changes have been saved.", [{label: "Undo", onClick: () => console.log("Undo clicked")}]);
|
|
58
|
+
```
|
|
59
|
+
2. **Show an info toast:**
|
|
60
|
+
```
|
|
61
|
+
toastNotify.info("Your changes are syncing in the background.");
|
|
62
|
+
toastNotify.info("Your changes are syncing in the background.", "Syncing");
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
3. **Show a failure toast:**
|
|
66
|
+
```
|
|
67
|
+
toastNotify.failure("Save failed", new Error("500 Internal Server Error"), "Please try again.");
|
|
68
|
+
toastNotify.failure("Save failed", new Error("500 Internal Server Error"), "Please try again.", [{label: "Retry", onClick: () => console.log("Retry clicked")}]);
|
|
69
|
+
```
|
|
70
|
+
4. **Clear notifications:**
|
|
71
|
+
```
|
|
72
|
+
toastNotify.clear(); // clears all toast notifications
|
|
73
|
+
toastNotify.clear([notificationId]); // clears specific toast notifications
|
|
74
|
+
```
|
|
75
|
+
5. **Toggle the notification list view:**
|
|
76
|
+
```
|
|
77
|
+
toastNotify.toggleListView();
|
|
78
|
+
```
|
|
79
|
+
6. **Get the count of notifications by severity:**
|
|
80
|
+
```
|
|
81
|
+
const count = toastNotify.countBySeverity;
|
|
82
|
+
console.log(count.positive);
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Alternatively, you can use the `ToastNotification` and `ToastNotificationList` components directly, without using the provider.
|
|
86
|
+
*/
|
|
87
|
+
|
|
88
|
+
var ToastNotificationProvider = _ref => {
|
|
89
|
+
var {
|
|
90
|
+
children
|
|
91
|
+
} = _ref;
|
|
92
|
+
var [notifications, setNotifications] = useState([]);
|
|
93
|
+
var [showList, setShowList] = useState(false);
|
|
94
|
+
var [notificationTimer, setNotificationTimer] = useState(null);
|
|
95
|
+
|
|
96
|
+
// cleanup on timer if unmounted
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
return () => {
|
|
99
|
+
if (notificationTimer) {
|
|
100
|
+
clearTimeout(notificationTimer);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
104
|
+
}, []);
|
|
105
|
+
var showNotificationWithDelay = () => {
|
|
106
|
+
setNotificationTimer(prevTimer => {
|
|
107
|
+
if (prevTimer) {
|
|
108
|
+
clearTimeout(prevTimer);
|
|
109
|
+
}
|
|
110
|
+
if (!showList) {
|
|
111
|
+
return setTimeout(() => {
|
|
112
|
+
setNotificationTimer(null);
|
|
113
|
+
}, HIDE_NOTIFICATION_DELAY);
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
});
|
|
117
|
+
};
|
|
118
|
+
var clearNotificationTimer = () => {
|
|
119
|
+
setNotificationTimer(prevTimer => {
|
|
120
|
+
if (prevTimer) {
|
|
121
|
+
clearTimeout(prevTimer);
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
var addNotification = notification => {
|
|
127
|
+
var notificationToAdd = _objectSpread(_objectSpread({}, notification), {}, {
|
|
128
|
+
timestamp: new Date().toLocaleString(),
|
|
129
|
+
id: Date.now().toString() + (Math.random() + 1).toString(36).substring(7)
|
|
130
|
+
});
|
|
131
|
+
setNotifications(prev => {
|
|
132
|
+
return [...prev, notificationToAdd];
|
|
133
|
+
});
|
|
134
|
+
showNotificationWithDelay();
|
|
135
|
+
return notificationToAdd;
|
|
136
|
+
};
|
|
137
|
+
var clear = notifications => {
|
|
138
|
+
if (!notifications) {
|
|
139
|
+
setNotifications([]);
|
|
140
|
+
setShowList(false);
|
|
141
|
+
clearNotificationTimer();
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
setNotifications(prev => {
|
|
145
|
+
var removeIdLookup = new Set(notifications);
|
|
146
|
+
var newNotifications = prev.filter(item => !removeIdLookup.has(item));
|
|
147
|
+
|
|
148
|
+
// if we are clearing the last notification from an expanded list,
|
|
149
|
+
// then we want to collapse the list as well if all notifications has been cleared
|
|
150
|
+
if (!newNotifications.length) {
|
|
151
|
+
setShowList(false);
|
|
152
|
+
}
|
|
153
|
+
return newNotifications;
|
|
154
|
+
});
|
|
155
|
+
clearNotificationTimer();
|
|
156
|
+
};
|
|
157
|
+
var toggleListView = () => {
|
|
158
|
+
clearNotificationTimer();
|
|
159
|
+
setShowList(prev => !prev);
|
|
160
|
+
};
|
|
161
|
+
var countBySeverity = {
|
|
162
|
+
positive: 0,
|
|
163
|
+
negative: 0,
|
|
164
|
+
caution: 0,
|
|
165
|
+
information: 0
|
|
166
|
+
};
|
|
167
|
+
notifications.forEach(notification => {
|
|
168
|
+
countBySeverity[notification.type] += 1;
|
|
169
|
+
});
|
|
170
|
+
var helper = {
|
|
171
|
+
notifications,
|
|
172
|
+
failure: (title, error, message, actions) => addNotification(_failure(title, error, message, actions)),
|
|
173
|
+
info: (message, title) => addNotification(_info(message, title)),
|
|
174
|
+
success: (message, actions) => addNotification({
|
|
175
|
+
message,
|
|
176
|
+
actions,
|
|
177
|
+
type: NotificationSeverity.POSITIVE
|
|
178
|
+
}),
|
|
179
|
+
clear,
|
|
180
|
+
toggleListView,
|
|
181
|
+
isListView: showList,
|
|
182
|
+
countBySeverity
|
|
183
|
+
};
|
|
184
|
+
var latestNotification = notifications[notifications.length - 1];
|
|
185
|
+
var hasNotifications = !!notifications.length;
|
|
186
|
+
var showNotification = hasNotifications && !showList && notificationTimer;
|
|
187
|
+
var showNotificationList = hasNotifications && showList;
|
|
188
|
+
return /*#__PURE__*/React.createElement(ToastNotificationContext.Provider, {
|
|
189
|
+
value: helper
|
|
190
|
+
}, children, /*#__PURE__*/React.createElement(ToastNotification, {
|
|
191
|
+
notification: latestNotification,
|
|
192
|
+
onDismiss: clear,
|
|
193
|
+
show: !!showNotification
|
|
194
|
+
}), /*#__PURE__*/React.createElement(ToastNotificationList, {
|
|
195
|
+
notifications: notifications,
|
|
196
|
+
groupedCount: countBySeverity,
|
|
197
|
+
show: showNotificationList,
|
|
198
|
+
onDismiss: clear
|
|
199
|
+
}));
|
|
200
|
+
};
|
|
201
|
+
export default ToastNotificationProvider;
|
|
202
|
+
export var useToastNotification = () => useContext(ToastNotificationContext);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { default as ToastNotificationProvider } from "./ToastNotificationProvider";
|
|
2
|
+
export { useToastNotification } from "./ToastNotificationProvider";
|
|
3
|
+
export type { ToastNotificationType } from "./ToastNotificationProvider";
|
|
4
|
+
export { default as ToastNotification } from "./ToastNotification";
|
|
5
|
+
export { default as ToastNotificationList } from "./ToastNotificationList";
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { default as ToastNotificationProvider } from "./ToastNotificationProvider";
|
|
2
|
+
export { useToastNotification } from "./ToastNotificationProvider";
|
|
3
|
+
export { default as ToastNotification } from "./ToastNotification";
|
|
4
|
+
export { default as ToastNotificationList } from "./ToastNotificationList";
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { default, NotificationSeverity } from "./Notification";
|
|
2
|
+
export type { NotificationProps } from "./Notification";
|
|
3
|
+
export type { ToastNotificationType } from "./ToastNotification";
|
|
4
|
+
export { ToastNotification, ToastNotificationList, useToastNotification, ToastNotificationProvider, } from "./ToastNotification";
|
|
@@ -5,5 +5,6 @@ export { useOnEscapePressed } from "./useOnEscapePressed";
|
|
|
5
5
|
export { usePrevious } from "./usePrevious";
|
|
6
6
|
export { useThrottle } from "./useThrottle";
|
|
7
7
|
export { usePagination } from "./usePagination";
|
|
8
|
+
export { usePrefersReducedMotion } from "./usePrefersReducedMotion";
|
|
8
9
|
export { useWindowFitment } from "./useWindowFitment";
|
|
9
10
|
export type { WindowFitment } from "./useWindowFitment";
|
package/dist/esm/hooks/index.js
CHANGED
|
@@ -5,4 +5,5 @@ export { useOnEscapePressed } from "./useOnEscapePressed";
|
|
|
5
5
|
export { usePrevious } from "./usePrevious";
|
|
6
6
|
export { useThrottle } from "./useThrottle";
|
|
7
7
|
export { usePagination } from "./usePagination";
|
|
8
|
+
export { usePrefersReducedMotion } from "./usePrefersReducedMotion";
|
|
8
9
|
export { useWindowFitment } from "./useWindowFitment";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns true if the user prefers reduced motion.
|
|
3
|
+
* It's useful for conditionally disabling animations for users who prefer reduced motion.
|
|
4
|
+
* @returns {boolean} - true if the user prefers reduced motion, false otherwise.
|
|
5
|
+
*/
|
|
6
|
+
export declare const usePrefersReducedMotion: () => boolean;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
var QUERY = "(prefers-reduced-motion: reduce)";
|
|
3
|
+
var getInitialState = () => window.matchMedia(QUERY).matches;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Returns true if the user prefers reduced motion.
|
|
7
|
+
* It's useful for conditionally disabling animations for users who prefer reduced motion.
|
|
8
|
+
* @returns {boolean} - true if the user prefers reduced motion, false otherwise.
|
|
9
|
+
*/
|
|
10
|
+
export var usePrefersReducedMotion = () => {
|
|
11
|
+
var [prefersReducedMotion, setPrefersReducedMotion] = useState(getInitialState);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
var mediaQuery = window.matchMedia(QUERY);
|
|
14
|
+
var listener = event => {
|
|
15
|
+
setPrefersReducedMotion(event.matches);
|
|
16
|
+
};
|
|
17
|
+
mediaQuery.addEventListener("change", listener);
|
|
18
|
+
return () => {
|
|
19
|
+
mediaQuery.removeEventListener("change", listener);
|
|
20
|
+
};
|
|
21
|
+
}, []);
|
|
22
|
+
return prefersReducedMotion;
|
|
23
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|