diff --git a/packages/react-core/src/demos/AlertGroup.md b/packages/react-core/src/demos/AlertGroup.md new file mode 100644 index 00000000000..72329c72de0 --- /dev/null +++ b/packages/react-core/src/demos/AlertGroup.md @@ -0,0 +1,19 @@ +--- +id: Alert group +section: components +--- + +import { useEffect } from 'react'; +import SearchIcon from '@patternfly/react-icons/dist/js/icons/search-icon'; +import DashboardWrapper from './examples/DashboardWrapper'; +import DashboardHeader from './examples/DashboardHeader'; + + +## Demos + +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. + +### Alert group toast with notification drawer + +```js file='./examples/AlertGroup/AlertGroupToastWithNotificationDrawer.tsx' isFullscreen +``` \ No newline at end of file diff --git a/packages/react-core/src/demos/examples/AlertGroup/AlertGroupToastWithNotificationDrawer.tsx b/packages/react-core/src/demos/examples/AlertGroup/AlertGroupToastWithNotificationDrawer.tsx new file mode 100644 index 00000000000..515db88f516 --- /dev/null +++ b/packages/react-core/src/demos/examples/AlertGroup/AlertGroupToastWithNotificationDrawer.tsx @@ -0,0 +1,356 @@ +import React, { useEffect } from 'react'; +import { + Button, + Dropdown, + DropdownItem, + EmptyState, + EmptyStateBody, + EmptyStateIcon, + KebabToggle, + NotificationBadge, + NotificationBadgeVariant, + NotificationDrawer, + NotificationDrawerBody, + NotificationDrawerHeader, + NotificationDrawerList, + NotificationDrawerListItem, + NotificationDrawerListItemBody, + NotificationDrawerListItemHeader, + PageSection, + PageSectionVariants, + TextContent, + Text, + Title, + DropdownPosition, + EmptyStateVariant, + NumberInput, + Alert, + AlertProps, + AlertGroup, + AlertActionCloseButton +} from '@patternfly/react-core'; + +import SearchIcon from '@patternfly/react-icons/dist/js/icons/search-icon'; +import DashboardWrapper from '../DashboardWrapper'; +import DashboardHeader from '../DashboardHeader'; + +interface NotificationProps { + title: string; + srTitle: string; + variant: 'default' | 'success' | 'danger' | 'warning' | 'info'; + key: React.Key; + timestamp: string; + description: string; + isNotificationRead: boolean; +} + +export const AlertGroupToastWithNotificationDrawer: React.FunctionComponent = () => { + const maxDisplayedAlerts = 3; + const minAlerts = 0; + const maxAlerts = 100; + const alertTimeout = 8000; + + const [isDrawerExpanded, setDrawerExpanded] = React.useState(false); + const [openDropdownKey, setOpenDropdownKey] = React.useState(null); + const [overflowMessage, setOverflowMessage] = React.useState(''); + const [maxDisplayed, setMaxDisplayed] = React.useState(maxDisplayedAlerts); + const [alerts, setAlerts] = React.useState[]>([]); + const [notifications, setNotifications] = React.useState([]); + + useEffect(() => { + setOverflowMessage(buildOverflowMessage()); + }, [maxDisplayed, notifications, alerts]); + + const addNewNotification = (variant: NotificationProps['variant']) => { + const variantFormatted = variant.charAt(0).toUpperCase() + variant.slice(1); + const title = variantFormatted + ' alert notification'; + const srTitle = variantFormatted + ' alert'; + const description = variantFormatted + ' alert notification description'; + const key = getUniqueId(); + const timestamp = getTimeCreated(); + + setNotifications(prevNotifications => [ + { title, srTitle, variant, key, timestamp, description, isNotificationRead: false }, + ...prevNotifications + ]); + + if (!isDrawerExpanded) { + setAlerts(prevAlerts => [ + removeAlert(key)} + isLiveRegion + actionClose={ + removeAlert(key)} /> + } + key={key} + id={key.toString()} + > +

{description}

+
, + ...prevAlerts + ]); + } + }; + + const removeNotification = (key: React.Key) => { + setNotifications(prevNotifications => prevNotifications.filter(notification => notification.key !== key)); + }; + + const removeAllNotifications = () => { + setNotifications([]); + }; + + const isNotificationRead = (key: React.Key) => + notifications.find(notification => notification.key === key)?.isNotificationRead; + + const markNotificationRead = (key: React.Key) => { + setNotifications(prevNotifications => + prevNotifications.map(notification => + notification.key === key ? { ...notification, isNotificationRead: true } : notification + ) + ); + }; + + const markAllNotificationsRead = () => { + setNotifications(prevNotifications => + prevNotifications.map(notification => ({ ...notification, isNotificationRead: true })) + ); + }; + + const getUnreadNotificationsNumber = () => + notifications.filter(notification => notification.isNotificationRead === false).length; + + const containsUnreadAlertNotification = () => + notifications.filter(notification => notification.isNotificationRead === false && notification.variant === 'danger') + .length > 0; + + const getNotificationBadgeVariant = () => { + if (getUnreadNotificationsNumber() === 0) { + return NotificationBadgeVariant.read; + } + if (containsUnreadAlertNotification()) { + return NotificationBadgeVariant.attention; + } + return NotificationBadgeVariant.unread; + }; + + const onNotificationBadgeClick = () => { + removeAllAlerts(); + setDrawerExpanded(!isDrawerExpanded); + }; + + const onDropdownToggle = (id: React.Key, isActive: boolean) => { + if (isActive) { + setOpenDropdownKey(id); + return; + } + setOpenDropdownKey(null); + }; + + const onDropdownSelect = () => { + setOpenDropdownKey(null); + }; + + const buildOverflowMessage = () => { + const overflow = alerts.length - maxDisplayed; + if (overflow > 0 && maxDisplayed > 0) { + return `View ${overflow} more notification(s) in notification drawer`; + } + return ''; + }; + + const getUniqueId = () => new Date().getTime(); + + const getTimeCreated = () => { + const dateCreated = new Date(); + return ( + dateCreated.toDateString() + + ' at ' + + ('00' + dateCreated.getHours().toString()).slice(-2) + + ':' + + ('00' + dateCreated.getMinutes().toString()).slice(-2) + ); + }; + + const removeAlert = (key: React.Key) => { + setAlerts(prevAlerts => prevAlerts.filter(alert => alert.props.id !== key.toString())); + }; + + const removeAllAlerts = () => { + setAlerts([]); + }; + + const onAlertGroupOverflowClick = () => { + removeAllAlerts(); + setDrawerExpanded(true); + }; + + const onMaxDisplayedAlertsMinus = () => { + setMaxDisplayed(normalizeAlertsNumber(maxDisplayed - 1)); + }; + + const onMaxDisplayedAlertsChange = (event: any) => { + setMaxDisplayed(normalizeAlertsNumber(Number(event.target.value))); + }; + + const onMaxDisplayedAlertsPlus = () => { + setMaxDisplayed(normalizeAlertsNumber(maxDisplayed + 1)); + }; + + const normalizeAlertsNumber = (value: number) => Math.max(Math.min(value, maxAlerts), minAlerts); + + const alertButtonStyle = { marginRight: '8px', marginTop: '8px' }; + + const notificationBadge = ( + + ); + + const notificationDrawerActions = [ + + Mark all read + , + + Clear all + + ]; + + const notificationDrawerDropdownItems = (key: React.Key) => [ + markNotificationRead(key)}> + Mark as read + , + removeNotification(key)}> + Clear + + ]; + + const notificationDrawer = ( + + setDrawerExpanded(false)}> + onDropdownToggle('dropdown-toggle-id-0', isActive)} + id="dropdown-toggle-id-0" + /> + } + isOpen={openDropdownKey === 'dropdown-toggle-id-0'} + isPlain + dropdownItems={notificationDrawerActions} + id="notification-drawer-0" + position={DropdownPosition.right} + /> + + + {notifications.length !== 0 && ( + + {notifications.map(({ key, variant, title, srTitle, description, timestamp }) => ( + markNotificationRead(key)} + > + + onDropdownToggle(key, isActive)} id={key.toString()} />} + isOpen={openDropdownKey === key} + isPlain + dropdownItems={notificationDrawerDropdownItems(key)} + id={key.toString()} + /> + + {description} + + ))} + + )} + {notifications.length === 0 && ( + + + + No notifications found + + There are currently no notifications. + + )} + + + ); + + return ( + } + mainContainerId="main-content-page-layout-default-nav" + notificationDrawer={notificationDrawer} + isNotificationDrawerExpanded={isDrawerExpanded} + > + + + Alert Group with Notification Drawer demo + + New alerts can be added with buttons below. Each alert has a timeout of 7 seconds, however, even after the + timeout expires, all alerts are still visible in the notification drawer. By default, only 3 alerts are + displayed. The rest can be accessed in the notification drawer after clicking on the bell icon in the header + or by clicking on the overflow message. + + + + + + + + +
+
+ + +
+ + + +
+ Max displayed alerts + The maximum number of displayed alerts can be set below. +
+ +
+ + + {alerts.slice(0, maxDisplayed)} + + +
+ ); +}; diff --git a/packages/react-core/src/demos/examples/DashboardHeader.js b/packages/react-core/src/demos/examples/DashboardHeader.js index 8e7b6921607..9d7d4fae766 100644 --- a/packages/react-core/src/demos/examples/DashboardHeader.js +++ b/packages/react-core/src/demos/examples/DashboardHeader.js @@ -91,6 +91,7 @@ export default class DashboardHeader extends React.Component { render() { const { isDropdownOpen, isKebabDropdownOpen, isAppLauncherOpen } = this.state; + const { notificationBadge } = this.props; const kebabDropdownItems = [ @@ -127,9 +128,18 @@ export default class DashboardHeader extends React.Component { alignment={{ default: 'alignRight' }} spacer={{ default: 'spacerNone', md: 'spacerMd' }} > - -