diff --git a/packages/react-core/src/demos/NotificationDrawer/NotificationDrawer.md b/packages/react-core/src/demos/NotificationDrawer/NotificationDrawer.md index 751fc253d8c..0340deeb364 100644 --- a/packages/react-core/src/demos/NotificationDrawer/NotificationDrawer.md +++ b/packages/react-core/src/demos/NotificationDrawer/NotificationDrawer.md @@ -22,1447 +22,14 @@ import pfLogo from '@patternfly/react-core/src/demos/assets/pf-logo.svg'; ### Basic -```js isFullscreen -import React from 'react'; -import { - Avatar, - Brand, - Breadcrumb, - BreadcrumbItem, - Button, - ButtonVariant, - Divider, - Dropdown, - DropdownItem, - DropdownList, - EmptyState, - EmptyStateActions, - EmptyStateBody, - EmptyStateIcon, - EmptyStateHeader, - EmptyStateFooter, - EmptyStateVariant, - MenuToggle, - Nav, - NavItem, - NavList, - NotificationBadge, - NotificationDrawer, - NotificationDrawerBody, - NotificationDrawerHeader, - NotificationDrawerList, - NotificationDrawerListItem, - NotificationDrawerListItemBody, - NotificationDrawerListItemHeader, - Page, - PageSection, - PageSectionVariants, - PageSidebar, - PageSidebarBody, - SkipToContent, - TextContent, - Text, - Title, - PageToggleButton, - Masthead, - MastheadMain, - MastheadToggle, - MastheadContent, - MastheadBrand, - Toolbar, - ToolbarItem, - ToolbarGroup, - ToolbarContent -} from '@patternfly/react-core'; -import BellIcon from '@patternfly/react-icons/dist/esm/icons/bell-icon'; -import CogIcon from '@patternfly/react-icons/dist/esm/icons/cog-icon'; -import HelpIcon from '@patternfly/react-icons/dist/esm/icons/help-icon'; -import QuestionCircleIcon from '@patternfly/react-icons/dist/esm/icons/question-circle-icon'; -import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon'; -import BarsIcon from '@patternfly/react-icons/dist/js/icons/bars-icon'; -import imgAvatar from '@patternfly/react-core/src/components/assets/avatarImg.svg'; -import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon'; -import pfLogo from '@patternfly/react-core/src/demos/assets/pf-logo.svg'; - -class BasicNotificationDrawer extends React.Component { - constructor(props) { - super(props); - this.drawerRef = React.createRef(); - this.state = { - isDropdownOpen: false, - isKebabDropdownOpen: false, - activeItem: 0, - isDrawerExpanded: false, - isUnreadMap: { - 'notification-1': true, - 'notification-2': true - }, - showNotifications: true, - isActionsMenuOpen: {} - }; - this.onDropdownToggle = () => { - this.setState((prevState) => ({ - isDropdownOpen: !prevState.isDropdownOpen - })); - }; - - this.onDropdownSelect = () => { - this.setState({ - isDropdownOpen: false - }); - }; - - this.onKebabDropdownToggle = () => { - this.setState((prevState) => ({ - isKebabDropdownOpen: !prevState.isKebabDropdownOpen - })); - }; - - this.onKebabDropdownSelect = () => { - this.setState({ - isKebabDropdownOpen: false - }); - }; - - this.onNavSelect = (_event, result) => { - this.setState({ - activeItem: result.itemId - }); - }; - - this.onCloseNotificationDrawer = (_event) => { - this.setState((prevState) => { - return { - isDrawerExpanded: !prevState.isDrawerExpanded - }; - }); - }; - - this.onToggle = (id) => { - this.setState((prevState) => ({ - isActionsMenuOpen: { [id]: !prevState.isActionsMenuOpen[id] } - })); - }; +```ts file='./examples/NotificationDrawerBasic.tsx' isFullscreen - this.onSelect = () => { - this.setState({ - isActionsMenuOpen: {} - }); - }; - - this.onListItemClick = (id) => { - this.setState((prevState) => { - if (!prevState.isUnreadMap) return; - prevState.isUnreadMap[id] = false; - return { - isUnreadMap: prevState.isUnreadMap - }; - }); - }; - - this.getNumberUnread = () => { - const { isUnreadMap } = this.state; - if (isUnreadMap === null) return 0; - return Object.keys(isUnreadMap).reduce((count, id) => { - return isUnreadMap[id] ? count + 1 : count; - }, 0); - }; - - this.markAllRead = () => { - this.setState({ - isUnreadMap: null - }); - }; - - this.showNotifications = (showNotifications) => { - this.setState({ - isUnreadMap: null, - showNotifications: showNotifications - }); - }; - - this.focusDrawer = (_event) => { - const firstTabbableItem = this.drawerRef.current.querySelector('a, button'); - firstTabbableItem.focus(); - }; - } - - render() { - const { - isDropdownOpen, - isKebabDropdownOpen, - activeItem, - res, - isDrawerExpanded, - isActionsMenuOpen, - isUnreadMap, - showNotifications - } = this.state; - - const PageNav = ( - - ); - const kebabDropdownItems = ( - <> - - Settings - - - Help - - - ); - const userDropdownItems = ( - <> - My profile - User management - Logout - - ); - const headerToolbar = ( - - - - - - this.onCloseNotificationDrawer(event)} - aria-label="Notifications" - isExpanded={isDrawerExpanded} - > - - - - - - - - - - - - - - - - - - - - ); - - const Header = ( - - - - - - - - - - - - {headerToolbar} - - ); - const Sidebar = ( - - {PageNav} - - ); - const pageId = 'main-content-page-layout-default-nav'; - const PageSkipToContent = Skip to content; - - const PageBreadcrumb = ( - - Section home - Section title - Section title - - Section landing - - - ); - - const drawerContent = 'Panel content'; - - const notificationDrawerActions = ( - <> - - Mark all read - - this.showNotifications(false)}> - Clear all - - this.showNotifications(true)}> - Unclear last - - Settings - - ); - const notificationDrawerDropdownItems = ( - <> - ev.preventDefault()} - > - Link - - Action - - - Disabled Link - - - ); - const notificationDrawer = ( - - - !isOpen && this.setState({ isActionsMenuOpen: {} })} - popperProps={{ position: 'right' }} - toggle={(toggleRef) => ( - this.onToggle('toggle-id-0')} - isExpanded={isActionsMenuOpen['toggle-id-0'] || false} - > - - )} - > - {notificationDrawerActions} - - - - {showNotifications && ( - - this.onListItemClick('notification-1')} - isRead={isUnreadMap === null || !isUnreadMap['notification-1']} - > - - !isOpen && this.setState({ isActionsMenuOpen: {} })} - popperProps={{ position: 'right' }} - toggle={(toggleRef) => ( - this.onToggle('toggle-id-1')} - isExpanded={isActionsMenuOpen['toggle-id-1'] || false} - > - - )} - > - {notificationDrawerDropdownItems} - - - - This is an info notification description. - - - this.onListItemClick('notification-2')} - isRead={isUnreadMap === null || !isUnreadMap['notification-2']} - > - - !isOpen && this.setState({ isActionsMenuOpen: {} })} - popperProps={{ position: 'right' }} - toggle={(toggleRef) => ( - this.onToggle('toggle-id-2')} - isExpanded={isActionsMenuOpen['toggle-id-2'] || false} - > - - )} - > - {notificationDrawerDropdownItems} - - - - This is a danger notification description. This is a long description to show how the title will wrap - if it is long and wraps to multiple lines. - - - this.onListItemClick('notification-3')} - isRead={isUnreadMap === null || !isUnreadMap['notification-3']} - > - - !isOpen && this.setState({ isActionsMenuOpen: {} })} - popperProps={{ position: 'right' }} - toggle={(toggleRef) => ( - this.onToggle('toggle-id-3')} - isExpanded={isActionsMenuOpen['toggle-id-3'] || false} - > - - )} - > - {notificationDrawerDropdownItems} - - - - This is a warning notification description. - - - this.onListItemClick('notification-4')} - isRead={isUnreadMap === null || !isUnreadMap['notification-4']} - > - - !isOpen && this.setState({ isActionsMenuOpen: {} })} - popperProps={{ position: 'right' }} - toggle={(toggleRef) => ( - this.onToggle('toggle-id-4')} - isExpanded={isActionsMenuOpen['toggle-id-4'] || false} - > - - )} - > - {notificationDrawerDropdownItems} - - - - This is a success notification description. - - - - )} - {!showNotifications && ( - - } - /> - - There are currently no alerts. There may be silenced critical alerts however. - - - - - - - - )} - - - ); - - return ( - - this.focusDrawer(event)} - isNotificationDrawerExpanded={isDrawerExpanded} - skipToContent={PageSkipToContent} - breadcrumb={PageBreadcrumb} - mainContainerId={pageId} - > - - - Main title - - Body text should be Overpass Regular at 16px. It should have leading of 24px because
- of its relative line height of 1.5. -
-
-
- - Panel section content - -
-
- ); - } -} ``` ### Grouped When using the NotificationDrawerGroupList and related components, the function that is passed in to the `onNotificationDrawerExpand` prop on the Page component must also ensure the NotificationDrawer component only receives focus when it is initially opened. Otherwise any time a drawer group item is opened the NotificationDrawer component will receive focus, which would be unexpected behavior for users. -```js isFullscreen -import React from 'react'; -import { - Avatar, - Brand, - Breadcrumb, - BreadcrumbItem, - Button, - ButtonVariant, - Divider, - Dropdown, - DropdownItem, - DropdownList, - EmptyState, - EmptyStateActions, - EmptyStateBody, - EmptyStateIcon, - EmptyStateHeader, - EmptyStateFooter, - EmptyStateVariant, - MenuToggle, - Nav, - NavItem, - NavList, - NotificationBadge, - NotificationDrawer, - NotificationDrawerBody, - NotificationDrawerGroup, - NotificationDrawerGroupList, - NotificationDrawerHeader, - NotificationDrawerList, - NotificationDrawerListItem, - NotificationDrawerListItemBody, - NotificationDrawerListItemHeader, - Page, - PageSection, - PageSectionVariants, - PageSidebar, - PageSidebarBody, - SkipToContent, - Title, - TextContent, - Text, - PageToggleButton, - Masthead, - MastheadMain, - MastheadToggle, - MastheadContent, - MastheadBrand, - Toolbar, - ToolbarItem, - ToolbarGroup, - ToolbarContent -} from '@patternfly/react-core'; -import BellIcon from '@patternfly/react-icons/dist/esm/icons/bell-icon'; -import CogIcon from '@patternfly/react-icons/dist/esm/icons/cog-icon'; -import BarsIcon from '@patternfly/react-icons/dist/esm/icons/bars-icon'; -import HelpIcon from '@patternfly/react-icons/dist/esm/icons/help-icon'; -import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon'; -import QuestionCircleIcon from '@patternfly/react-icons/dist/esm/icons/question-circle-icon'; -import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon'; -import imgAvatar from '@patternfly/react-core/src/components/assets/avatarImg.svg'; -import pfLogo from '@patternfly/react-core/src/demos/assets/pf-logo.svg'; - -class GroupedNotificationDrawer extends React.Component { - constructor(props) { - super(props); - this.drawerRef = React.createRef(); - this.state = { - isDropdownOpen: false, - isKebabDropdownOpen: false, - activeItem: 0, - isDrawerExpanded: false, - firstDrawerGroupExpanded: false, - secondDrawerGroupExpanded: true, - thirdDrawerGroupExpanded: false, - isActionsMenuOpen: {}, - showNotifications: true, - isUnreadMap: { - 'group-1': { - 'notification-5': true, - 'notification-6': true - }, - 'group-2': { - 'notification-9': true, - 'notification-10': true - }, - 'group-3': null - } - }; - this.onDropdownToggle = () => { - this.setState((prevState) => ({ - isDropdownOpen: !prevState.isDropdownOpen - })); - }; - - this.onDropdownSelect = () => { - this.setState({ - isDropdownOpen: false - }); - }; - - this.onKebabDropdownToggle = () => { - this.setState((prevState) => ({ - isKebabDropdownOpen: !prevState.isKebabDropdownOpen - })); - }; - - this.onKebabDropdownSelect = () => { - this.setState({ - isKebabDropdownOpen: false - }); - }; - - this.onNavSelect = (_event, result) => { - this.setState({ - activeItem: result.itemId - }); - }; - - this.onCloseNotificationDrawer = (_event) => { - this.setState((prevState) => { - return { - isDrawerExpanded: !prevState.isDrawerExpanded - }; - }); - }; - - this.onToggle = (id) => { - this.setState((prevState) => ({ - isActionsMenuOpen: { [id]: !prevState.isActionsMenuOpen[id] } - })); - }; - - this.onSelect = () => { - this.setState({ - isActionsMenuOpen: {} - }); - }; - - this.onListItemClick = (groupId, id) => { - this.setState((prevState) => { - if (!prevState.isUnreadMap || !prevState.isUnreadMap[groupId]) return; - console.log(prevState.isUnreadMap); - prevState.isUnreadMap[groupId][id] = false; - return { - isUnreadMap: prevState.isUnreadMap - }; - }); - }; - - this.isUnread = (groupId, id) => { - const { isUnreadMap } = this.state; - return isUnreadMap && isUnreadMap[groupId] && isUnreadMap[groupId][id]; - }; - - this.getNumberUnread = (groupId) => { - const { isUnreadMap } = this.state; - if (isUnreadMap === null) return 0; - - if (groupId) { - if (isUnreadMap[groupId] === null) return 0; - - return Object.keys(isUnreadMap[groupId]).reduce((count, id) => { - return isUnreadMap[groupId][id] ? count + 1 : count; - }, 0); - } - - return Object.keys(isUnreadMap).reduce((count, groupId) => { - if (isUnreadMap[groupId] === null) return count; - - return Object.keys(isUnreadMap[groupId]).reduce((groupCount, id) => { - return isUnreadMap[groupId][id] ? groupCount + 1 : groupCount; - }, count); - }, 0); - }; - - this.markAllRead = () => { - this.setState({ - isUnreadMap: null - }); - }; - - this.showNotifications = (showNotifications) => { - this.setState({ - isUnreadMap: null, - showNotifications: showNotifications - }); - }; - - this.toggleFirstDrawer = (event, value) => { - this.setState({ - firstDrawerGroupExpanded: value - }); - }; - - this.toggleSecondDrawer = (event, value) => { - this.setState({ - secondDrawerGroupExpanded: value - }); - }; - - this.toggleThirdDrawer = (event, value) => { - this.setState({ - thirdDrawerGroupExpanded: value - }); - }; - - this.focusDrawer = (_event) => { - // Prevent the NotificationDrawer from receiving focus if a drawer group item is opened - if (!document.activeElement.closest(`.${this.drawerRef.current.className}`)) { - const firstTabbableItem = this.drawerRef.current.querySelector('a, button'); - firstTabbableItem.focus(); - } - }; - } - - render() { - const { - isDropdownOpen, - isKebabDropdownOpen, - activeItem, - res, - isDrawerExpanded, - isActionsMenuOpen, - isUnreadMap, - showNotifications, - firstDrawerGroupExpanded, - secondDrawerGroupExpanded, - thirdDrawerGroupExpanded - } = this.state; - - const PageNav = ( - - ); - const kebabDropdownItems = ( - <> - - Settings - - - Help - - - ); - const userDropdownItems = ( - <> - My profile - User management - Logout - - ); - const headerToolbar = ( - - - - - - this.onCloseNotificationDrawer(event)} - aria-label="Notifications" - isExpanded={isDrawerExpanded} - > - - - - - - - - - - - - - - - - - - - - ); - - const Header = ( - - - - - - - - - - - - {headerToolbar} - - ); - const Sidebar = ( - - {PageNav} - - ); - const pageId = 'main-content-page-layout-default-nav'; - const PageSkipToContent = Skip to content; - - const PageBreadcrumb = ( - - Section home - Section title - Section title - - Section landing - - - ); - - const drawerContent = 'Panel content'; - const notificationDrawerActions = ( - <> - - Mark all read - - this.showNotifications(false)}> - Clear all - - this.showNotifications(true)}> - Unclear last - - Settings - - ); - const notificationDrawerDropdownItems = ( - <> - ev.preventDefault()} - > - Link - - Action - - - Disabled Link - - - ); - - const notificationDrawer = ( - - - !isOpen && this.setState({ isActionsMenuOpen: {} })} - popperProps={{ position: 'right' }} - toggle={(toggleRef) => ( - this.onToggle('toggle-id-0')} - isExpanded={isActionsMenuOpen['toggle-id-0'] || false} - > - - )} - > - {notificationDrawerActions} - - - - {showNotifications && ( - - - - this.onListItemClick('group-1', 'notification-5')} - isRead={!this.isUnread('group-1', 'notification-5')} - > - - !isOpen && this.setState({ isActionsMenuOpen: {} })} - popperProps={{ position: 'right' }} - toggle={(toggleRef) => ( - this.onToggle('toggle-id-5')} - isExpanded={isActionsMenuOpen['toggle-id-5'] || false} - > - - )} - > - {notificationDrawerDropdownItems} - - - - This is an info notification description. - - - this.onListItemClick('group-1', 'notification-6')} - isRead={!this.isUnread('group-1', 'notification-6')} - > - - !isOpen && this.setState({ isActionsMenuOpen: {} })} - popperProps={{ position: 'right' }} - toggle={(toggleRef) => ( - this.onToggle('toggle-id-6')} - isExpanded={isActionsMenuOpen['toggle-id-6'] || false} - > - - )} - > - {notificationDrawerDropdownItems} - - - - This is a danger notification description. This is a long description to show how the title will - wrap if it is long and wraps to multiple lines. - - - this.onListItemClick('group-1', 'notification-7')} - isRead={!this.isUnread('group-1', 'notification-7')} - > - - !isOpen && this.setState({ isActionsMenuOpen: {} })} - popperProps={{ position: 'right' }} - toggle={(toggleRef) => ( - this.onToggle('toggle-id-7')} - isExpanded={isActionsMenuOpen['toggle-id-7'] || false} - > - - )} - > - {notificationDrawerDropdownItems} - - - - This is a warning notification description. - - - this.onListItemClick('group-1', 'notification-8')} - isRead={!this.isUnread('group-1', 'notification-8')} - > - - !isOpen && this.setState({ isActionsMenuOpen: {} })} - popperProps={{ position: 'right' }} - toggle={(toggleRef) => ( - this.onToggle('toggle-id-8')} - isExpanded={isActionsMenuOpen['toggle-id-8'] || false} - > - - )} - > - {notificationDrawerDropdownItems} - - - - This is a success notification description. - - - - - - - this.onListItemClick('group-2', 'notification-9')} - isRead={!this.isUnread('group-2', 'notification-9')} - > - - !isOpen && this.setState({ isActionsMenuOpen: {} })} - popperProps={{ position: 'right' }} - toggle={(toggleRef) => ( - this.onToggle('toggle-id-9')} - isExpanded={isActionsMenuOpen['toggle-id-9'] || false} - > - - )} - > - {notificationDrawerDropdownItems} - - - - This is an info notification description. - - - this.onListItemClick('group-2', 'notification-10')} - isRead={!this.isUnread('group-2', 'notification-10')} - > - - !isOpen && this.setState({ isActionsMenuOpen: {} })} - popperProps={{ position: 'right' }} - toggle={(toggleRef) => ( - this.onToggle('toggle-id-10')} - isExpanded={isActionsMenuOpen['toggle-id-10'] || false} - > - - )} - > - {notificationDrawerDropdownItems} - - - - This is a danger notification description. This is a long description to show how the title will - wrap if it is long and wraps to multiple lines. - - - this.onListItemClick('group-2', 'notification-11')} - isRead={!this.isUnread('group-2', 'notification-11')} - > - - !isOpen && this.setState({ isActionsMenuOpen: {} })} - popperProps={{ position: 'right' }} - toggle={(toggleRef) => ( - this.onToggle('toggle-id-11')} - isExpanded={isActionsMenuOpen['toggle-id-11'] || false} - > - - )} - > - {notificationDrawerDropdownItems} - - - - This is a warning notification description. - - - this.onListItemClick('group-2', 'notification-12')} - isRead={!this.isUnread('group-2', 'notification-12')} - > - - !isOpen && this.setState({ isActionsMenuOpen: {} })} - popperProps={{ position: 'right' }} - toggle={(toggleRef) => ( - this.onToggle('toggle-id-12')} - isExpanded={isActionsMenuOpen['toggle-id-12'] || false} - > - - )} - > - {notificationDrawerDropdownItems} - - - - This is a success notification description. - - - - - - - - } - /> - - There are currently no critical alerts firing. There may be firing alerts of other severities or - silenced critical alerts however. - - - - - - - - - - - )} - {!showNotifications && ( - - } - /> - - There are currently no alerts. There may be silenced critical alerts however. - - - - - - - - )} - - - ); +```ts file='./examples/NotificationDrawerGrouped.tsx' isFullscreen - return ( - - this.focusDrawer(event)} - skipToContent={PageSkipToContent} - breadcrumb={PageBreadcrumb} - mainContainerId={pageId} - > - - - Main title - - Body text should be Overpass Regular at 16px. It should have leading of 24px because
- of its relative line height of 1.5. -
-
-
- - Panel section content - -
-
- ); - } -} ``` diff --git a/packages/react-core/src/demos/NotificationDrawer/examples/NotificationDrawerBasic.tsx b/packages/react-core/src/demos/NotificationDrawer/examples/NotificationDrawerBasic.tsx new file mode 100644 index 00000000000..721e40cdb1f --- /dev/null +++ b/packages/react-core/src/demos/NotificationDrawer/examples/NotificationDrawerBasic.tsx @@ -0,0 +1,557 @@ +import React from 'react'; +import { + Avatar, + Brand, + Breadcrumb, + BreadcrumbItem, + Button, + ButtonVariant, + Divider, + Dropdown, + DropdownItem, + DropdownList, + EmptyState, + EmptyStateActions, + EmptyStateBody, + EmptyStateIcon, + EmptyStateHeader, + EmptyStateFooter, + EmptyStateVariant, + MenuToggle, + Nav, + NavItem, + NavList, + NotificationBadge, + NotificationDrawer, + NotificationDrawerBody, + NotificationDrawerHeader, + NotificationDrawerList, + NotificationDrawerListItem, + NotificationDrawerListItemBody, + NotificationDrawerListItemHeader, + Page, + PageSection, + PageSectionVariants, + PageSidebar, + PageSidebarBody, + SkipToContent, + TextContent, + Text, + PageToggleButton, + Masthead, + MastheadMain, + MastheadToggle, + MastheadContent, + MastheadBrand, + Toolbar, + ToolbarItem, + ToolbarGroup, + ToolbarContent +} from '@patternfly/react-core'; +import BellIcon from '@patternfly/react-icons/dist/esm/icons/bell-icon'; +import CogIcon from '@patternfly/react-icons/dist/esm/icons/cog-icon'; +import HelpIcon from '@patternfly/react-icons/dist/esm/icons/help-icon'; +import QuestionCircleIcon from '@patternfly/react-icons/dist/esm/icons/question-circle-icon'; +import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon'; +import BarsIcon from '@patternfly/react-icons/dist/js/icons/bars-icon'; +import imgAvatar from '@patternfly/react-core/src/components/assets/avatarImg.svg'; +import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon'; +import pfLogo from '@patternfly/react-core/src/demos/assets/pf-logo.svg'; + +export const NotificationDrawerBasic: React.FunctionComponent = () => { + const drawerRef = React.useRef(null); + + const [isDropdownOpen, setIsDropdownOpen] = React.useState(false); + const [isKebabDropdownOpen, setIsKebabDropdownOpen] = React.useState(false); + const [isDrawerExpanded, setIsDrawerExpanded] = React.useState(false); + + interface UnreadMap { + [notificationId: string]: boolean; + } + + const [activeItem, setActiveItem] = React.useState(0); + const [isUnreadMap, setIsUnreadMap] = React.useState({ + 'notification-1': true, + 'notification-2': true, + 'notification-3': false, + 'notification-4': false + }); + + const [shouldShowNotifications, setShouldShowNotifications] = React.useState(true); + + interface ActionsMenu { + [toggleId: string]: boolean; + } + + const [isActionsMenuOpen, setIsActionsMenuOpen] = React.useState({}); + + const onNavSelect = ( + _event: React.FormEvent, + selectedItem: { + groupId: number | string; + itemId: number | string; + to: string; + } + ) => setActiveItem(selectedItem.itemId); + + const onDropdownToggle = () => setIsDropdownOpen((prevState) => !prevState); + const onDropdownSelect = () => setIsDropdownOpen(false); + const onKebabDropdownToggle = () => setIsKebabDropdownOpen((prevState) => !prevState); + const onKebabDropdownSelect = () => setIsKebabDropdownOpen(false); + const onCloseNotificationDrawer = (_event: any) => setIsDrawerExpanded((prevState) => !prevState); + + const onToggle = (id: string) => { + setIsActionsMenuOpen({ [id]: !isActionsMenuOpen[id] }); + }; + + const closeActionsMenu = () => setIsActionsMenuOpen({}); + + const onListItemClick = (id: string) => { + if (!isUnreadMap) { + return; + } + setIsUnreadMap({ ...isUnreadMap, [id]: false }); + }; + + const getNumberUnread: () => number = () => { + if (!isUnreadMap) { + return 0; + } + return Object.values(isUnreadMap).reduce((count, value) => count + (value ? 1 : 0), 0); + }; + + const markAllRead = () => setIsUnreadMap(null); + + const showNotifications = (showNotifications: boolean) => { + setIsUnreadMap(null); + setShouldShowNotifications(showNotifications); + }; + + const focusDrawer = (_event: any) => { + if (drawerRef.current === null) { + return; + } + const firstTabbableItem = drawerRef.current.querySelector('a, button') as + | HTMLAnchorElement + | HTMLButtonElement + | null; + firstTabbableItem?.focus(); + }; + + const PageNav = ( + + ); + const kebabDropdownItems = ( + <> + + Settings + + + Help + + + ); + const userDropdownItems = ( + <> + My profile + User management + Logout + + ); + const headerToolbar = ( + + + + + + onCloseNotificationDrawer(event)} + aria-label="Notifications" + isExpanded={isDrawerExpanded} + > + + + + + + + + + + + + + + + + + + + + ); + + const Header = ( + + + + + + + + + + + + {headerToolbar} + + ); + const Sidebar = ( + + {PageNav} + + ); + const pageId = 'main-content-page-layout-default-nav'; + const PageSkipToContent = Skip to content; + + const PageBreadcrumb = ( + + Section home + Section title + Section title + + Section landing + + + ); + + const notificationDrawerActions = ( + <> + + Mark all read + + showNotifications(false)}> + Clear all + + showNotifications(true)}> + Unclear last + + Settings + + ); + const notificationDrawerDropdownItems = ( + <> + ev.preventDefault()} + > + Link + + Action + + + Disabled Link + + + ); + const notificationDrawer = ( + + + !isOpen && closeActionsMenu()} + popperProps={{ position: 'right' }} + toggle={(toggleRef: React.RefObject) => ( + onToggle('toggle-id-0')} + isExpanded={isActionsMenuOpen['toggle-id-0'] || false} + > + + )} + > + {notificationDrawerActions} + + + + {shouldShowNotifications && ( + + onListItemClick('notification-1')} + isRead={isUnreadMap === null || !isUnreadMap['notification-1']} + > + + !isOpen && closeActionsMenu()} + popperProps={{ position: 'right' }} + toggle={(toggleRef: React.RefObject) => ( + onToggle('toggle-id-1')} + isExpanded={isActionsMenuOpen['toggle-id-1'] || false} + > + + )} + > + {notificationDrawerDropdownItems} + + + + This is an info notification description. + + + onListItemClick('notification-2')} + isRead={isUnreadMap === null || !isUnreadMap['notification-2']} + > + + !isOpen && closeActionsMenu()} + popperProps={{ position: 'right' }} + toggle={(toggleRef: React.RefObject) => ( + onToggle('toggle-id-2')} + isExpanded={isActionsMenuOpen['toggle-id-2'] || false} + > + + )} + > + {notificationDrawerDropdownItems} + + + + This is a danger notification description. This is a long description to show how the title will wrap if + it is long and wraps to multiple lines. + + + onListItemClick('notification-3')} + isRead={isUnreadMap === null || !isUnreadMap['notification-3']} + > + + !isOpen && closeActionsMenu()} + popperProps={{ position: 'right' }} + toggle={(toggleRef: React.RefObject) => ( + onToggle('toggle-id-3')} + isExpanded={isActionsMenuOpen['toggle-id-3'] || false} + > + + )} + > + {notificationDrawerDropdownItems} + + + + This is a warning notification description. + + + onListItemClick('notification-4')} + isRead={isUnreadMap === null || !isUnreadMap['notification-4']} + > + + !isOpen && closeActionsMenu()} + popperProps={{ position: 'right' }} + toggle={(toggleRef: React.RefObject) => ( + onToggle('toggle-id-4')} + isExpanded={isActionsMenuOpen['toggle-id-4'] || false} + > + + )} + > + {notificationDrawerDropdownItems} + + + + This is a success notification description. + + + + )} + {!shouldShowNotifications && ( + + } + /> + + There are currently no alerts. There may be silenced critical alerts however. + + + + + + + + )} + + + ); + + return ( + + | KeyboardEvent | React.TransitionEvent + ) => focusDrawer(event)} + isNotificationDrawerExpanded={isDrawerExpanded} + skipToContent={PageSkipToContent} + breadcrumb={PageBreadcrumb} + mainContainerId={pageId} + > + + + Main title + + Body text should be Overpass Regular at 16px. It should have leading of 24px because
+ of its relative line height of 1.5. +
+
+
+ Panel section content +
+
+ ); +}; diff --git a/packages/react-core/src/demos/NotificationDrawer/examples/NotificationDrawerGrouped.tsx b/packages/react-core/src/demos/NotificationDrawer/examples/NotificationDrawerGrouped.tsx new file mode 100644 index 00000000000..e1bf93e05c9 --- /dev/null +++ b/packages/react-core/src/demos/NotificationDrawer/examples/NotificationDrawerGrouped.tsx @@ -0,0 +1,796 @@ +import React from 'react'; +import { + Avatar, + Brand, + Breadcrumb, + BreadcrumbItem, + Button, + ButtonVariant, + Divider, + Dropdown, + DropdownItem, + DropdownList, + EmptyState, + EmptyStateActions, + EmptyStateBody, + EmptyStateIcon, + EmptyStateHeader, + EmptyStateFooter, + EmptyStateVariant, + MenuToggle, + Nav, + NavItem, + NavList, + NotificationBadge, + NotificationDrawer, + NotificationDrawerBody, + NotificationDrawerGroup, + NotificationDrawerGroupList, + NotificationDrawerHeader, + NotificationDrawerList, + NotificationDrawerListItem, + NotificationDrawerListItemBody, + NotificationDrawerListItemHeader, + Page, + PageSection, + PageSectionVariants, + PageSidebar, + PageSidebarBody, + SkipToContent, + TextContent, + Text, + PageToggleButton, + Masthead, + MastheadMain, + MastheadToggle, + MastheadContent, + MastheadBrand, + Toolbar, + ToolbarItem, + ToolbarGroup, + ToolbarContent +} from '@patternfly/react-core'; +import BellIcon from '@patternfly/react-icons/dist/esm/icons/bell-icon'; +import CogIcon from '@patternfly/react-icons/dist/esm/icons/cog-icon'; +import BarsIcon from '@patternfly/react-icons/dist/esm/icons/bars-icon'; +import HelpIcon from '@patternfly/react-icons/dist/esm/icons/help-icon'; +import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon'; +import QuestionCircleIcon from '@patternfly/react-icons/dist/esm/icons/question-circle-icon'; +import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon'; +import imgAvatar from '@patternfly/react-core/src/components/assets/avatarImg.svg'; +import pfLogo from '@patternfly/react-core/src/demos/assets/pf-logo.svg'; + +export const NotificationDrawerGrouped: React.FunctionComponent = () => { + const drawerRef = React.useRef(null); + + const [isDropdownOpen, setIsDropdownOpen] = React.useState(false); + const [isKebabDropdownOpen, setIsKebabDropdownOpen] = React.useState(false); + const [isDrawerExpanded, setIsDrawerExpanded] = React.useState(false); + const [firstDrawerGroupExpanded, setFirstDrawerGroupExpanded] = React.useState(false); + const [secondDrawerGroupExpanded, setSecondDrawerGroupExpanded] = React.useState(true); + const [thirdDrawerGroupExpanded, setThirdDrawerGroupExpanded] = React.useState(false); + + interface UnreadMap { + [groupName: string]: { + [notificationId: string]: boolean; + } | null; + } + + const [activeItem, setActiveItem] = React.useState(0); + const [isUnreadMap, setIsUnreadMap] = React.useState({ + 'group-1': { + 'notification-5': true, + 'notification-6': true + }, + 'group-2': { + 'notification-9': true, + 'notification-10': true + }, + 'group-3': null + }); + + const [shouldShowNotifications, setShouldShowNotifications] = React.useState(true); + + interface ActionsMenu { + [toggleId: string]: boolean; + } + + const [isActionsMenuOpen, setIsActionsMenuOpen] = React.useState({}); + + const onNavSelect = ( + _event: React.FormEvent, + selectedItem: { + groupId: number | string; + itemId: number | string; + to: string; + } + ) => setActiveItem(selectedItem.itemId); + + const onDropdownToggle = () => setIsDropdownOpen((prevState) => !prevState); + const onDropdownSelect = () => setIsDropdownOpen(false); + const onKebabDropdownToggle = () => setIsKebabDropdownOpen((prevState) => !prevState); + const onKebabDropdownSelect = () => setIsKebabDropdownOpen(false); + const onCloseNotificationDrawer = (_event: any) => setIsDrawerExpanded((prevState) => !prevState); + + const onToggle = (id: string) => { + setIsActionsMenuOpen({ [id]: !isActionsMenuOpen[id] }); + }; + + const closeActionsMenu = () => setIsActionsMenuOpen({}); + + const onListItemClick = (groupId: string, id: string) => { + if (isUnreadMap === null) { + return; + } + if (!isUnreadMap[groupId]) { + setIsUnreadMap({ ...isUnreadMap, [groupId]: { [id]: false } }); + } else { + setIsUnreadMap({ ...isUnreadMap, [groupId]: { ...isUnreadMap[groupId], [id]: false } }); + } + }; + + const isUnread = (groupId: string, id: string) => + isUnreadMap && isUnreadMap[groupId] !== null && isUnreadMap[groupId][id]; + + const getNumberUnread = (groupId: string | null) => { + if (!isUnreadMap) { + return 0; + } + + if (groupId) { + const group = isUnreadMap[groupId]; + if (!group) { + return 0; + } + return Object.values(group).reduce((count, value) => (value ? count + 1 : count), 0); + } + + return Object.keys(isUnreadMap).reduce((count, groupId) => { + const group = isUnreadMap[groupId]; + if (!group) { + return count; + } + + return Object.values(group).reduce((groupCount, value) => (value ? groupCount + 1 : groupCount), count); + }, 0); + }; + + const markAllRead = () => setIsUnreadMap(null); + + const showNotifications = (showNotifications: boolean) => { + setIsUnreadMap(null); + setShouldShowNotifications(showNotifications); + }; + + const toggleFirstDrawer = (_event: any, value: boolean | ((prevState: boolean) => boolean)) => { + setFirstDrawerGroupExpanded(value); + }; + + const toggleSecondDrawer = (_event: any, value: boolean | ((prevState: boolean) => boolean)) => { + setSecondDrawerGroupExpanded(value); + }; + + const toggleThirdDrawer = (_event: any, value: boolean | ((prevState: boolean) => boolean)) => { + setThirdDrawerGroupExpanded(value); + }; + + const focusDrawer = (_event: any) => { + if (drawerRef.current === null) { + return; + } + // Prevent the NotificationDrawer from receiving focus if a drawer group item is opened + if (!document.activeElement?.closest(`.${drawerRef.current.className}`)) { + const firstTabbableItem = drawerRef.current.querySelector('a, button') as + | HTMLAnchorElement + | HTMLButtonElement + | null; + firstTabbableItem?.focus(); + } + }; + + const PageNav = ( + + ); + const kebabDropdownItems = ( + <> + + Settings + + + Help + + + ); + const userDropdownItems = ( + <> + My profile + User management + Logout + + ); + const headerToolbar = ( + + + + + + onCloseNotificationDrawer(event)} + aria-label="Notifications" + isExpanded={isDrawerExpanded} + > + + + + + + + + + + + + + + + + + + + + ); + + const Header = ( + + + + + + + + + + + + {headerToolbar} + + ); + const Sidebar = ( + + {PageNav} + + ); + const pageId = 'main-content-page-layout-default-nav'; + const PageSkipToContent = Skip to content; + + const PageBreadcrumb = ( + + Section home + Section title + Section title + + Section landing + + + ); + + const notificationDrawerActions = ( + <> + + Mark all read + + showNotifications(false)}> + Clear all + + showNotifications(true)}> + Unclear last + + Settings + + ); + const notificationDrawerDropdownItems = ( + <> + ev.preventDefault()} + > + Link + + Action + + + Disabled Link + + + ); + + const notificationDrawer = ( + + + !isOpen && closeActionsMenu()} + popperProps={{ position: 'right' }} + toggle={(toggleRef: React.RefObject) => ( + onToggle('toggle-id-0')} + isExpanded={isActionsMenuOpen['toggle-id-0'] || false} + > + + )} + > + {notificationDrawerActions} + + + + {shouldShowNotifications && ( + + + + onListItemClick('group-1', 'notification-5')} + isRead={!isUnread('group-1', 'notification-5')} + > + + !isOpen && closeActionsMenu()} + popperProps={{ position: 'right' }} + toggle={(toggleRef: React.RefObject) => ( + onToggle('toggle-id-5')} + isExpanded={isActionsMenuOpen['toggle-id-5'] || false} + > + + )} + > + {notificationDrawerDropdownItems} + + + + This is an info notification description. + + + onListItemClick('group-1', 'notification-6')} + isRead={!isUnread('group-1', 'notification-6')} + > + + !isOpen && closeActionsMenu()} + popperProps={{ position: 'right' }} + toggle={(toggleRef: React.RefObject) => ( + onToggle('toggle-id-6')} + isExpanded={isActionsMenuOpen['toggle-id-6'] || false} + > + + )} + > + {notificationDrawerDropdownItems} + + + + This is a danger notification description. This is a long description to show how the title will + wrap if it is long and wraps to multiple lines. + + + onListItemClick('group-1', 'notification-7')} + isRead={!isUnread('group-1', 'notification-7')} + > + + !isOpen && closeActionsMenu()} + popperProps={{ position: 'right' }} + toggle={(toggleRef: React.RefObject) => ( + onToggle('toggle-id-7')} + isExpanded={isActionsMenuOpen['toggle-id-7'] || false} + > + + )} + > + {notificationDrawerDropdownItems} + + + + This is a warning notification description. + + + onListItemClick('group-1', 'notification-8')} + isRead={!isUnread('group-1', 'notification-8')} + > + + !isOpen && closeActionsMenu()} + popperProps={{ position: 'right' }} + toggle={(toggleRef: React.RefObject) => ( + onToggle('toggle-id-8')} + isExpanded={isActionsMenuOpen['toggle-id-8'] || false} + > + + )} + > + {notificationDrawerDropdownItems} + + + + This is a success notification description. + + + + + + + onListItemClick('group-2', 'notification-9')} + isRead={!isUnread('group-2', 'notification-9')} + > + + !isOpen && closeActionsMenu()} + popperProps={{ position: 'right' }} + toggle={(toggleRef: React.RefObject) => ( + onToggle('toggle-id-9')} + isExpanded={isActionsMenuOpen['toggle-id-9'] || false} + > + + )} + > + {notificationDrawerDropdownItems} + + + + This is an info notification description. + + + onListItemClick('group-2', 'notification-10')} + isRead={!isUnread('group-2', 'notification-10')} + > + + !isOpen && closeActionsMenu()} + popperProps={{ position: 'right' }} + toggle={(toggleRef: React.RefObject) => ( + onToggle('toggle-id-10')} + isExpanded={isActionsMenuOpen['toggle-id-10'] || false} + > + + )} + > + {notificationDrawerDropdownItems} + + + + This is a danger notification description. This is a long description to show how the title will + wrap if it is long and wraps to multiple lines. + + + onListItemClick('group-2', 'notification-11')} + isRead={!isUnread('group-2', 'notification-11')} + > + + !isOpen && closeActionsMenu()} + popperProps={{ position: 'right' }} + toggle={(toggleRef: React.RefObject) => ( + onToggle('toggle-id-11')} + isExpanded={isActionsMenuOpen['toggle-id-11'] || false} + > + + )} + > + {notificationDrawerDropdownItems} + + + + This is a warning notification description. + + + onListItemClick('group-2', 'notification-12')} + isRead={!isUnread('group-2', 'notification-12')} + > + + !isOpen && closeActionsMenu()} + popperProps={{ position: 'right' }} + toggle={(toggleRef: React.RefObject) => ( + onToggle('toggle-id-12')} + isExpanded={isActionsMenuOpen['toggle-id-12'] || false} + > + + )} + > + {notificationDrawerDropdownItems} + + + + This is a success notification description. + + + + + + + + } + /> + + There are currently no critical alerts firing. There may be firing alerts of other severities or + silenced critical alerts however. + + + + + + + + + + + )} + {!shouldShowNotifications && ( + + } + /> + + There are currently no alerts. There may be silenced critical alerts however. + + + + + + + + )} + + + ); + + return ( + + | KeyboardEvent | React.TransitionEvent + ) => focusDrawer(event)} + skipToContent={PageSkipToContent} + breadcrumb={PageBreadcrumb} + mainContainerId={pageId} + > + + + Main title + + Body text should be Overpass Regular at 16px. It should have leading of 24px because
+ of its relative line height of 1.5. +
+
+
+ Panel section content +
+
+ ); +};