@patternfly/react-core 4.241.1 → 4.241.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patternfly/react-core",
3
- "version": "4.241.1",
3
+ "version": "4.241.3",
4
4
  "description": "This library provides a set of common React components for use with the PatternFly reference implementation.",
5
5
  "main": "dist/js/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -44,9 +44,9 @@
44
44
  "generate": "node scripts/copyStyles.js"
45
45
  },
46
46
  "dependencies": {
47
- "@patternfly/react-icons": "^4.92.1",
48
- "@patternfly/react-styles": "^4.91.1",
49
- "@patternfly/react-tokens": "^4.93.1",
47
+ "@patternfly/react-icons": "^4.92.3",
48
+ "@patternfly/react-styles": "^4.91.3",
49
+ "@patternfly/react-tokens": "^4.93.3",
50
50
  "focus-trap": "6.9.2",
51
51
  "react-dropzone": "9.0.0",
52
52
  "tippy.js": "5.1.2",
@@ -70,5 +70,5 @@
70
70
  "react": "^16.8 || ^17 || ^18",
71
71
  "react-dom": "^16.8 || ^17 || ^18"
72
72
  },
73
- "gitHead": "447aaa1f6cad3927b263c7eb737f16a4c3dde438"
73
+ "gitHead": "0a76fbdba0aabc4636f2e27a87c7bcc8b6e0939c"
74
74
  }
@@ -0,0 +1,19 @@
1
+ ---
2
+ id: Alert group
3
+ section: components
4
+ ---
5
+
6
+ import { useEffect } from 'react';
7
+ import SearchIcon from '@patternfly/react-icons/dist/js/icons/search-icon';
8
+ import DashboardWrapper from './examples/DashboardWrapper';
9
+ import DashboardHeader from './examples/DashboardHeader';
10
+
11
+
12
+ ## Demos
13
+
14
+ This demonstrates how you can assemble a full page view including the use of alert group toast notifications with timeout that are also displayed inside the notification drawer.
15
+
16
+ ### Alert group toast with notification drawer
17
+
18
+ ```js file='./examples/AlertGroup/AlertGroupToastWithNotificationDrawer.tsx' isFullscreen
19
+ ```
@@ -0,0 +1,356 @@
1
+ import React, { useEffect } from 'react';
2
+ import {
3
+ Button,
4
+ Dropdown,
5
+ DropdownItem,
6
+ EmptyState,
7
+ EmptyStateBody,
8
+ EmptyStateIcon,
9
+ KebabToggle,
10
+ NotificationBadge,
11
+ NotificationBadgeVariant,
12
+ NotificationDrawer,
13
+ NotificationDrawerBody,
14
+ NotificationDrawerHeader,
15
+ NotificationDrawerList,
16
+ NotificationDrawerListItem,
17
+ NotificationDrawerListItemBody,
18
+ NotificationDrawerListItemHeader,
19
+ PageSection,
20
+ PageSectionVariants,
21
+ TextContent,
22
+ Text,
23
+ Title,
24
+ DropdownPosition,
25
+ EmptyStateVariant,
26
+ NumberInput,
27
+ Alert,
28
+ AlertProps,
29
+ AlertGroup,
30
+ AlertActionCloseButton
31
+ } from '@patternfly/react-core';
32
+
33
+ import SearchIcon from '@patternfly/react-icons/dist/js/icons/search-icon';
34
+ import DashboardWrapper from '../DashboardWrapper';
35
+ import DashboardHeader from '../DashboardHeader';
36
+
37
+ interface NotificationProps {
38
+ title: string;
39
+ srTitle: string;
40
+ variant: 'default' | 'success' | 'danger' | 'warning' | 'info';
41
+ key: React.Key;
42
+ timestamp: string;
43
+ description: string;
44
+ isNotificationRead: boolean;
45
+ }
46
+
47
+ export const AlertGroupToastWithNotificationDrawer: React.FunctionComponent = () => {
48
+ const maxDisplayedAlerts = 3;
49
+ const minAlerts = 0;
50
+ const maxAlerts = 100;
51
+ const alertTimeout = 8000;
52
+
53
+ const [isDrawerExpanded, setDrawerExpanded] = React.useState(false);
54
+ const [openDropdownKey, setOpenDropdownKey] = React.useState<React.Key | null>(null);
55
+ const [overflowMessage, setOverflowMessage] = React.useState<string>('');
56
+ const [maxDisplayed, setMaxDisplayed] = React.useState(maxDisplayedAlerts);
57
+ const [alerts, setAlerts] = React.useState<React.ReactElement<AlertProps>[]>([]);
58
+ const [notifications, setNotifications] = React.useState<NotificationProps[]>([]);
59
+
60
+ useEffect(() => {
61
+ setOverflowMessage(buildOverflowMessage());
62
+ }, [maxDisplayed, notifications, alerts]);
63
+
64
+ const addNewNotification = (variant: NotificationProps['variant']) => {
65
+ const variantFormatted = variant.charAt(0).toUpperCase() + variant.slice(1);
66
+ const title = variantFormatted + ' alert notification';
67
+ const srTitle = variantFormatted + ' alert';
68
+ const description = variantFormatted + ' alert notification description';
69
+ const key = getUniqueId();
70
+ const timestamp = getTimeCreated();
71
+
72
+ setNotifications(prevNotifications => [
73
+ { title, srTitle, variant, key, timestamp, description, isNotificationRead: false },
74
+ ...prevNotifications
75
+ ]);
76
+
77
+ if (!isDrawerExpanded) {
78
+ setAlerts(prevAlerts => [
79
+ <Alert
80
+ variant={variant}
81
+ title={title}
82
+ timeout={alertTimeout}
83
+ onTimeout={() => removeAlert(key)}
84
+ isLiveRegion
85
+ actionClose={
86
+ <AlertActionCloseButton title={title} variantLabel={`${variant} alert`} onClose={() => removeAlert(key)} />
87
+ }
88
+ key={key}
89
+ id={key.toString()}
90
+ >
91
+ <p>{description}</p>
92
+ </Alert>,
93
+ ...prevAlerts
94
+ ]);
95
+ }
96
+ };
97
+
98
+ const removeNotification = (key: React.Key) => {
99
+ setNotifications(prevNotifications => prevNotifications.filter(notification => notification.key !== key));
100
+ };
101
+
102
+ const removeAllNotifications = () => {
103
+ setNotifications([]);
104
+ };
105
+
106
+ const isNotificationRead = (key: React.Key) =>
107
+ notifications.find(notification => notification.key === key)?.isNotificationRead;
108
+
109
+ const markNotificationRead = (key: React.Key) => {
110
+ setNotifications(prevNotifications =>
111
+ prevNotifications.map(notification =>
112
+ notification.key === key ? { ...notification, isNotificationRead: true } : notification
113
+ )
114
+ );
115
+ };
116
+
117
+ const markAllNotificationsRead = () => {
118
+ setNotifications(prevNotifications =>
119
+ prevNotifications.map(notification => ({ ...notification, isNotificationRead: true }))
120
+ );
121
+ };
122
+
123
+ const getUnreadNotificationsNumber = () =>
124
+ notifications.filter(notification => notification.isNotificationRead === false).length;
125
+
126
+ const containsUnreadAlertNotification = () =>
127
+ notifications.filter(notification => notification.isNotificationRead === false && notification.variant === 'danger')
128
+ .length > 0;
129
+
130
+ const getNotificationBadgeVariant = () => {
131
+ if (getUnreadNotificationsNumber() === 0) {
132
+ return NotificationBadgeVariant.read;
133
+ }
134
+ if (containsUnreadAlertNotification()) {
135
+ return NotificationBadgeVariant.attention;
136
+ }
137
+ return NotificationBadgeVariant.unread;
138
+ };
139
+
140
+ const onNotificationBadgeClick = () => {
141
+ removeAllAlerts();
142
+ setDrawerExpanded(!isDrawerExpanded);
143
+ };
144
+
145
+ const onDropdownToggle = (id: React.Key, isActive: boolean) => {
146
+ if (isActive) {
147
+ setOpenDropdownKey(id);
148
+ return;
149
+ }
150
+ setOpenDropdownKey(null);
151
+ };
152
+
153
+ const onDropdownSelect = () => {
154
+ setOpenDropdownKey(null);
155
+ };
156
+
157
+ const buildOverflowMessage = () => {
158
+ const overflow = alerts.length - maxDisplayed;
159
+ if (overflow > 0 && maxDisplayed > 0) {
160
+ return `View ${overflow} more notification(s) in notification drawer`;
161
+ }
162
+ return '';
163
+ };
164
+
165
+ const getUniqueId = () => new Date().getTime();
166
+
167
+ const getTimeCreated = () => {
168
+ const dateCreated = new Date();
169
+ return (
170
+ dateCreated.toDateString() +
171
+ ' at ' +
172
+ ('00' + dateCreated.getHours().toString()).slice(-2) +
173
+ ':' +
174
+ ('00' + dateCreated.getMinutes().toString()).slice(-2)
175
+ );
176
+ };
177
+
178
+ const removeAlert = (key: React.Key) => {
179
+ setAlerts(prevAlerts => prevAlerts.filter(alert => alert.props.id !== key.toString()));
180
+ };
181
+
182
+ const removeAllAlerts = () => {
183
+ setAlerts([]);
184
+ };
185
+
186
+ const onAlertGroupOverflowClick = () => {
187
+ removeAllAlerts();
188
+ setDrawerExpanded(true);
189
+ };
190
+
191
+ const onMaxDisplayedAlertsMinus = () => {
192
+ setMaxDisplayed(normalizeAlertsNumber(maxDisplayed - 1));
193
+ };
194
+
195
+ const onMaxDisplayedAlertsChange = (event: any) => {
196
+ setMaxDisplayed(normalizeAlertsNumber(Number(event.target.value)));
197
+ };
198
+
199
+ const onMaxDisplayedAlertsPlus = () => {
200
+ setMaxDisplayed(normalizeAlertsNumber(maxDisplayed + 1));
201
+ };
202
+
203
+ const normalizeAlertsNumber = (value: number) => Math.max(Math.min(value, maxAlerts), minAlerts);
204
+
205
+ const alertButtonStyle = { marginRight: '8px', marginTop: '8px' };
206
+
207
+ const notificationBadge = (
208
+ <NotificationBadge
209
+ variant={getNotificationBadgeVariant()}
210
+ onClick={onNotificationBadgeClick}
211
+ aria-label="Notifications"
212
+ ></NotificationBadge>
213
+ );
214
+
215
+ const notificationDrawerActions = [
216
+ <DropdownItem key="markAllRead" onClick={markAllNotificationsRead} component="button">
217
+ Mark all read
218
+ </DropdownItem>,
219
+ <DropdownItem key="clearAll" onClick={removeAllNotifications} component="button">
220
+ Clear all
221
+ </DropdownItem>
222
+ ];
223
+
224
+ const notificationDrawerDropdownItems = (key: React.Key) => [
225
+ <DropdownItem key="markRead" component="button" onClick={() => markNotificationRead(key)}>
226
+ Mark as read
227
+ </DropdownItem>,
228
+ <DropdownItem key="action" component="button" onClick={() => removeNotification(key)}>
229
+ Clear
230
+ </DropdownItem>
231
+ ];
232
+
233
+ const notificationDrawer = (
234
+ <NotificationDrawer>
235
+ <NotificationDrawerHeader count={getUnreadNotificationsNumber()} onClose={() => setDrawerExpanded(false)}>
236
+ <Dropdown
237
+ onSelect={onDropdownSelect}
238
+ toggle={
239
+ <KebabToggle
240
+ onToggle={isActive => onDropdownToggle('dropdown-toggle-id-0', isActive)}
241
+ id="dropdown-toggle-id-0"
242
+ />
243
+ }
244
+ isOpen={openDropdownKey === 'dropdown-toggle-id-0'}
245
+ isPlain
246
+ dropdownItems={notificationDrawerActions}
247
+ id="notification-drawer-0"
248
+ position={DropdownPosition.right}
249
+ />
250
+ </NotificationDrawerHeader>
251
+ <NotificationDrawerBody>
252
+ {notifications.length !== 0 && (
253
+ <NotificationDrawerList>
254
+ {notifications.map(({ key, variant, title, srTitle, description, timestamp }) => (
255
+ <NotificationDrawerListItem
256
+ key={key}
257
+ variant={variant}
258
+ isRead={isNotificationRead(key)}
259
+ onClick={() => markNotificationRead(key)}
260
+ >
261
+ <NotificationDrawerListItemHeader variant={variant} title={title} srTitle={srTitle}>
262
+ <Dropdown
263
+ position={DropdownPosition.right}
264
+ onSelect={onDropdownSelect}
265
+ toggle={<KebabToggle onToggle={isActive => onDropdownToggle(key, isActive)} id={key.toString()} />}
266
+ isOpen={openDropdownKey === key}
267
+ isPlain
268
+ dropdownItems={notificationDrawerDropdownItems(key)}
269
+ id={key.toString()}
270
+ />
271
+ </NotificationDrawerListItemHeader>
272
+ <NotificationDrawerListItemBody timestamp={timestamp}> {description} </NotificationDrawerListItemBody>
273
+ </NotificationDrawerListItem>
274
+ ))}
275
+ </NotificationDrawerList>
276
+ )}
277
+ {notifications.length === 0 && (
278
+ <EmptyState variant={EmptyStateVariant.full}>
279
+ <EmptyStateIcon icon={SearchIcon} />
280
+ <Title headingLevel="h2" size="lg">
281
+ No notifications found
282
+ </Title>
283
+ <EmptyStateBody>There are currently no notifications.</EmptyStateBody>
284
+ </EmptyState>
285
+ )}
286
+ </NotificationDrawerBody>
287
+ </NotificationDrawer>
288
+ );
289
+
290
+ return (
291
+ <DashboardWrapper
292
+ header={<DashboardHeader notificationBadge={notificationBadge} />}
293
+ mainContainerId="main-content-page-layout-default-nav"
294
+ notificationDrawer={notificationDrawer}
295
+ isNotificationDrawerExpanded={isDrawerExpanded}
296
+ >
297
+ <PageSection variant={PageSectionVariants.light}>
298
+ <TextContent>
299
+ <Text component="h1">Alert Group with Notification Drawer demo</Text>
300
+ <Text component="p">
301
+ New alerts can be added with buttons below. Each alert has a timeout of 7 seconds, however, even after the
302
+ timeout expires, all alerts are still visible in the notification drawer. By default, only 3 alerts are
303
+ displayed. The rest can be accessed in the notification drawer after clicking on the bell icon in the header
304
+ or by clicking on the overflow message.
305
+ </Text>
306
+ </TextContent>
307
+ </PageSection>
308
+
309
+ <PageSection variant={PageSectionVariants.light}>
310
+ <Button variant="secondary" onClick={() => addNewNotification('success')} style={alertButtonStyle}>
311
+ Add toast success alert
312
+ </Button>
313
+ <Button variant="secondary" onClick={() => addNewNotification('danger')} style={alertButtonStyle}>
314
+ Add toast danger alert
315
+ </Button>
316
+ <Button variant="secondary" onClick={() => addNewNotification('info')} style={alertButtonStyle}>
317
+ Add toast info alert
318
+ </Button>
319
+ <br />
320
+ <br />
321
+ <Button variant="secondary" onClick={() => addNewNotification('warning')} style={alertButtonStyle}>
322
+ Add toast warning alert
323
+ </Button>
324
+ <Button variant="secondary" onClick={() => addNewNotification('default')} style={alertButtonStyle}>
325
+ Add toast default alert
326
+ </Button>
327
+ </PageSection>
328
+
329
+ <PageSection variant={PageSectionVariants.light}>
330
+ <TextContent>
331
+ <br />
332
+ <Text component="h2">Max displayed alerts</Text>
333
+ <Text component="p">The maximum number of displayed alerts can be set below.</Text>
334
+ </TextContent>
335
+ <NumberInput
336
+ value={maxDisplayed}
337
+ min={minAlerts}
338
+ max={maxAlerts}
339
+ onMinus={onMaxDisplayedAlertsMinus}
340
+ onChange={onMaxDisplayedAlertsChange}
341
+ onPlus={onMaxDisplayedAlertsPlus}
342
+ inputName="input"
343
+ inputAriaLabel="max diplayed alerts number input"
344
+ minusBtnAriaLabel="minus"
345
+ plusBtnAriaLabel="plus"
346
+ style={{ margin: '12px 0' }}
347
+ />
348
+ </PageSection>
349
+ <PageSection variant={PageSectionVariants.light}>
350
+ <AlertGroup isToast isLiveRegion onOverflowClick={onAlertGroupOverflowClick} overflowMessage={overflowMessage}>
351
+ {alerts.slice(0, maxDisplayed)}
352
+ </AlertGroup>
353
+ </PageSection>
354
+ </DashboardWrapper>
355
+ );
356
+ };
@@ -92,6 +92,7 @@ export default class DashboardHeader extends React.Component {
92
92
 
93
93
  render() {
94
94
  const { isDropdownOpen, isKebabDropdownOpen, isAppLauncherOpen } = this.state;
95
+ const { notificationBadge } = this.props;
95
96
 
96
97
  const kebabDropdownItems = [
97
98
  <DropdownItem key="kebab-1">
@@ -128,9 +129,18 @@ export default class DashboardHeader extends React.Component {
128
129
  alignment={{ default: 'alignRight' }}
129
130
  spacer={{ default: 'spacerNone', md: 'spacerMd' }}
130
131
  >
131
- <ToolbarItem>
132
- <Button aria-label="Notifications" variant={ButtonVariant.plain} icon={<BellIcon />} />
133
- </ToolbarItem>
132
+ {notificationBadge ? (
133
+ notificationBadge
134
+ ) : (
135
+ <ToolbarItem>
136
+ <Button
137
+ aria-label="Notifications"
138
+ variant={ButtonVariant.plain}
139
+ icon={<BellIcon />}
140
+ onClick={() => {}}
141
+ />
142
+ </ToolbarItem>
143
+ )}
134
144
  <ToolbarGroup variant="icon-button-group" visibility={{ default: 'hidden', lg: 'visible' }}>
135
145
  <ToolbarItem visibility={{ default: 'hidden', sm: 'hidden', lg: 'visible' }}>
136
146
  <ApplicationLauncher
@@ -58,6 +58,8 @@ export default class DashboardWrapper extends React.Component {
58
58
  sidebarNavOpen,
59
59
  onPageResize,
60
60
  hasNoBreadcrumb,
61
+ notificationDrawer,
62
+ isNotificationDrawerExpanded,
61
63
  hasPageTemplateTitle
62
64
  } = this.props;
63
65
 
@@ -103,6 +105,8 @@ export default class DashboardWrapper extends React.Component {
103
105
  skipToContent={PageSkipToContent}
104
106
  breadcrumb={renderedBreadcrumb}
105
107
  mainContainerId={mainContainerId ? mainContainerId : 'main-content-page-layout-default-nav'}
108
+ notificationDrawer={notificationDrawer}
109
+ isNotificationDrawerExpanded={isNotificationDrawerExpanded}
106
110
  onPageResize={onPageResize}
107
111
  >
108
112
  {hasPageTemplateTitle && PageTemplateTitle}