diff --git a/packages/react-core/src/components/index.ts b/packages/react-core/src/components/index.ts
index 7b0707bfb48..bdd2f2166e6 100644
--- a/packages/react-core/src/components/index.ts
+++ b/packages/react-core/src/components/index.ts
@@ -4,7 +4,6 @@ export * from './Accordion';
export * from './ActionList';
export * from './Alert';
export * from './AlertGroup';
-export * from './ApplicationLauncher';
export * from './Avatar';
export * from './BackToTop';
export * from './Backdrop';
diff --git a/packages/react-core/src/demos/ComposableMenu/examples/ComposableApplicationLauncher.tsx b/packages/react-core/src/demos/ComposableMenu/examples/ComposableApplicationLauncher.tsx
index 763a69a6b37..9003472d57e 100644
--- a/packages/react-core/src/demos/ComposableMenu/examples/ComposableApplicationLauncher.tsx
+++ b/packages/react-core/src/demos/ComposableMenu/examples/ComposableApplicationLauncher.tsx
@@ -50,7 +50,7 @@ export const ComposableApplicationLauncher: React.FunctionComponent = () => {
'li > button:not(:disabled), li > a:not(:disabled), input:not(:disabled)'
);
firstElement && (firstElement as HTMLElement).focus();
- setRefFullOptions(Array.from(menuRef.current.querySelectorAll('li:not(li[role=separator])')));
+ setRefFullOptions(Array.from(menuRef.current.querySelectorAll('li:not(li[role=separator])>*:first-child')));
}
}, 0);
setIsOpen(!isOpen);
diff --git a/packages/react-core/src/demos/Masthead.md b/packages/react-core/src/demos/Masthead.md
index 8e2e1dbe55c..2bf3adef33f 100644
--- a/packages/react-core/src/demos/Masthead.md
+++ b/packages/react-core/src/demos/Masthead.md
@@ -7,6 +7,7 @@ import BarsIcon from '@patternfly/react-icons/dist/esm/icons/bars-icon';
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 ThIcon from '@patternfly/react-icons/dist/esm/icons/th-icon';
import QuestionCircleIcon from '@patternfly/react-icons/dist/esm/icons/question-circle-icon';
import imgAvatar from '@patternfly/react-core/src/components/Avatar/examples/avatarImg.svg';
import {
@@ -16,6 +17,8 @@ DropdownItem as DropdownItemDeprecated,
DropdownToggle,
KebabToggle
} from '@patternfly/react-core/deprecated';
+import pfIcon from './Card/pf-logo-small.svg';
+import { Link } from '@reach/router';
## Demos
diff --git a/packages/react-core/src/demos/examples/DashboardHeader.js b/packages/react-core/src/demos/examples/DashboardHeader.js
index f6a878e47f3..cd9b8142487 100644
--- a/packages/react-core/src/demos/examples/DashboardHeader.js
+++ b/packages/react-core/src/demos/examples/DashboardHeader.js
@@ -1,7 +1,5 @@
import React from 'react';
import {
- ApplicationLauncher,
- ApplicationLauncherItem,
Avatar,
Brand,
Button,
@@ -16,7 +14,7 @@ import {
ToolbarContent,
ToolbarGroup,
ToolbarItem,
- PageToggleButton
+ PageToggleButton,
} from '@patternfly/react-core';
import {
Dropdown as DropdownDeprecated,
@@ -40,7 +38,6 @@ export default class DashboardHeader extends React.Component {
isDropdownOpen: false,
isKebabDropdownOpen: false,
isFullKebabDropdownOpen: false,
- isAppLauncherOpen: false,
activeItem: 0
};
@@ -79,22 +76,10 @@ export default class DashboardHeader extends React.Component {
isFullKebabDropdownOpen: !this.state.isFullKebabDropdownOpen
});
};
-
- this.onAppLauncherToggle = (_event, isAppLauncherOpen) => {
- this.setState({
- isAppLauncherOpen
- });
- };
-
- this.onAppLauncherSelect = () => {
- this.setState({
- isAppLauncherOpen: !this.state.isAppLauncherOpen
- });
- };
}
render() {
- const { isDropdownOpen, isKebabDropdownOpen, isFullKebabDropdownOpen, isAppLauncherOpen } = this.state;
+ const { isDropdownOpen, isKebabDropdownOpen, isFullKebabDropdownOpen } = this.state;
const { notificationBadge } = this.props;
const kebabDropdownItems = [
@@ -133,15 +118,6 @@ export default class DashboardHeader extends React.Component {
];
- const appLauncherItems = [
-
- Application 1 (anchor link)
- ,
- alert('Clicked item 2')}>
- Application 2 (button with onClick)
-
- ];
-
const headerToolbar = (
@@ -163,15 +139,6 @@ export default class DashboardHeader extends React.Component {
)}
-
-
-
} />
diff --git a/packages/react-core/src/demos/examples/Masthead/MastheadWithUtilitiesAndUserDropdownMenu.tsx b/packages/react-core/src/demos/examples/Masthead/MastheadWithUtilitiesAndUserDropdownMenu.tsx
index 81b1748bae5..e7d027723d0 100644
--- a/packages/react-core/src/demos/examples/Masthead/MastheadWithUtilitiesAndUserDropdownMenu.tsx
+++ b/packages/react-core/src/demos/examples/Masthead/MastheadWithUtilitiesAndUserDropdownMenu.tsx
@@ -1,7 +1,5 @@
import React from 'react';
import {
- ApplicationLauncher,
- ApplicationLauncherItem,
Avatar,
Brand,
Breadcrumb,
@@ -18,6 +16,11 @@ import {
MastheadContent,
MastheadMain,
MastheadToggle,
+ Menu,
+ MenuContent,
+ MenuList,
+ MenuItem,
+ MenuToggle,
Nav,
NavItem,
NavList,
@@ -26,13 +29,18 @@ import {
PageSectionVariants,
PageSidebar,
PageToggleButton,
+ Popper,
SkipToContent,
Text,
TextContent,
Toolbar,
ToolbarContent,
ToolbarGroup,
- ToolbarItem
+ ToolbarItem,
+ MenuGroup,
+ MenuInput,
+ SearchInput,
+ Tooltip
} from '@patternfly/react-core';
import {
Dropdown as DropdownDeprecated,
@@ -41,12 +49,15 @@ import {
DropdownToggle,
KebabToggle
} from '@patternfly/react-core/deprecated';
+import { Link } from '@reach/router';
import BarsIcon from '@patternfly/react-icons/dist/esm/icons/bars-icon';
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 ThIcon from '@patternfly/react-icons/dist/esm/icons/th-icon';
import QuestionCircleIcon from '@patternfly/react-icons/dist/esm/icons/question-circle-icon';
import imgAvatar from '@patternfly/react-core/src/components/Avatar/examples/avatarImg.svg';
+import pfIcon from './pf-logo-small.svg';
interface NavOnSelectProps {
groupId: number | string;
@@ -59,9 +70,15 @@ export const MastheadWithUtilitiesAndUserDropdownMenu: React.FunctionComponent =
const [isDropdownOpen, setIsDropdownOpen] = React.useState(false);
const [isKebabDropdownOpen, setIsKebabDropdownOpen] = React.useState(false);
const [isFullKebabDropdownOpen, setIsFullKebabDropdownOpen] = React.useState(false);
- const [isAppLauncherOpen, setIsAppLauncherOpen] = React.useState(false);
const [activeItem, setActiveItem] = React.useState(1);
+ const [isOpen, setIsOpen] = React.useState(false);
+ const [refFullOptions, setRefFullOptions] = React.useState();
+ const [favorites, setFavorites] = React.useState([]);
+ const [filteredIds, setFilteredIds] = React.useState(['*']);
+ const menuRef = React.useRef(null);
+ const toggleRef = React.useRef(null);
+
const onNavSelect = (selectedItem: NavOnSelectProps) => {
typeof selectedItem.itemId === 'number' && setActiveItem(selectedItem.itemId);
};
@@ -90,14 +107,249 @@ export const MastheadWithUtilitiesAndUserDropdownMenu: React.FunctionComponent =
setIsFullKebabDropdownOpen(!isFullKebabDropdownOpen);
};
- const onAppLauncherToggle = (_event: any, isOpen: boolean) => {
- setIsAppLauncherOpen(isOpen);
+ const handleMenuKeys = (event: KeyboardEvent) => {
+ if (!isOpen) {
+ return;
+ }
+ if (menuRef.current?.contains(event.target as Node) || toggleRef.current?.contains(event.target as Node)) {
+ if (event.key === 'Escape') {
+ setIsOpen(!isOpen);
+ toggleRef.current?.focus();
+ }
+ }
+ };
+
+ const handleClickOutside = (event: MouseEvent) => {
+ if (isOpen && !menuRef.current?.contains(event.target as Node)) {
+ setIsOpen(false);
+ }
+ };
+
+ const onToggleClick = (ev: React.MouseEvent) => {
+ ev.stopPropagation(); // Stop handleClickOutside from handling
+ setTimeout(() => {
+ if (menuRef.current) {
+ const firstElement = menuRef.current.querySelector(
+ 'li > button:not(:disabled), li > a:not(:disabled), input:not(:disabled)'
+ );
+ firstElement && (firstElement as HTMLElement).focus();
+ setRefFullOptions(Array.from(menuRef.current.querySelectorAll('li:not(li[role=separator])>*:first-child')));
+ }
+ }, 0);
+ setIsOpen(!isOpen);
+ };
+
+ React.useEffect(() => {
+ window.addEventListener('keydown', handleMenuKeys);
+ window.addEventListener('click', handleClickOutside);
+
+ return () => {
+ window.removeEventListener('keydown', handleMenuKeys);
+ window.removeEventListener('click', handleClickOutside);
+ };
+ }, [isOpen, menuRef]);
+
+
+ const toggle = (
+
+
+
+ );
+
+ const menuItems = [
+
+
+
+
+
+ ,
+ ,
+
+
+
+ }
+ component={(props) => }
+ >
+ @reach/router Link with icon
+
+
+ ,
+ ,
+
+
+
+
+ ];
+
+ const createFavorites = (favIds: string[]) => {
+ const favorites: unknown[] = [];
+
+ menuItems.forEach((item) => {
+ if (item.type === MenuList) {
+ item.props.children.filter((child) => {
+ if (favIds.includes(child.props.itemId)) {
+ favorites.push(child);
+ }
+ });
+ } else if (item.type === MenuGroup) {
+ item.props.children.props.children.filter((child) => {
+ if (favIds.includes(child.props.itemId)) {
+ favorites.push(child);
+ }
+ });
+ } else {
+ if (favIds.includes(item.props.itemId)) {
+ favorites.push(item);
+ }
+ }
+ });
+
+ return favorites;
+ };
+
+ const filterItems = (items: any[], filteredIds: string[]) => {
+ if (filteredIds.length === 1 && filteredIds[0] === '*') {
+ return items;
+ }
+ let keepDivider = false;
+ const filteredCopy = items
+ .map((group) => {
+ if (group.type === MenuGroup) {
+ const filteredGroup = React.cloneElement(group, {
+ children: React.cloneElement(group.props.children, {
+ children: group.props.children.props.children.filter((child) => {
+ if (filteredIds.includes(child.props.itemId)) {
+ return child;
+ }
+ })
+ })
+ });
+ const filteredList = filteredGroup.props.children;
+ if (filteredList.props.children.length > 0) {
+ keepDivider = true;
+ return filteredGroup;
+ } else {
+ keepDivider = false;
+ }
+ } else if (group.type === MenuList) {
+ const filteredGroup = React.cloneElement(group, {
+ children: group.props.children.filter((child) => {
+ if (filteredIds.includes(child.props.itemId)) {
+ return child;
+ }
+ })
+ });
+ if (filteredGroup.props.children.length > 0) {
+ keepDivider = true;
+ return filteredGroup;
+ } else {
+ keepDivider = false;
+ }
+ } else {
+ if ((keepDivider && group.type === Divider) || filteredIds.includes(group.props.itemId)) {
+ return group;
+ }
+ }
+ })
+ .filter((newGroup) => newGroup);
+
+ if (filteredCopy.length > 0) {
+ const lastGroup = filteredCopy.pop();
+ if (lastGroup.type !== Divider) {
+ filteredCopy.push(lastGroup);
+ }
+ }
+
+ return filteredCopy;
};
- const onAppLauncherSelect = () => {
- setIsAppLauncherOpen(!isAppLauncherOpen);
+ const onTextChange = (textValue: string) => {
+ if (textValue === '') {
+ setFilteredIds(['*']);
+ return;
+ }
+
+ const filteredIds =
+ refFullOptions
+ ?.filter((item) => (item as HTMLElement).innerText.toLowerCase().includes(textValue.toString().toLowerCase()))
+ .map((item) => item.id) || [];
+ setFilteredIds(filteredIds);
};
+ const onFavorite = (event: any, itemId: string, actionId: string) => {
+ event.stopPropagation();
+ if (actionId === 'fav') {
+ const isFavorite = favorites.includes(itemId);
+ if (isFavorite) {
+ setFavorites(favorites.filter((fav) => fav !== itemId));
+ } else {
+ setFavorites([...favorites, itemId]);
+ }
+ }
+ };
+
+ const filteredFavorites = filterItems(createFavorites(favorites), filteredIds);
+ const filteredItems = filterItems(menuItems, filteredIds);
+ if (filteredItems.length === 0) {
+ filteredItems.push();
+ }
+
+ const menu = (
+ // eslint-disable-next-line no-console
+
+ );
+
const dashboardBreadcrumb = (
Section home
@@ -144,16 +396,7 @@ export const MastheadWithUtilitiesAndUserDropdownMenu: React.FunctionComponent =
Logout
];
-
- const appLauncherItems = [
-
- Application 1 (anchor link)
- ,
- alert('Clicked item 2')}>
- Application 2 (button with onClick)
-
- ];
-
+
const headerToolbar = (
@@ -167,13 +410,7 @@ export const MastheadWithUtilitiesAndUserDropdownMenu: React.FunctionComponent =
-
+
} />
diff --git a/packages/react-core/src/demos/examples/Page/PageStickySectionBreadcrumb.tsx b/packages/react-core/src/demos/examples/Page/PageStickySectionBreadcrumb.tsx
index e340f0ef342..99bc02b7706 100644
--- a/packages/react-core/src/demos/examples/Page/PageStickySectionBreadcrumb.tsx
+++ b/packages/react-core/src/demos/examples/Page/PageStickySectionBreadcrumb.tsx
@@ -1,7 +1,5 @@
import React from 'react';
import {
- ApplicationLauncher,
- ApplicationLauncherItem,
Avatar,
Brand,
Breadcrumb,
@@ -32,7 +30,7 @@ import {
Toolbar,
ToolbarContent,
ToolbarGroup,
- ToolbarItem
+ ToolbarItem,
} from '@patternfly/react-core';
import {
Dropdown as DropdownDeprecated,
@@ -59,7 +57,6 @@ export const PageStickySectionBreadcrumb: React.FunctionComponent = () => {
const [isDropdownOpen, setIsDropdownOpen] = React.useState(false);
const [isKebabDropdownOpen, setIsKebabDropdownOpen] = React.useState(false);
const [isFullKebabDropdownOpen, setIsFullKebabDropdownOpen] = React.useState(false);
- const [isAppLauncherOpen, setIsAppLauncherOpen] = React.useState(false);
const [activeItem, setActiveItem] = React.useState(1);
const onNavSelect = (selectedItem: NavOnSelectProps) => {
@@ -90,14 +87,6 @@ export const PageStickySectionBreadcrumb: React.FunctionComponent = () => {
setIsFullKebabDropdownOpen(!isFullKebabDropdownOpen);
};
- const onAppLauncherToggle = (_event: any, isOpen: boolean) => {
- setIsAppLauncherOpen(isOpen);
- };
-
- const onAppLauncherSelect = () => {
- setIsAppLauncherOpen(!isAppLauncherOpen);
- };
-
const dashboardBreadcrumb = (
Section home
@@ -145,15 +134,6 @@ export const PageStickySectionBreadcrumb: React.FunctionComponent = () => {
];
- const appLauncherItems = [
-
- Application 1 (anchor link)
- ,
- alert('Clicked item 2')}>
- Application 2 (button with onClick)
-
- ];
-
const headerToolbar = (
@@ -166,15 +146,6 @@ export const PageStickySectionBreadcrumb: React.FunctionComponent = () => {
} />
-
-
-
} />
diff --git a/packages/react-core/src/demos/examples/Page/PageStickySectionGroup.tsx b/packages/react-core/src/demos/examples/Page/PageStickySectionGroup.tsx
index 7b46030dd24..5cbfc6b632e 100644
--- a/packages/react-core/src/demos/examples/Page/PageStickySectionGroup.tsx
+++ b/packages/react-core/src/demos/examples/Page/PageStickySectionGroup.tsx
@@ -1,7 +1,5 @@
import React from 'react';
import {
- ApplicationLauncher,
- ApplicationLauncherItem,
Avatar,
Brand,
Breadcrumb,
@@ -59,7 +57,6 @@ export const PageStickySectionGroup: React.FunctionComponent = () => {
const [isDropdownOpen, setIsDropdownOpen] = React.useState(false);
const [isKebabDropdownOpen, setIsKebabDropdownOpen] = React.useState(false);
const [isFullKebabDropdownOpen, setIsFullKebabDropdownOpen] = React.useState(false);
- const [isAppLauncherOpen, setIsAppLauncherOpen] = React.useState(false);
const [activeItem, setActiveItem] = React.useState(1);
const onNavSelect = (selectedItem: NavOnSelectProps) => {
@@ -90,14 +87,6 @@ export const PageStickySectionGroup: React.FunctionComponent = () => {
setIsFullKebabDropdownOpen(!isFullKebabDropdownOpen);
};
- const onAppLauncherToggle = (_event: any, isOpen: boolean) => {
- setIsAppLauncherOpen(isOpen);
- };
-
- const onAppLauncherSelect = () => {
- setIsAppLauncherOpen(!isAppLauncherOpen);
- };
-
const dashboardBreadcrumb = (
Section home
@@ -145,15 +134,6 @@ export const PageStickySectionGroup: React.FunctionComponent = () => {
];
- const appLauncherItems = [
-
- Application 1 (anchor link)
- ,
- alert('Clicked item 2')}>
- Application 2 (button with onClick)
-
- ];
-
const headerToolbar = (
@@ -166,15 +146,6 @@ export const PageStickySectionGroup: React.FunctionComponent = () => {
} />
-
-
-
} />
diff --git a/packages/react-core/src/demos/examples/Page/PageStickySectionGroupAlternate.tsx b/packages/react-core/src/demos/examples/Page/PageStickySectionGroupAlternate.tsx
index e3b06f5ab1f..4010f60897a 100644
--- a/packages/react-core/src/demos/examples/Page/PageStickySectionGroupAlternate.tsx
+++ b/packages/react-core/src/demos/examples/Page/PageStickySectionGroupAlternate.tsx
@@ -1,7 +1,5 @@
import React from 'react';
import {
- ApplicationLauncher,
- ApplicationLauncherItem,
Avatar,
Brand,
Breadcrumb,
@@ -61,7 +59,6 @@ export const PageStickySectionGroupAlternate: React.FunctionComponent = () => {
const [isDropdownOpen, setIsDropdownOpen] = React.useState(false);
const [isKebabDropdownOpen, setIsKebabDropdownOpen] = React.useState(false);
const [isFullKebabDropdownOpen, setIsFullKebabDropdownOpen] = React.useState(false);
- const [isAppLauncherOpen, setIsAppLauncherOpen] = React.useState(false);
const [activeItem, setActiveItem] = React.useState(1);
const onNavSelect = (selectedItem: NavOnSelectProps) => {
@@ -92,14 +89,6 @@ export const PageStickySectionGroupAlternate: React.FunctionComponent = () => {
setIsFullKebabDropdownOpen(!isFullKebabDropdownOpen);
};
- const onAppLauncherToggle = (_event: any, isOpen: boolean) => {
- setIsAppLauncherOpen(isOpen);
- };
-
- const onAppLauncherSelect = () => {
- setIsAppLauncherOpen(!isAppLauncherOpen);
- };
-
const kebabDropdownItems = [
Settings
@@ -136,15 +125,6 @@ export const PageStickySectionGroupAlternate: React.FunctionComponent = () => {
];
- const appLauncherItems = [
-
- Application 1 (anchor link)
- ,
- alert('Clicked item 2')}>
- Application 2 (button with onClick)
-
- ];
-
const headerToolbar = (
@@ -157,15 +137,6 @@ export const PageStickySectionGroupAlternate: React.FunctionComponent = () => {
} />
-
-
-
} />
diff --git a/packages/react-core/src/demos/examples/Page/PageStickySectionGroupUsingPageHeader.tsx b/packages/react-core/src/demos/examples/Page/PageStickySectionGroupUsingPageHeader.tsx
index 1db39ca902b..1e597a0c168 100644
--- a/packages/react-core/src/demos/examples/Page/PageStickySectionGroupUsingPageHeader.tsx
+++ b/packages/react-core/src/demos/examples/Page/PageStickySectionGroupUsingPageHeader.tsx
@@ -1,7 +1,5 @@
import React from 'react';
import {
- ApplicationLauncher,
- ApplicationLauncherItem,
Avatar,
Brand,
Breadcrumb,
@@ -52,7 +50,6 @@ export const PageStickySectionGroupUsingPageHeader: React.FunctionComponent = ()
const [isDropdownOpen, setIsDropdownOpen] = React.useState(false);
const [isKebabDropdownOpen, setIsKebabDropdownOpen] = React.useState(false);
const [isFullKebabDropdownOpen, setIsFullKebabDropdownOpen] = React.useState(false);
- const [isAppLauncherOpen, setIsAppLauncherOpen] = React.useState(false);
const [activeItem, setActiveItem] = React.useState(1);
const onNavSelect = (selectedItem: NavOnSelectProps) => {
@@ -83,14 +80,6 @@ export const PageStickySectionGroupUsingPageHeader: React.FunctionComponent = ()
setIsFullKebabDropdownOpen(!isFullKebabDropdownOpen);
};
- const onAppLauncherToggle = (_event: any, isOpen: boolean) => {
- setIsAppLauncherOpen(isOpen);
- };
-
- const onAppLauncherSelect = () => {
- setIsAppLauncherOpen(!isAppLauncherOpen);
- };
-
const dashboardBreadcrumb = (
Section home
@@ -138,15 +127,6 @@ export const PageStickySectionGroupUsingPageHeader: React.FunctionComponent = ()
];
- const appLauncherItems = [
-
- Application 1 (anchor link)
- ,
- alert('Clicked item 2')}>
- Application 2 (button with onClick)
-
- ];
-
const pageNav = (