From bb333f379d238b0ad58165ac39ac033a90e9cdd1 Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Mon, 1 May 2023 10:03:25 -0400 Subject: [PATCH 01/12] feat(menus): custom menus updates --- .../src/components/Dropdown/Dropdown.tsx | 10 +- .../src/components/Menu/MenuContainer.tsx | 100 +++++++ .../src/components/Menu/examples/Menu.md | 13 +- .../react-core/src/components/Menu/index.ts | 1 + .../ComposableMenu/ApplicationLauncher.md | 15 ++ .../demos/ComposableMenu/ContextSelector.md | 14 + .../{ComposableMenu.md => CustomMenus.md} | 70 ++--- .../src/demos/ComposableMenu/OptionsMenu.md | 14 + ...bleActionsMenu.tsx => ActionsMenuDemo.tsx} | 82 +++--- ...uncher.tsx => ApplicationLauncherDemo.tsx} | 170 +++++------- .../ComposableMultipleTypeaheadSelect.tsx | 253 ------------------ .../ComposableOptionsMenuVariants.tsx | 96 ------- .../ComposableSimpleCheckboxSelect.tsx | 101 ------- .../examples/ComposableSimpleDropdown.tsx | 70 ----- .../examples/ComposableSimpleSelect.tsx | 83 ------ .../examples/ComposableTypeaheadSelect.tsx | 234 ---------------- ...xtSelector.tsx => ContextSelectorDemo.tsx} | 110 +++----- .../examples/DateSelectDemo.tsx | 88 ++++++ ...rilldownMenu.tsx => DrilldownMenuDemo.tsx} | 41 +-- .../ComposableMenu/examples/FavoritesDemo.tsx | 127 +++++++++ .../{ComposableFlyout.tsx => FlyoutDemo.tsx} | 44 +-- .../examples/OptionsMenuDemo.tsx | 68 +++++ ...eTreeViewMenu.tsx => TreeViewMenuDemo.tsx} | 58 +--- 23 files changed, 648 insertions(+), 1214 deletions(-) create mode 100644 packages/react-core/src/components/Menu/MenuContainer.tsx create mode 100644 packages/react-core/src/demos/ComposableMenu/ApplicationLauncher.md create mode 100644 packages/react-core/src/demos/ComposableMenu/ContextSelector.md rename packages/react-core/src/demos/ComposableMenu/{ComposableMenu.md => CustomMenus.md} (56%) create mode 100644 packages/react-core/src/demos/ComposableMenu/OptionsMenu.md rename packages/react-core/src/demos/ComposableMenu/examples/{ComposableActionsMenu.tsx => ActionsMenuDemo.tsx} (62%) rename packages/react-core/src/demos/ComposableMenu/examples/{ComposableApplicationLauncher.tsx => ApplicationLauncherDemo.tsx} (63%) delete mode 100644 packages/react-core/src/demos/ComposableMenu/examples/ComposableMultipleTypeaheadSelect.tsx delete mode 100644 packages/react-core/src/demos/ComposableMenu/examples/ComposableOptionsMenuVariants.tsx delete mode 100644 packages/react-core/src/demos/ComposableMenu/examples/ComposableSimpleCheckboxSelect.tsx delete mode 100644 packages/react-core/src/demos/ComposableMenu/examples/ComposableSimpleDropdown.tsx delete mode 100644 packages/react-core/src/demos/ComposableMenu/examples/ComposableSimpleSelect.tsx delete mode 100644 packages/react-core/src/demos/ComposableMenu/examples/ComposableTypeaheadSelect.tsx rename packages/react-core/src/demos/ComposableMenu/examples/{ComposableContextSelector.tsx => ContextSelectorDemo.tsx} (63%) create mode 100644 packages/react-core/src/demos/ComposableMenu/examples/DateSelectDemo.tsx rename packages/react-core/src/demos/ComposableMenu/examples/{ComposableDrilldownMenu.tsx => DrilldownMenuDemo.tsx} (89%) create mode 100644 packages/react-core/src/demos/ComposableMenu/examples/FavoritesDemo.tsx rename packages/react-core/src/demos/ComposableMenu/examples/{ComposableFlyout.tsx => FlyoutDemo.tsx} (67%) create mode 100644 packages/react-core/src/demos/ComposableMenu/examples/OptionsMenuDemo.tsx rename packages/react-core/src/demos/ComposableMenu/examples/{ComposableTreeViewMenu.tsx => TreeViewMenuDemo.tsx} (78%) diff --git a/packages/react-core/src/components/Dropdown/Dropdown.tsx b/packages/react-core/src/components/Dropdown/Dropdown.tsx index f3d2b3b4d4a..882decd404d 100644 --- a/packages/react-core/src/components/Dropdown/Dropdown.tsx +++ b/packages/react-core/src/components/Dropdown/Dropdown.tsx @@ -43,7 +43,7 @@ export interface DropdownProps extends MenuProps, OUIAProps { /** Function callback called when user selects item. */ onSelect?: (event?: React.MouseEvent, itemId?: string | number) => void; /** Callback to allow the dropdown component to change the open state of the menu. - * Triggered by clicking outside of the menu, or by pressing either tab or escape. */ + * Triggered by clicking outside of the menu, or by pressing either tab or escape (or specificed in onOpenChangeKeys). */ onOpenChange?: (isOpen: boolean) => void; /** Indicates if the menu should be without the outer box-shadow. */ isPlain?: boolean; @@ -59,6 +59,8 @@ export interface DropdownProps extends MenuProps, OUIAProps { zIndex?: number; /** Additional properties to pass to the Popper */ popperProps?: DropdownPopperProps; + /** Keys that trigger onOpenChange, defaults to tab and escape. */ + onOpenChangeKeys?: string[]; } const DropdownBase: React.FunctionComponent = ({ @@ -76,6 +78,7 @@ const DropdownBase: React.FunctionComponent = ({ ouiaSafe = true, zIndex = 9999, popperProps, + onOpenChangeKeys = ['Escape', 'Tab'], ...props }: DropdownProps) => { const localMenuRef = React.useRef(); @@ -96,8 +99,7 @@ const DropdownBase: React.FunctionComponent = ({ onOpenChange && (menuRef.current?.contains(event.target as Node) || toggleRef.current?.contains(event.target as Node)) ) { - if (event.key === 'Escape' || event.key === 'Tab') { - event.preventDefault(); + if (onOpenChangeKeys.includes(event.key)) { onOpenChange(false); toggleRef.current?.focus(); } @@ -130,7 +132,7 @@ const DropdownBase: React.FunctionComponent = ({ window.removeEventListener('keydown', handleMenuKeys); window.removeEventListener('click', handleClick); }; - }, [isOpen, menuRef, toggleRef, onOpenChange]); + }, [isOpen, menuRef, toggleRef, onOpenChange, onOpenChangeKeys]); const menu = ( >; + /** Reference to the menu */ + menuRef: React.RefObject; + /** Toggle to be rendered */ + toggle: React.ReactNode; + /** Reference to the toggle */ + toggleRef: React.RefObject; + /** Flag to indicate if menu is opened.*/ + isOpen: boolean; + /** Callback to change the open state of the menu. + * Triggered by clicking outside of the menu, or by pressing either tab or escape (or keys specified in onOpenChangeKeys). */ + onOpenChange?: (isOpen: boolean) => void; + /** Keys that trigger onOpenChange, defaults to tab and escape. */ + onOpenChangeKeys?: string[]; + /** z-index of the dropdown menu */ + zIndex?: number; + /** Additional properties to pass to the Popper */ + popperProps?: Partial; +} + +/** + * Container that links a menu and menu toggle together, to handle basic keyboard input and control the opening and closing of a menu + */ +export const MenuContainer: React.FunctionComponent = ({ + menu, + menuRef, + isOpen, + toggle, + toggleRef, + onOpenChange, + zIndex = 9999, + popperProps, + onOpenChangeKeys = ['Escape', 'Tab'], + ...props +}: MenuContainerProps) => { + const containerRef = React.useRef(); + + React.useEffect(() => { + const handleMenuKeys = (event: KeyboardEvent) => { + // Close the menu on tab or escape if onOpenChange is provided + if ( + (isOpen && onOpenChange && menuRef.current?.contains(event.target as Node)) || + toggleRef.current?.contains(event.target as Node) + ) { + if (onOpenChangeKeys.includes(event.key)) { + onOpenChange(false); + toggleRef.current?.focus(); + } + } + }; + + const handleClick = (event: MouseEvent) => { + // toggle was clicked open via keyboard, focus on first menu item + if (isOpen && toggleRef.current?.contains(event.target as Node) && event.detail === 0) { + setTimeout(() => { + const firstElement = menuRef?.current?.querySelector( + 'li button:not(:disabled),li input:not(:disabled),li a:not([aria-disabled="true"])' + ); + firstElement && (firstElement as HTMLElement).focus(); + }, 0); + } + + // If the event is not on the toggle and onOpenChange callback is provided, close the menu + if (isOpen && onOpenChange && !toggleRef?.current?.contains(event.target as Node)) { + if (isOpen && !menuRef.current?.contains(event.target as Node)) { + onOpenChange(false); + } + } + }; + + window.addEventListener('keydown', handleMenuKeys); + window.addEventListener('click', handleClick); + + return () => { + window.removeEventListener('keydown', handleMenuKeys); + window.removeEventListener('click', handleClick); + }; + }, [isOpen, menuRef, onOpenChange, onOpenChangeKeys, toggleRef]); + + return ( +
+ +
+ ); +}; +MenuContainer.displayName = 'MenuContainer'; diff --git a/packages/react-core/src/components/Menu/examples/Menu.md b/packages/react-core/src/components/Menu/examples/Menu.md index 2686b81a06c..f9c7579dd1f 100644 --- a/packages/react-core/src/components/Menu/examples/Menu.md +++ b/packages/react-core/src/components/Menu/examples/Menu.md @@ -3,7 +3,18 @@ id: Menu section: components subsection: menus cssPrefix: pf-c-menu -propComponents: ['Menu', 'MenuList', 'MenuItem', 'MenuItemAction', 'MenuContent', 'MenuSearch', 'MenuSearchInput', 'MenuGroup'] +propComponents: + [ + 'Menu', + 'MenuList', + 'MenuItem', + 'MenuItemAction', + 'MenuContent', + 'MenuSearch', + 'MenuSearchInput', + 'MenuGroup', + 'MenuContainer' + ] ouia: true --- diff --git a/packages/react-core/src/components/Menu/index.ts b/packages/react-core/src/components/Menu/index.ts index f2958c9e1b1..ca9649ff577 100644 --- a/packages/react-core/src/components/Menu/index.ts +++ b/packages/react-core/src/components/Menu/index.ts @@ -9,3 +9,4 @@ export * from './MenuList'; export * from './MenuItemAction'; export * from './DrilldownMenu'; export * from './MenuBreadcrumb'; +export * from './MenuContainer'; diff --git a/packages/react-core/src/demos/ComposableMenu/ApplicationLauncher.md b/packages/react-core/src/demos/ComposableMenu/ApplicationLauncher.md new file mode 100644 index 00000000000..d26cda421e0 --- /dev/null +++ b/packages/react-core/src/demos/ComposableMenu/ApplicationLauncher.md @@ -0,0 +1,15 @@ +--- +id: Application launcher +section: components +subsection: menus +source: react-demos +--- + +import ThIcon from '@patternfly/react-icons/dist/esm/icons/th-icon'; +import pfIcon from './examples/pf-logo-small.svg'; + +### Application launcher + +```ts file="./examples/ApplicationLauncherDemo.tsx" + +``` diff --git a/packages/react-core/src/demos/ComposableMenu/ContextSelector.md b/packages/react-core/src/demos/ComposableMenu/ContextSelector.md new file mode 100644 index 00000000000..0ed22193fd6 --- /dev/null +++ b/packages/react-core/src/demos/ComposableMenu/ContextSelector.md @@ -0,0 +1,14 @@ +--- +id: Context selector +section: components +subsection: menus +source: react-demos +--- + +import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon'; + +### Context selector + +```ts file="./examples/ContextSelectorDemo.tsx" + +``` diff --git a/packages/react-core/src/demos/ComposableMenu/ComposableMenu.md b/packages/react-core/src/demos/ComposableMenu/CustomMenus.md similarity index 56% rename from packages/react-core/src/demos/ComposableMenu/ComposableMenu.md rename to packages/react-core/src/demos/ComposableMenu/CustomMenus.md index b221de57461..3d7f774faae 100644 --- a/packages/react-core/src/demos/ComposableMenu/ComposableMenu.md +++ b/packages/react-core/src/demos/ComposableMenu/CustomMenus.md @@ -27,92 +27,56 @@ import styles from '@patternfly/react-styles/css/components/Menu/menu'; ## Demos -Custom menus can be constructed using a composable approach by combining the [Menu](/components/menus/menu) and [Menu toggle](/components/menus/menu-toggle) components in unique ways. Composable menus currently require consumer keyboard handling and use of our undocumented [popper.js](https://popper.js.org/) wrapper component called Popper. We understand this is inconvientent boilerplate and these examples will be updated to use [Dropdown](/components/dropdown) in a future release. +Custom menus can be constructed using a composable approach by combining the [Menu](/components/menus/menu) and [Menu toggle](/components/menus/menu-toggle) components in unique ways. [Dropdown](/components/menus/dropdown), [Select](/components/menus/select), or [MenuContainer](/components/menus/menu#menucontainer) may be used in combination with menu components to handle basic keyboard inputs, or menu components may be connected manually through our undocumented internal [popper.js](https://popper.js.org/) wrapper component called Popper. -### Composable simple dropdown +### Actions menu -```ts file="./examples/ComposableSimpleDropdown.tsx" +```ts file="./examples/ActionsMenuDemo.tsx" ``` -### Composable actions menu +### Favorites menu -```ts file="./examples/ComposableActionsMenu.tsx" +```ts file="./examples/FavoritesDemo.tsx" ``` -### Composable simple select +### Drilldown menu -```ts file="./examples/ComposableSimpleSelect.tsx" +```ts isBeta file="./examples/DrilldownMenuDemo.tsx" ``` -### Composable simple checkbox select - -```ts file="./examples/ComposableSimpleCheckboxSelect.tsx" - -``` - -### Composable typeahead select - -```ts file="./examples/ComposableTypeaheadSelect.tsx" - -``` - -### Composable multiple typeahead select - -```ts file="./examples/ComposableMultipleTypeaheadSelect.tsx" - -``` - -### Composable drilldown menu - -```ts isBeta file="./examples/ComposableDrilldownMenu.tsx" - -``` - -### Composable tree view menu +### Tree view menu When rendering a menu-like element that does not contain MenuItem components, [Panel](/components/panel) allows more flexible control and customization. -```ts file="./examples/ComposableTreeViewMenu.tsx" +```ts file="./examples/TreeViewMenuDemo.tsx" ``` -### Composable flyout +### Flyout menu The flyout will automatically position to the left or top if it would otherwise go outside the window. The menu must be placed in a container outside the main content like Popper, [Popover](/components/popover) or [Tooltip](/components/tooltip) since it may go over the side nav. -```ts isBeta file="./examples/ComposableFlyout.tsx" - -``` - -### Composable application launcher - -```ts file="./examples/ComposableApplicationLauncher.tsx" - -``` - -### Composable context selector - -```ts file="./examples/ComposableContextSelector.tsx" +```ts isBeta file="./examples/FlyoutDemo.tsx" ``` -### Composable options menu variants +### Application launcher -```ts file="./examples/ComposableOptionsMenuVariants.tsx" +```ts file="./examples/ApplicationLauncherDemo.tsx" ``` -### Composable dropdown variants +### Context selector -```ts file="./examples/ComposableDropdwnVariants.tsx" +```ts file="./examples/ContextSelectorDemo.tsx" ``` -### Composable date select +### Date select -```ts file="./examples/ComposableDateSelect.tsx" +```ts file="./examples/DateSelectDemo.tsx" ``` diff --git a/packages/react-core/src/demos/ComposableMenu/OptionsMenu.md b/packages/react-core/src/demos/ComposableMenu/OptionsMenu.md new file mode 100644 index 00000000000..c85fa1c8c85 --- /dev/null +++ b/packages/react-core/src/demos/ComposableMenu/OptionsMenu.md @@ -0,0 +1,14 @@ +--- +id: Options menu +section: components +subsection: menus +source: react-demos +--- + +import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon'; + +### Options menu + +```ts file="./examples/OptionsMenuDemo.tsx" + +``` diff --git a/packages/react-core/src/demos/ComposableMenu/examples/ComposableActionsMenu.tsx b/packages/react-core/src/demos/ComposableMenu/examples/ActionsMenuDemo.tsx similarity index 62% rename from packages/react-core/src/demos/ComposableMenu/examples/ComposableActionsMenu.tsx rename to packages/react-core/src/demos/ComposableMenu/examples/ActionsMenuDemo.tsx index f942fc467f1..8f8e32c26d3 100644 --- a/packages/react-core/src/demos/ComposableMenu/examples/ComposableActionsMenu.tsx +++ b/packages/react-core/src/demos/ComposableMenu/examples/ActionsMenuDemo.tsx @@ -1,40 +1,22 @@ import React from 'react'; -import { MenuToggle, Menu, MenuList, MenuItem, MenuGroup, MenuItemAction, Popper } from '@patternfly/react-core'; +import { + MenuToggle, + MenuItemAction, + Dropdown, + DropdownGroup, + DropdownList, + DropdownItem +} from '@patternfly/react-core'; import BarsIcon from '@patternfly/react-icons/dist/esm/icons/bars-icon'; import ClipboardIcon from '@patternfly/react-icons/dist/esm/icons/clipboard-icon'; import CodeBranchIcon from '@patternfly/react-icons/dist/esm/icons/code-branch-icon'; import BellIcon from '@patternfly/react-icons/dist/esm/icons/bell-icon'; -export const ComposableActionsMenu: React.FunctionComponent = () => { +export const ActionsMenuDemo: React.FunctionComponent = () => { const [isOpen, setIsOpen] = React.useState(false); const [selectedItems, setSelectedItems] = React.useState([]); - const toggleRef = React.useRef(null); const menuRef = React.useRef(null); - const handleMenuKeys = (event: KeyboardEvent) => { - if (isOpen && menuRef.current?.contains(event.target as Node)) { - if (event.key === 'Escape' || event.key === 'Tab') { - setIsOpen(!isOpen); - toggleRef.current?.focus(); - } - } - }; - - const handleClickOutside = (event: MouseEvent) => { - if (isOpen && !menuRef.current?.contains(event.target as Node)) { - setIsOpen(false); - } - }; - - React.useEffect(() => { - window.addEventListener('keydown', handleMenuKeys); - window.addEventListener('click', handleClickOutside); - return () => { - window.removeEventListener('keydown', handleMenuKeys); - window.removeEventListener('click', handleClickOutside); - }; - }, [isOpen, menuRef]); - const onSelect = (event: React.MouseEvent | undefined, itemId: string | number | undefined) => { if (typeof itemId === 'string' || typeof itemId === 'undefined') { return; @@ -48,7 +30,7 @@ export const ComposableActionsMenu: React.FunctionComponent = () => { }; const onToggleClick = (ev: React.MouseEvent) => { - ev.stopPropagation(); // Stop handleClickOutside from handling + ev.stopPropagation(); setTimeout(() => { if (menuRef.current) { const firstElement = menuRef.current.querySelector('li > button:not(:disabled), li > a:not(:disabled)'); @@ -58,21 +40,23 @@ export const ComposableActionsMenu: React.FunctionComponent = () => { setIsOpen(!isOpen); }; - const toggle = ( - - {isOpen ? 'Expanded' : 'Collapsed'} - - ); - const menu = ( - ( + + {isOpen ? 'Expanded' : 'Collapsed'} + + )} // eslint-disable-next-line no-console onActionClick={(event, itemId, actionId) => console.log(`clicked on ${itemId} - ${actionId}`)} onSelect={onSelect} + onOpenChange={(isOpen) => setIsOpen(isOpen)} > - - - + + { itemId={0} > Item 1 - - + } actionId="alert" aria-label="Alert" />} @@ -96,26 +80,24 @@ export const ComposableActionsMenu: React.FunctionComponent = () => { itemId={1} > Item 2 - - + } actionId="copy" aria-label="Copy" />} itemId={2} > Item 3 - - + } actionId="expand" aria-label="Expand" />} description="This is a description" itemId={3} > Item 4 - - - - + + + + ); - - return ; }; diff --git a/packages/react-core/src/demos/ComposableMenu/examples/ComposableApplicationLauncher.tsx b/packages/react-core/src/demos/ComposableMenu/examples/ApplicationLauncherDemo.tsx similarity index 63% rename from packages/react-core/src/demos/ComposableMenu/examples/ComposableApplicationLauncher.tsx rename to packages/react-core/src/demos/ComposableMenu/examples/ApplicationLauncherDemo.tsx index cf43bb18172..c443eee4f3b 100644 --- a/packages/react-core/src/demos/ComposableMenu/examples/ComposableApplicationLauncher.tsx +++ b/packages/react-core/src/demos/ComposableMenu/examples/ApplicationLauncherDemo.tsx @@ -1,50 +1,30 @@ import React from 'react'; import { MenuToggle, - Menu, - MenuContent, - MenuList, - MenuItem, - MenuGroup, MenuSearch, MenuSearchInput, - Popper, Tooltip, Divider, - SearchInput + SearchInput, + Dropdown, + DropdownGroup, + DropdownList, + DropdownItem } from '@patternfly/react-core'; -import { Link } from '@reach/router'; import ThIcon from '@patternfly/react-icons/dist/js/icons/th-icon'; import pfIcon from 'pf-logo-small.svg'; -export const ComposableApplicationLauncher: React.FunctionComponent = () => { +const MockLink: React.FunctionComponent = ({ to, ...props }: any) => ; + +export const ApplicationLauncherDemo: React.FunctionComponent = () => { 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 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 + ev.stopPropagation(); setTimeout(() => { if (menuRef.current) { const firstElement = menuRef.current.querySelector( @@ -57,36 +37,13 @@ export const ComposableApplicationLauncher: React.FunctionComponent = () => { 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 = [ - - - + + + Application 1 - - + { onClick={(ev) => ev.preventDefault()} > Application 2 - - - , + + + , , - - - + + } + component={(props) => } > - @reach/router Link - - + } - component={(props) => } + component={(props) => } > - @reach/router Link with icon - - - , + Custom component with icon + + + , , - - + + Launch Application 3} position="right"> Application 3 with tooltip - - + + Unavailable Application - - + + ]; const createFavorites = (favIds: string[]) => { const favorites: unknown[] = []; menuItems.forEach((item) => { - if (item.type === MenuList) { + if (item.type === DropdownList) { item.props.children.filter((child) => { if (favIds.includes(child.props.itemId)) { favorites.push(child); } }); - } else if (item.type === MenuGroup) { + } else if (item.type === DropdownGroup) { item.props.children.props.children.filter((child) => { if (favIds.includes(child.props.itemId)) { favorites.push(child); @@ -166,7 +123,7 @@ export const ComposableApplicationLauncher: React.FunctionComponent = () => { let keepDivider = false; const filteredCopy = items .map((group) => { - if (group.type === MenuGroup) { + if (group.type === DropdownGroup) { const filteredGroup = React.cloneElement(group, { children: React.cloneElement(group.props.children, { children: group.props.children.props.children.filter((child) => { @@ -183,7 +140,7 @@ export const ComposableApplicationLauncher: React.FunctionComponent = () => { } else { keepDivider = false; } - } else if (group.type === MenuList) { + } else if (group.type === DropdownList) { const filteredGroup = React.cloneElement(group, { children: group.props.children.filter((child) => { if (filteredIds.includes(child.props.itemId)) { @@ -243,30 +200,47 @@ export const ComposableApplicationLauncher: React.FunctionComponent = () => { const filteredFavorites = filterItems(createFavorites(favorites), filteredIds); const filteredItems = filterItems(menuItems, filteredIds); if (filteredItems.length === 0) { - filteredItems.push(No results found); + filteredItems.push(No results found); } - const menu = ( + return ( // eslint-disable-next-line no-console - console.log('selected', itemId)}> + setIsOpen(isOpen)} + onOpenChangeKeys={['Escape']} + toggle={(toggleRef) => ( + + + + )} + ref={menuRef} + onActionClick={onFavorite} + // eslint-disable-next-line no-console + onSelect={(_ev, itemId) => console.log('selected', itemId)} + > onTextChange(value)} /> - - {filteredFavorites.length > 0 && ( - - - {filteredFavorites} - - - - )} - {filteredItems} - - + {filteredFavorites.length > 0 && ( + + + {filteredFavorites} + + + + )} + {filteredItems} + ); - return ; }; diff --git a/packages/react-core/src/demos/ComposableMenu/examples/ComposableMultipleTypeaheadSelect.tsx b/packages/react-core/src/demos/ComposableMenu/examples/ComposableMultipleTypeaheadSelect.tsx deleted file mode 100644 index 4dc8503783c..00000000000 --- a/packages/react-core/src/demos/ComposableMenu/examples/ComposableMultipleTypeaheadSelect.tsx +++ /dev/null @@ -1,253 +0,0 @@ -import React from 'react'; -import { - Menu, - MenuContent, - MenuList, - MenuItem, - MenuItemProps, - MenuToggle, - MenuToggleElement, - Popper, - TextInputGroup, - TextInputGroupMain, - TextInputGroupUtilities, - ChipGroup, - Chip, - Button -} from '@patternfly/react-core'; -import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon'; - -const intitalMenuItems = [ - { itemId: 'Option 1', children: 'Option 1' }, - { itemId: 'Option 2', children: 'Option 2' }, - { itemId: 'Option 3', children: 'Option 3' } -]; - -export const ComposableMultipleTypeaheadSelect: React.FunctionComponent = () => { - const [isMenuOpen, setIsMenuOpen] = React.useState(false); - const [inputValue, setInputValue] = React.useState(''); - const [menuItems, setMenuItems] = React.useState(intitalMenuItems); - const [focusedItemIndex, setFocusedItemIndex] = React.useState(null); - const [activeItem, setActiveItem] = React.useState(null); - const [selected, setSelected] = React.useState([]); - const menuToggleRef = React.useRef({} as MenuToggleElement); - const textInputRef = React.useRef(); - const menuRef = React.useRef(); - const toggleRef = React.useRef(null); - - React.useEffect(() => { - let newMenuItems: MenuItemProps[] = intitalMenuItems; - - // Filter menu items based on the text input value when one exists - if (inputValue) { - newMenuItems = intitalMenuItems.filter((menuItem) => - String(menuItem.children).toLowerCase().includes(inputValue.toLowerCase()) - ); - - // When no options are found after filtering, display 'No results found'. - if (!newMenuItems.length) { - newMenuItems = [{ isDisabled: false, children: `No results found for "${inputValue}"`, itemId: 'no results' }]; - } - - // Open the menu when the input value changes and the new value is not empty - if (!isMenuOpen) { - setIsMenuOpen(true); - } - } - - setMenuItems(newMenuItems); - setActiveItem(null); - setFocusedItemIndex(null); - }, [inputValue]); - - const focusOnInput = () => textInputRef.current?.focus(); - - const handleMenuArrowKeys = (key: string) => { - let indexToFocus; - - if (isMenuOpen) { - if (key === 'ArrowUp') { - // When no index is set or at the first index, focus to the last, otherwise decrement focus index - if (focusedItemIndex === null || focusedItemIndex === 0) { - indexToFocus = menuItems.length - 1; - } else { - indexToFocus = focusedItemIndex - 1; - } - } - - if (key === 'ArrowDown') { - // When no index is set or at the last index, focus to the first, otherwise increment focus index - if (focusedItemIndex === null || focusedItemIndex === menuItems.length - 1) { - indexToFocus = 0; - } else { - indexToFocus = focusedItemIndex + 1; - } - } - - setFocusedItemIndex(indexToFocus); - const focusedItem = menuItems.filter((item) => !item.isDisabled)[indexToFocus]; - setActiveItem(`composable-multi-typeahead-${focusedItem.itemId.replace(' ', '-')}`); - } - }; - - const onInputKeyDown = (event: React.KeyboardEvent) => { - const enabledMenuItems = menuItems.filter((menuItem) => !menuItem.isDisabled); - const [firstMenuItem] = enabledMenuItems; - const focusedItem = focusedItemIndex ? enabledMenuItems[focusedItemIndex] : firstMenuItem; - - switch (event.key) { - // Select the first available option - case 'Enter': - if (!isMenuOpen) { - setIsMenuOpen((prevIsOpen) => !prevIsOpen); - // Only allow selection if the first item is a valid, selectable option - } else if (isMenuOpen && focusedItem.itemId !== 'no results') { - onMenuSelect(focusedItem.itemId as string); - } - break; - case 'Tab': - case 'Escape': - setIsMenuOpen(false); - setActiveItem(null); - break; - case 'ArrowUp': - case 'ArrowDown': - event.preventDefault(); - handleMenuArrowKeys(event.key); - break; - } - }; - - // Close the menu when a click occurs outside of the menu, toggle, or input. - const onDocumentClick = (event: MouseEvent | undefined) => { - const isValidClick = [menuRef, menuToggleRef, textInputRef].some((ref) => - ref?.current?.contains(event?.target as HTMLElement) - ); - if (isMenuOpen && !isValidClick) { - setIsMenuOpen(false); - setActiveItem(null); - } - }; - - // Close the menu when focus is on a menu item and Escape or Tab is pressed - const onDocumentKeydown = (event: KeyboardEvent | undefined) => { - if (isMenuOpen && menuRef?.current?.contains(event?.target as HTMLElement)) { - if (event?.key === 'Escape') { - setIsMenuOpen(false); - focusOnInput(); - } else if (event?.key === 'Tab') { - setIsMenuOpen(false); - } - } - }; - - const toggleMenuOpen = () => { - setIsMenuOpen(!isMenuOpen); - focusOnInput(); - }; - - const onTextInputChange = (_event: React.FormEvent, value: string) => { - setInputValue(value); - }; - - const onMenuSelect = (itemId: string) => { - // Only allow selection if the item is a valid, selectable option - if (itemId && itemId !== 'no results') { - setSelected( - selected.includes(itemId) ? selected.filter((selection) => selection !== itemId) : [...selected, itemId] - ); - } - - focusOnInput(); - }; - - const toggle = ( - - - - - {selected.map((selection, index) => ( - { - ev.stopPropagation(); - onMenuSelect(selection); - }} - > - {selection} - - ))} - - - - {selected.length > 0 && ( - - )} - - - - ); - const menu = ( - } - id="multiple-typeahead-select-menu" - onSelect={(_ev, itemId) => onMenuSelect(itemId?.toString() as string)} - selected={selected} - role="listbox" - > - - - {menuItems.map((itemProps, index) => ( - - ))} - - - - ); - return ( - - ); -}; diff --git a/packages/react-core/src/demos/ComposableMenu/examples/ComposableOptionsMenuVariants.tsx b/packages/react-core/src/demos/ComposableMenu/examples/ComposableOptionsMenuVariants.tsx deleted file mode 100644 index bc9a1a084e8..00000000000 --- a/packages/react-core/src/demos/ComposableMenu/examples/ComposableOptionsMenuVariants.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import React from 'react'; -import { MenuToggle, Menu, MenuContent, MenuList, MenuItem, MenuGroup, Popper, Divider } from '@patternfly/react-core'; - -export const ComposableOptionsMenuVariants: React.FunctionComponent = () => { - const [isOpen, setIsOpen] = React.useState(false); - const [selected, setSelected] = React.useState(''); - const menuRef = React.useRef(); - const toggleRef = React.useRef(); - - const handleMenuKeys = (event: KeyboardEvent) => { - if (isOpen && menuRef?.current?.contains(event.target as Node)) { - if (event.key === 'Escape' || event.key === 'Tab') { - setIsOpen(!isOpen); - toggleRef?.current?.focus(); - } - } - }; - - const handleClickOutside = (event: MouseEvent) => { - if (isOpen && !menuRef?.current?.contains(event.target as Node)) { - setIsOpen(false); - } - }; - - React.useEffect(() => { - window.addEventListener('keydown', handleMenuKeys); - window.addEventListener('click', handleClickOutside); - - return () => { - window.removeEventListener('keydown', handleMenuKeys); - window.removeEventListener('click', handleClickOutside); - }; - }, [isOpen, menuRef]); - - 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(); - } - }, 0); - setIsOpen(!isOpen); - }; - - const toggle = ( - - Options menu - - ); - - const menu = ( - setSelected(itemId.toString())} - > - - - - Option 1 - - - Disabled Option - - - - - - Option 1 - - - Option 2 - - - - - - - - Option 1 - - - Option 2 - - - - - - - ); - return ; -}; diff --git a/packages/react-core/src/demos/ComposableMenu/examples/ComposableSimpleCheckboxSelect.tsx b/packages/react-core/src/demos/ComposableMenu/examples/ComposableSimpleCheckboxSelect.tsx deleted file mode 100644 index 61ea2852880..00000000000 --- a/packages/react-core/src/demos/ComposableMenu/examples/ComposableSimpleCheckboxSelect.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import React from 'react'; -import { MenuToggle, Menu, MenuContent, MenuList, MenuItem, Popper, Badge } from '@patternfly/react-core'; - -export const ComposableSimpleCheckboxSelect: React.FunctionComponent = () => { - const [isOpen, setIsOpen] = React.useState(false); - const [selectedItems, setSelectedItems] = React.useState([]); - const toggleRef = React.useRef(null); - const menuRef = React.useRef(null); - - const handleMenuKeys = React.useCallback( - (event) => { - if (menuRef.current) { - if (isOpen && menuRef.current.contains(event.target as Node)) { - if (event.key === 'Escape' || event.key === 'Tab') { - setIsOpen(!isOpen); - toggleRef?.current?.focus(); - } - } - } - }, - [isOpen] - ); - - const handleClickOutside = React.useCallback( - (event) => { - if (isOpen && !menuRef?.current?.contains(event.target as Node)) { - setIsOpen(false); - } - }, - [isOpen] - ); - - React.useEffect(() => { - window.addEventListener('keydown', handleMenuKeys); - window.addEventListener('click', handleClickOutside); - return () => { - window.removeEventListener('keydown', handleMenuKeys); - window.removeEventListener('click', handleClickOutside); - }; - }, [handleClickOutside, handleMenuKeys]); - - 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)'); - firstElement && (firstElement as HTMLElement).focus(); - } - }, 0); - setIsOpen(!isOpen); - }; - - const onSelect = (event: React.MouseEvent | undefined, itemId: string | number | undefined) => { - if (typeof itemId === 'string' || typeof itemId === 'undefined') { - return; - } - - if (selectedItems.includes(itemId)) { - setSelectedItems(selectedItems.filter((id) => id !== itemId)); - } else { - setSelectedItems([...selectedItems, itemId]); - } - }; - - const toggle = ( - 0 && { badge: {selectedItems.length} })} - onClick={onToggleClick} - isExpanded={isOpen} - style={ - { - width: '220px' - } as React.CSSProperties - } - > - Filter by status - - ); - const menu = ( - - - - - Debug - - - Info - - - Warn - - - Error - - - - - ); - return ; -}; diff --git a/packages/react-core/src/demos/ComposableMenu/examples/ComposableSimpleDropdown.tsx b/packages/react-core/src/demos/ComposableMenu/examples/ComposableSimpleDropdown.tsx deleted file mode 100644 index 98d446418e3..00000000000 --- a/packages/react-core/src/demos/ComposableMenu/examples/ComposableSimpleDropdown.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import React from 'react'; -import { MenuToggle, Menu, MenuContent, MenuList, MenuItem, Popper } from '@patternfly/react-core'; - -export const ComposableSimpleDropdown: React.FunctionComponent = () => { - const [isOpen, setIsOpen] = React.useState(false); - const toggleRef = React.useRef(null); - const menuRef = React.useRef(null); - - 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' || event.key === 'Tab') { - setIsOpen(!isOpen); - toggleRef.current?.focus(); - } - } - }; - - const handleClickOutside = (event: MouseEvent) => { - if (isOpen && !menuRef.current?.contains(event.target as Node)) { - setIsOpen(false); - } - }; - - React.useEffect(() => { - window.addEventListener('keydown', handleMenuKeys); - window.addEventListener('click', handleClickOutside); - return () => { - window.removeEventListener('keydown', handleMenuKeys); - window.removeEventListener('click', handleClickOutside); - }; - }, [isOpen, menuRef]); - - 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)'); - firstElement && (firstElement as HTMLElement).focus(); - } - }, 0); - setIsOpen(!isOpen); - }; - - const toggle = ( - - {isOpen ? 'Expanded' : 'Collapsed'} - - ); - const menu = ( - // eslint-disable-next-line no-console - console.log('selected', itemId)}> - - - Action - ev.preventDefault()}> - Link - - Disabled Action - - Disabled Link - - - - - ); - return ; -}; diff --git a/packages/react-core/src/demos/ComposableMenu/examples/ComposableSimpleSelect.tsx b/packages/react-core/src/demos/ComposableMenu/examples/ComposableSimpleSelect.tsx deleted file mode 100644 index e17561a0f7a..00000000000 --- a/packages/react-core/src/demos/ComposableMenu/examples/ComposableSimpleSelect.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import React from 'react'; -import { MenuToggle, Menu, MenuContent, MenuList, MenuItem, Popper } from '@patternfly/react-core'; -import TableIcon from '@patternfly/react-icons/dist/esm/icons/table-icon'; - -export const ComposableSimpleSelect: React.FunctionComponent = () => { - const [isOpen, setIsOpen] = React.useState(false); - const [selected, setSelected] = React.useState('Select a value'); - const toggleRef = React.useRef(null); - const menuRef = React.useRef(null); - - const handleMenuKeys = (event: KeyboardEvent) => { - if (isOpen && menuRef.current?.contains(event.target as Node)) { - if (event.key === 'Escape' || event.key === 'Tab') { - setIsOpen(!isOpen); - toggleRef.current?.focus(); - } - } - }; - - const handleClickOutside = (event: MouseEvent) => { - if (isOpen && !menuRef.current?.contains(event.target as Node)) { - setIsOpen(false); - } - }; - - React.useEffect(() => { - window.addEventListener('keydown', handleMenuKeys); - window.addEventListener('click', handleClickOutside); - return () => { - window.removeEventListener('keydown', handleMenuKeys); - window.removeEventListener('click', handleClickOutside); - }; - }, [isOpen, menuRef]); - - 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)'); - firstElement && (firstElement as HTMLElement).focus(); - } - }, 0); - setIsOpen(!isOpen); - }; - - const toggle = ( - - {selected} - - ); - - function onSelect(event: React.MouseEvent | undefined, itemId: string | number | undefined) { - if (typeof itemId === 'undefined') { - return; - } - - setSelected(itemId.toString()); - } - - const menu = ( - - - - Option 1 - Option 2 - }> - Option 3 - - - - - ); - return ; -}; diff --git a/packages/react-core/src/demos/ComposableMenu/examples/ComposableTypeaheadSelect.tsx b/packages/react-core/src/demos/ComposableMenu/examples/ComposableTypeaheadSelect.tsx deleted file mode 100644 index d57c232d70d..00000000000 --- a/packages/react-core/src/demos/ComposableMenu/examples/ComposableTypeaheadSelect.tsx +++ /dev/null @@ -1,234 +0,0 @@ -import React from 'react'; - -import { - Menu, - MenuContent, - MenuList, - MenuItem, - Popper, - MenuToggle, - TextInputGroup, - MenuItemProps, - Button, - TextInputGroupUtilities, - TextInputGroupMain, - MenuToggleElement -} from '@patternfly/react-core'; -import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon'; -import TableIcon from '@patternfly/react-icons/dist/esm/icons/table-icon'; - -const intitalMenuItems: MenuItemProps[] = [ - { itemId: 'Option 1', children: 'Option 1' }, - { itemId: 'Option 2', children: 'Option 2' }, - { itemId: 'Option 3', children: 'Option 3', icon: } -]; - -export const ComposableTypeaheadSelect: React.FunctionComponent = () => { - const [isMenuOpen, setIsMenuOpen] = React.useState(false); - const [inputValue, setInputValue] = React.useState(''); - const [menuItems, setMenuItems] = React.useState(intitalMenuItems); - const [focusedItemIndex, setFocusedItemIndex] = React.useState(null); - const [activeItem, setActiveItem] = React.useState(null); - const [isSelected, setIsSelected] = React.useState(false); - - const menuToggleRef = React.useRef({} as MenuToggleElement); - const textInputRef = React.useRef(); - const menuRef = React.useRef(null); - const triggerRef = React.useRef(null); - - React.useEffect(() => { - let newMenuItems: MenuItemProps[] = intitalMenuItems; - - // Filter menu items based on the text input value when one exists - if (inputValue) { - newMenuItems = intitalMenuItems.filter((menuItem) => - String(menuItem.children).toLowerCase().includes(inputValue.toLowerCase()) - ); - - // When no options are found after filtering, display 'No results found' - if (!newMenuItems.length) { - newMenuItems = [{ isDisabled: false, children: `No results found for "${inputValue}"`, itemId: 'no results' }]; - } - - // Open the menu when the input value changes and the new value is not empty - if (!isMenuOpen) { - setIsMenuOpen(true); - } - } - - setMenuItems(newMenuItems); - setActiveItem(null); - setFocusedItemIndex(null); - }, [inputValue]); - - const focusOnInput = () => textInputRef.current?.focus(); - - const onMenuSelect = (_event: React.MouseEvent | undefined, itemId: string | number | undefined) => { - // Only allow selection if the item is a valid, selectable option - if (itemId && itemId !== 'no results') { - setInputValue(itemId.toString()); - setIsSelected(true); - } - - setIsMenuOpen(false); - setFocusedItemIndex(null); - setActiveItem(null); - focusOnInput(); - }; - - const handleMenuArrowKeys = (key: string) => { - let indexToFocus; - - if (isMenuOpen) { - if (key === 'ArrowUp') { - // When no index is set or at the first index, focus to the last, otherwise decrement focus index - if (focusedItemIndex === null || focusedItemIndex === 0) { - indexToFocus = menuItems.length - 1; - } else { - indexToFocus = focusedItemIndex - 1; - } - } - - if (key === 'ArrowDown') { - // When no index is set or at the last index, focus to the first, otherwise increment focus index - if (focusedItemIndex === null || focusedItemIndex === menuItems.length - 1) { - indexToFocus = 0; - } else { - indexToFocus = focusedItemIndex + 1; - } - } - - setFocusedItemIndex(indexToFocus); - const focusedItem = menuItems.filter((item) => !item.isDisabled)[indexToFocus]; - setActiveItem(`composable-typeahead-${focusedItem.itemId.replace(' ', '-')}`); - } - }; - - const onInputKeyDown = (event: React.KeyboardEvent) => { - const enabledMenuItems = menuItems.filter((menuItem) => !menuItem.isDisabled); - const [firstMenuItem] = enabledMenuItems; - const focusedItem = focusedItemIndex ? enabledMenuItems[focusedItemIndex] : firstMenuItem; - - switch (event.key) { - // Select the first available option - case 'Enter': - // Only allow selection if the first item is a valid, selectable option - if (isMenuOpen && focusedItem.itemId !== 'no results') { - setInputValue(String(focusedItem.children)); - setIsSelected(true); - } - - setIsMenuOpen((prevIsOpen) => !prevIsOpen); - setFocusedItemIndex(null); - setActiveItem(null); - focusOnInput(); - - break; - case 'Tab': - case 'Escape': - setIsMenuOpen(false); - setActiveItem(null); - break; - case 'ArrowUp': - case 'ArrowDown': - event.preventDefault(); - handleMenuArrowKeys(event.key); - break; - } - }; - - // Close the menu when a click occurs outside of the menu, toggle, or input - const onDocumentClick = (event: MouseEvent | undefined) => { - const isValidClick = [menuRef, menuToggleRef, textInputRef].some((ref) => - ref?.current?.contains(event?.target as HTMLElement) - ); - if (isMenuOpen && !isValidClick) { - setIsMenuOpen(false); - setActiveItem(null); - } - }; - - // Close the menu when focus is on a menu item and Escape or Tab is pressed - const onDocumentKeydown = (event: KeyboardEvent | undefined) => { - if (isMenuOpen && menuRef?.current?.contains(event?.target as HTMLElement)) { - if (event?.key === 'Escape') { - setIsMenuOpen(false); - focusOnInput(); - } else if (event?.key === 'Tab') { - setIsMenuOpen(false); - } - } - }; - - const toggleMenuOpen = () => { - setIsMenuOpen((prevIsOpen) => !prevIsOpen); - textInputRef.current?.focus(); - }; - - const onTextInputChange = (_event: React.FormEvent, value: string) => { - setInputValue(value); - setIsSelected(false); - }; - - return ( - - - - - - {!!inputValue && ( - - )} - - - - } - triggerRef={triggerRef} - popper={ - - - - {menuItems.map((itemProps, index) => ( - - ))} - - - - } - popperRef={menuRef} - isVisible={isMenuOpen} - onDocumentClick={onDocumentClick} - onDocumentKeyDown={onDocumentKeydown} - /> - ); -}; diff --git a/packages/react-core/src/demos/ComposableMenu/examples/ComposableContextSelector.tsx b/packages/react-core/src/demos/ComposableMenu/examples/ContextSelectorDemo.tsx similarity index 63% rename from packages/react-core/src/demos/ComposableMenu/examples/ComposableContextSelector.tsx rename to packages/react-core/src/demos/ComposableMenu/examples/ContextSelectorDemo.tsx index 9a0e6a8151e..866c48356e3 100644 --- a/packages/react-core/src/demos/ComposableMenu/examples/ComposableContextSelector.tsx +++ b/packages/react-core/src/demos/ComposableMenu/examples/ContextSelectorDemo.tsx @@ -1,32 +1,30 @@ import React from 'react'; import { MenuToggle, - Menu, - MenuContent, MenuFooter, - MenuList, - MenuItem, MenuSearch, MenuSearchInput, - Popper, Divider, InputGroup, InputGroupItem, Button, ButtonVariant, - SearchInput + SearchInput, + Dropdown, + DropdownList, + DropdownItem } from '@patternfly/react-core'; import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon'; interface ItemData { text: string; href?: string; - isDisabled?: boolean; + isDisabled?: boolean | undefined; } type ItemArrayType = (ItemData | string)[]; -export const ComposableContextSelector: React.FunctionComponent = () => { +export const ContextSelectorDemo: React.FunctionComponent = () => { const items: ItemArrayType = [ { text: 'Action' @@ -60,46 +58,8 @@ export const ComposableContextSelector: React.FunctionComponent = () => { const [filteredItems, setFilteredItems] = React.useState(items); const [searchInputValue, setSearchInputValue] = React.useState(''); const menuRef = React.useRef(null); - const toggleRef = React.useRef(null); const menuFooterBtnRef = React.useRef(null); - const handleMenuKeys = (event: KeyboardEvent) => { - if (!isOpen) { - return; - } - if (menuFooterBtnRef.current?.contains(event.target as Node)) { - if (event.key === 'Tab') { - if (event.shiftKey) { - return; - } - setIsOpen(!isOpen); - toggleRef.current?.focus(); - } - } - if (menuRef.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); - } - }; - - React.useEffect(() => { - window.addEventListener('keydown', handleMenuKeys); - window.addEventListener('click', handleClickOutside); - - return () => { - window.removeEventListener('keydown', handleMenuKeys); - window.removeEventListener('click', handleClickOutside); - }; - }, [isOpen, menuRef]); - const onToggleClick = (ev: React.MouseEvent) => { ev.stopPropagation(); // Stop handleClickOutside from handling setTimeout(() => { @@ -113,11 +73,10 @@ export const ComposableContextSelector: React.FunctionComponent = () => { setIsOpen(!isOpen); }; - const toggle = ( - - {selected} - - ); + const onSelect = (ev: React.MouseEvent | undefined, itemId: string | number | undefined) => { + if (typeof itemId === 'number' || typeof itemId === 'undefined') { + return; + } const onSelect = (ev: React.MouseEvent, itemId: string | number) => { setSelected(itemId.toString()); @@ -147,8 +106,21 @@ export const ComposableContextSelector: React.FunctionComponent = () => { } }; - const menu = ( - + return ( + setIsOpen(isOpen)} + onOpenChangeKeys={['Escape']} + toggle={(toggleRef) => ( + + {selected} + + )} + ref={menuRef} + id="context-selector" + onSelect={onSelect} + isScrollable + > @@ -175,25 +147,27 @@ export const ComposableContextSelector: React.FunctionComponent = () => { - - - {filteredItems.map((item, index) => { - const [itemText, isDisabled, href] = - typeof item === 'string' ? [item, null, null] : [item.text, item.isDisabled || null, item.href || null]; - return ( - - {itemText} - - ); - })} - - + + {filteredItems.map((item, index) => { + const [itemText, isDisabled, href] = + typeof item === 'string' ? [item, null, null] : [item.text, item.isDisabled || null, item.href || null]; + return ( + + {itemText} + + ); + })} + - + ); - return ; }; diff --git a/packages/react-core/src/demos/ComposableMenu/examples/DateSelectDemo.tsx b/packages/react-core/src/demos/ComposableMenu/examples/DateSelectDemo.tsx new file mode 100644 index 00000000000..b5a8949b434 --- /dev/null +++ b/packages/react-core/src/demos/ComposableMenu/examples/DateSelectDemo.tsx @@ -0,0 +1,88 @@ +import React from 'react'; +import { MenuToggle, Select, SelectList, SelectOption } from '@patternfly/react-core'; + +export const DateSelectDemo: React.FunctionComponent = () => { + const [isOpen, setIsOpen] = React.useState(false); + const [selected, setSelected] = React.useState(0); + const menuRef = React.useRef(); + + const onToggleClick = (ev: React.MouseEvent) => { + ev.stopPropagation(); // Stop handleClickOutside from handling + setTimeout(() => { + if (menuRef.current) { + const firstElement = menuRef.current.querySelector('li > button:not(:disabled)'); + firstElement && (firstElement as HTMLElement).focus(); + } + }, 0); + setIsOpen(!isOpen); + }; + + const monthStrings = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December' + ]; + + const dateString = (date: Date) => `${monthStrings[date.getMonth()]} ${date.getDate()}, ${date.getFullYear()}`; + + const date = new Date(); + + const toggleText = { + 0: 'Today ', + 1: 'Yesterday ', + 2: 'Last 7 days ', + 3: 'Last 14 days ' + }; + + const dateText = { + 0: ({dateString(date)}), + 1: ( + + ({dateString(new Date(new Date().setDate(date.getDate() - 1)))} - {dateString(date)}) + + ), + 2: ( + + ({dateString(new Date(new Date().setDate(date.getDate() - 7)))} - {dateString(date)}) + + ), + 3: ( + + ({dateString(new Date(new Date().setDate(date.getDate() - 14)))} - {dateString(date)}) + + ) + }; + + return ( + // eslint-disable-next-line no-console + + ); +}; diff --git a/packages/react-core/src/demos/ComposableMenu/examples/ComposableDrilldownMenu.tsx b/packages/react-core/src/demos/ComposableMenu/examples/DrilldownMenuDemo.tsx similarity index 89% rename from packages/react-core/src/demos/ComposableMenu/examples/ComposableDrilldownMenu.tsx rename to packages/react-core/src/demos/ComposableMenu/examples/DrilldownMenuDemo.tsx index f25da999673..7b27ad2afc5 100644 --- a/packages/react-core/src/demos/ComposableMenu/examples/ComposableDrilldownMenu.tsx +++ b/packages/react-core/src/demos/ComposableMenu/examples/DrilldownMenuDemo.tsx @@ -7,7 +7,7 @@ import { MenuItem, DrilldownMenu, Divider, - Popper + MenuContainer } from '@patternfly/react-core'; import StorageDomainIcon from '@patternfly/react-icons/dist/esm/icons/storage-domain-icon'; import CodeBranchIcon from '@patternfly/react-icons/dist/esm/icons/code-branch-icon'; @@ -18,7 +18,7 @@ interface MenuHeightsType { [id: string]: number; } -export const ComposableDrilldownMenu: React.FunctionComponent = () => { +export const DrilldownMenuDemo: React.FunctionComponent = () => { const [isOpen, setIsOpen] = React.useState(false); const [activeMenu, setActiveMenu] = React.useState('rootMenu'); const [menuDrilledIn, setMenuDrilledIn] = React.useState([]); @@ -27,30 +27,6 @@ export const ComposableDrilldownMenu: React.FunctionComponent = () => { const toggleRef = React.useRef(null); const menuRef = React.useRef(null); - const handleMenuKeys = (event: KeyboardEvent) => { - if (isOpen && menuRef.current?.contains(event.target as Node)) { - if (event.key === 'Escape' || event.key === 'Tab') { - setIsOpen(!isOpen); - toggleRef.current?.focus(); - } - } - }; - - const handleClickOutside = (event: MouseEvent) => { - if (isOpen && !menuRef.current?.contains(event.target as Node)) { - setIsOpen(false); - } - }; - - React.useEffect(() => { - window.addEventListener('keydown', handleMenuKeys); - window.addEventListener('click', handleClickOutside); - return () => { - window.removeEventListener('keydown', handleMenuKeys); - window.removeEventListener('click', handleClickOutside); - }; - }, [isOpen, menuRef]); - const onToggleClick = (ev: React.MouseEvent) => { ev.stopPropagation(); // Stop handleClickOutside from handling setTimeout(() => { @@ -83,7 +59,7 @@ export const ComposableDrilldownMenu: React.FunctionComponent = () => { }; const setHeight = (menuId: string, height: number) => { - if (!menuHeights[menuId]) { + if (!menuHeights[menuId] || (menuId !== 'rootMenu' && menuHeights[menuId] !== height)) { setMenuHeights({ ...menuHeights, [menuId]: height @@ -242,5 +218,14 @@ export const ComposableDrilldownMenu: React.FunctionComponent = () => {
); - return ; + return ( + setIsOpen(isOpen)} + menu={menu} + menuRef={menuRef} + toggle={toggle} + toggleRef={toggleRef} + /> + ); }; diff --git a/packages/react-core/src/demos/ComposableMenu/examples/FavoritesDemo.tsx b/packages/react-core/src/demos/ComposableMenu/examples/FavoritesDemo.tsx new file mode 100644 index 00000000000..003443b9859 --- /dev/null +++ b/packages/react-core/src/demos/ComposableMenu/examples/FavoritesDemo.tsx @@ -0,0 +1,127 @@ +import React from 'react'; +import { MenuToggle, Divider, Dropdown, DropdownGroup, DropdownList, DropdownItem } from '@patternfly/react-core'; + +export const FavoritesDemo: React.FunctionComponent = () => { + const [isOpen, setIsOpen] = React.useState(false); + const [favorites, setFavorites] = React.useState([]); + const menuRef = React.useRef(null); + + const onToggleClick = (ev: React.MouseEvent) => { + ev.stopPropagation(); + setTimeout(() => { + if (menuRef.current) { + const firstElement = menuRef.current.querySelector('li > button:not(:disabled)'); + firstElement && (firstElement as HTMLElement).focus(); + } + }, 0); + setIsOpen(!isOpen); + }; + + const menuItems = [ + + + + Item 1 + + + Item 2 + + + Item 3 + + + Item 4 + + + , + , + + + + Item 5 + + + Item 6 + + + Item 7 + + + Item 8 + + + + ]; + + const createFavorites = (favIds: string[]) => { + const favorites: JSX.Element[] = []; + + menuItems.forEach((item) => { + if (item.type === DropdownList) { + item.props.children.filter((child) => { + if (favIds.includes(child.props.itemId)) { + favorites.push(child); + } + }); + } else if (item.type === DropdownGroup) { + 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; + }; + + React.useEffect(() => { + if (favorites.length === 0) { + const firstElement = menuRef?.current?.querySelector('li > button:not(:disabled)'); + firstElement && (firstElement as HTMLElement).focus(); + } + }, [favorites]); + + 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]); + } + } + }; + + return ( + // eslint-disable-next-line no-console + setIsOpen(isOpen)} + toggle={(toggleRef) => ( + + {isOpen ? 'Expanded' : 'Collapsed'} + + )} + ref={menuRef} + onActionClick={onFavorite} + // eslint-disable-next-line no-console + onSelect={(_ev, itemId) => console.log('selected', itemId)} + > + {favorites.length > 0 && ( + + + {createFavorites(favorites)} + + + + )} + {menuItems} + + ); +}; diff --git a/packages/react-core/src/demos/ComposableMenu/examples/ComposableFlyout.tsx b/packages/react-core/src/demos/ComposableMenu/examples/FlyoutDemo.tsx similarity index 67% rename from packages/react-core/src/demos/ComposableMenu/examples/ComposableFlyout.tsx rename to packages/react-core/src/demos/ComposableMenu/examples/FlyoutDemo.tsx index 0043decf203..03ae255d10a 100644 --- a/packages/react-core/src/demos/ComposableMenu/examples/ComposableFlyout.tsx +++ b/packages/react-core/src/demos/ComposableMenu/examples/FlyoutDemo.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { MenuToggle, Menu, MenuContent, MenuList, MenuItem, Popper } from '@patternfly/react-core'; +import { MenuToggle, Menu, MenuContent, MenuList, MenuItem, MenuContainer } from '@patternfly/react-core'; /* eslint-disable no-console */ const onSelect = (event: React.MouseEvent | undefined, itemId: string | number | undefined) => @@ -30,38 +30,11 @@ const FlyoutMenu: React.FunctionComponent = ({ depth, children ); -export const ComposableFlyout: React.FunctionComponent = () => { +export const FlyoutDemo: React.FunctionComponent = () => { const [isOpen, setIsOpen] = React.useState(false); const menuRef = React.useRef(null); const toggleRef = React.useRef(null); - 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' || event.key === 'Tab') { - setIsOpen(!isOpen); - toggleRef.current?.focus(); - } - } - }; - - const handleClickOutside = (event: MouseEvent) => { - if (isOpen && !menuRef.current?.contains(event.target as Node)) { - setIsOpen(false); - } - }; - - React.useEffect(() => { - window.addEventListener('keydown', handleMenuKeys); - window.addEventListener('click', handleClickOutside); - return () => { - window.removeEventListener('keydown', handleMenuKeys); - window.removeEventListener('click', handleClickOutside); - }; - }, [isOpen, menuRef]); - let curFlyout = ; for (let i = 2; i < 14; i++) { curFlyout = {curFlyout}; @@ -79,7 +52,7 @@ export const ComposableFlyout: React.FunctionComponent = () => { }; const toggle = ( - + {isOpen ? 'Expanded' : 'Collapsed'} ); @@ -100,5 +73,14 @@ export const ComposableFlyout: React.FunctionComponent = () => { ); - return ; + return ( + setIsOpen(isOpen)} + menu={menu} + menuRef={menuRef} + toggle={toggle} + toggleRef={toggleRef} + /> + ); }; diff --git a/packages/react-core/src/demos/ComposableMenu/examples/OptionsMenuDemo.tsx b/packages/react-core/src/demos/ComposableMenu/examples/OptionsMenuDemo.tsx new file mode 100644 index 00000000000..51828377cca --- /dev/null +++ b/packages/react-core/src/demos/ComposableMenu/examples/OptionsMenuDemo.tsx @@ -0,0 +1,68 @@ +import React from 'react'; +import { MenuToggle, Divider, Dropdown, DropdownList, DropdownItem, DropdownGroup } from '@patternfly/react-core'; + +export const OptionsMenuDemo: React.FunctionComponent = () => { + const [isOpen, setIsOpen] = React.useState(false); + const [selected, setSelected] = React.useState(''); + const menuRef = React.useRef(); + + 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(); + } + }, 0); + setIsOpen(!isOpen); + }; + + return ( + setIsOpen(isOpen)} + toggle={(toggleRef) => ( + + Options menu + + )} + ref={menuRef} + id="options-menu" + selected={selected} + onSelect={(_ev, itemId) => itemId && setSelected(itemId.toString())} + > + + + Option 1 + + + Disabled Option + + + + + + Option 1 + + + Option 2 + + + + + + + + Option 1 + + + Option 2 + + + + + + ); +}; diff --git a/packages/react-core/src/demos/ComposableMenu/examples/ComposableTreeViewMenu.tsx b/packages/react-core/src/demos/ComposableMenu/examples/TreeViewMenuDemo.tsx similarity index 78% rename from packages/react-core/src/demos/ComposableMenu/examples/ComposableTreeViewMenu.tsx rename to packages/react-core/src/demos/ComposableMenu/examples/TreeViewMenuDemo.tsx index b3380281eab..a2de9b0f68d 100644 --- a/packages/react-core/src/demos/ComposableMenu/examples/ComposableTreeViewMenu.tsx +++ b/packages/react-core/src/demos/ComposableMenu/examples/TreeViewMenuDemo.tsx @@ -5,12 +5,12 @@ import { PanelMain, PanelMainBody, Title, - Popper, + MenuContainer, TreeView, TreeViewDataItem } from '@patternfly/react-core'; -export const ComposableTreeViewMenu: React.FunctionComponent = () => { +export const TreeViewMenuDemo: React.FunctionComponent = () => { const [isOpen, setIsOpen] = React.useState(false); const [checkedItems, setCheckedItems] = React.useState([]); const toggleRef = React.useRef(null); @@ -166,47 +166,6 @@ export const ComposableTreeViewMenu: React.FunctionComponent = () => { ); }; - // Controls keys that should open/close the menu - const handleMenuKeys = (event: KeyboardEvent) => { - if (!isOpen) { - return; - } - if (menuRef.current?.contains(event.target as Node) || toggleRef.current?.contains(event.target as Node)) { - // The escape key when pressed while inside the menu should close the menu and refocus the toggle - if (event.key === 'Escape') { - setIsOpen(!isOpen); - toggleRef.current?.focus(); - } - - // The tab key when pressed while inside the menu and on the contained last tree view should close the menu and refocus the toggle - // Shift tab should keep the default behavior to return to a previous tree view - if (event.key === 'Tab' && !event.shiftKey) { - const treeList = menuRef.current?.querySelectorAll('.pf-c-tree-view') || []; - if (treeList[treeList.length - 1].contains(event.target as Node)) { - event.preventDefault(); - setIsOpen(!isOpen); - toggleRef.current?.focus(); - } - } - } - }; - - // Controls that a click outside the menu while the menu is open should close the menu - const handleClickOutside = (event: MouseEvent) => { - if (isOpen && !menuRef.current?.contains(event.target as Node)) { - setIsOpen(false); - } - }; - - React.useEffect(() => { - window.addEventListener('keydown', handleMenuKeys); - window.addEventListener('click', handleClickOutside); - return () => { - window.removeEventListener('keydown', handleMenuKeys); - window.removeEventListener('click', handleClickOutside); - }; - }, [isOpen, menuRef]); - const onToggleClick = (ev: React.MouseEvent) => { ev.stopPropagation(); // Stop handleClickOutside from handling setTimeout(() => { @@ -267,5 +226,16 @@ export const ComposableTreeViewMenu: React.FunctionComponent = () => { ); - return ; + + return ( + setIsOpen(isOpen)} + onOpenChangeKeys={['Escape']} + menu={menu} + menuRef={menuRef} + toggle={toggle} + toggleRef={toggleRef} + /> + ); }; From ed0a7c36b2818809da2e62854368eedf1b8c5626 Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Mon, 1 May 2023 10:44:45 -0400 Subject: [PATCH 02/12] fix merge update --- .../src/demos/ComposableMenu/examples/ContextSelectorDemo.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/react-core/src/demos/ComposableMenu/examples/ContextSelectorDemo.tsx b/packages/react-core/src/demos/ComposableMenu/examples/ContextSelectorDemo.tsx index 866c48356e3..9d0c28dfe76 100644 --- a/packages/react-core/src/demos/ComposableMenu/examples/ContextSelectorDemo.tsx +++ b/packages/react-core/src/demos/ComposableMenu/examples/ContextSelectorDemo.tsx @@ -77,8 +77,6 @@ export const ContextSelectorDemo: React.FunctionComponent = () => { if (typeof itemId === 'number' || typeof itemId === 'undefined') { return; } - - const onSelect = (ev: React.MouseEvent, itemId: string | number) => { setSelected(itemId.toString()); setIsOpen(!isOpen); }; From 635081a841e1a38c2f10d6ce1c8e65c3b8cf6c69 Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Wed, 3 May 2023 14:19:19 -0400 Subject: [PATCH 03/12] rebase and deprecated blurb --- .../react-core/src/demos/ComposableMenu/ApplicationLauncher.md | 2 ++ packages/react-core/src/demos/ComposableMenu/ContextSelector.md | 2 ++ packages/react-core/src/demos/ComposableMenu/OptionsMenu.md | 2 ++ 3 files changed, 6 insertions(+) diff --git a/packages/react-core/src/demos/ComposableMenu/ApplicationLauncher.md b/packages/react-core/src/demos/ComposableMenu/ApplicationLauncher.md index d26cda421e0..b295d709af2 100644 --- a/packages/react-core/src/demos/ComposableMenu/ApplicationLauncher.md +++ b/packages/react-core/src/demos/ComposableMenu/ApplicationLauncher.md @@ -8,6 +8,8 @@ source: react-demos import ThIcon from '@patternfly/react-icons/dist/esm/icons/th-icon'; import pfIcon from './examples/pf-logo-small.svg'; +As the `ApplicationLauncher` component is now deprecated, an application launcher may now be built using the new suite of `Menu` components. This is showcased below in the example using the new `Dropdpown` component built off of `Menu`. + ### Application launcher ```ts file="./examples/ApplicationLauncherDemo.tsx" diff --git a/packages/react-core/src/demos/ComposableMenu/ContextSelector.md b/packages/react-core/src/demos/ComposableMenu/ContextSelector.md index 0ed22193fd6..dac4600597d 100644 --- a/packages/react-core/src/demos/ComposableMenu/ContextSelector.md +++ b/packages/react-core/src/demos/ComposableMenu/ContextSelector.md @@ -7,6 +7,8 @@ source: react-demos import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon'; +As the `ContextSelector` component is now deprecated, a context selector may now be built using the new suite of `Menu` components. This is showcased below in the example using the new `Dropdpown` component built off of `Menu`. + ### Context selector ```ts file="./examples/ContextSelectorDemo.tsx" diff --git a/packages/react-core/src/demos/ComposableMenu/OptionsMenu.md b/packages/react-core/src/demos/ComposableMenu/OptionsMenu.md index c85fa1c8c85..1f6726b690e 100644 --- a/packages/react-core/src/demos/ComposableMenu/OptionsMenu.md +++ b/packages/react-core/src/demos/ComposableMenu/OptionsMenu.md @@ -7,6 +7,8 @@ source: react-demos import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon'; +As the `OptionsMenu` component is now deprecated, an options menu may now be built using the new suite of `Menu` components. This is showcased below in the example using the new `Dropdpown` component built off of `Menu`. + ### Options menu ```ts file="./examples/OptionsMenuDemo.tsx" From f467daa744f5ed54383d95d544d6d2e727853d93 Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Sun, 7 May 2023 21:57:02 -0400 Subject: [PATCH 04/12] rename directory, remove dupe focusing, update desc --- .../src/components/Dropdown/Dropdown.tsx | 4 +- .../src/components/Menu/MenuContainer.tsx | 26 +++---- .../examples/OptionsMenuDemo.tsx | 68 ------------------- .../ApplicationLauncher.md | 0 .../ContextSelector.md | 0 .../CustomMenus.md | 0 .../OptionsMenu.md | 2 +- .../examples/ActionsMenuDemo.tsx | 46 +++++-------- .../examples/ApplicationLauncherDemo.tsx | 3 +- .../examples/ComposableDateSelect.tsx | 0 .../examples/ComposableDropdwnVariants.tsx | 0 .../examples/ContextSelectorDemo.tsx | 11 +-- .../examples/DateSelectDemo.tsx | 14 ++-- .../examples/DrilldownMenuDemo.tsx | 9 +-- .../examples/FavoritesDemo.tsx | 9 +-- .../examples/FlyoutDemo.tsx | 9 +-- .../CustomMenus/examples/OptionsMenuDemo.tsx | 59 ++++++++++++++++ .../examples/TreeViewMenuDemo.tsx | 9 +-- .../examples/avatarImg.svg | 0 .../examples/pf-logo-small.svg | 0 20 files changed, 99 insertions(+), 170 deletions(-) delete mode 100644 packages/react-core/src/demos/ComposableMenu/examples/OptionsMenuDemo.tsx rename packages/react-core/src/demos/{ComposableMenu => CustomMenus}/ApplicationLauncher.md (100%) rename packages/react-core/src/demos/{ComposableMenu => CustomMenus}/ContextSelector.md (100%) rename packages/react-core/src/demos/{ComposableMenu => CustomMenus}/CustomMenus.md (100%) rename packages/react-core/src/demos/{ComposableMenu => CustomMenus}/OptionsMenu.md (79%) rename packages/react-core/src/demos/{ComposableMenu => CustomMenus}/examples/ActionsMenuDemo.tsx (77%) rename packages/react-core/src/demos/{ComposableMenu => CustomMenus}/examples/ApplicationLauncherDemo.tsx (98%) rename packages/react-core/src/demos/{ComposableMenu => CustomMenus}/examples/ComposableDateSelect.tsx (100%) rename packages/react-core/src/demos/{ComposableMenu => CustomMenus}/examples/ComposableDropdwnVariants.tsx (100%) rename packages/react-core/src/demos/{ComposableMenu => CustomMenus}/examples/ContextSelectorDemo.tsx (91%) rename packages/react-core/src/demos/{ComposableMenu => CustomMenus}/examples/DateSelectDemo.tsx (84%) rename packages/react-core/src/demos/{ComposableMenu => CustomMenus}/examples/DrilldownMenuDemo.tsx (95%) rename packages/react-core/src/demos/{ComposableMenu => CustomMenus}/examples/FavoritesDemo.tsx (92%) rename packages/react-core/src/demos/{ComposableMenu => CustomMenus}/examples/FlyoutDemo.tsx (87%) create mode 100644 packages/react-core/src/demos/CustomMenus/examples/OptionsMenuDemo.tsx rename packages/react-core/src/demos/{ComposableMenu => CustomMenus}/examples/TreeViewMenuDemo.tsx (94%) rename packages/react-core/src/demos/{ComposableMenu => CustomMenus}/examples/avatarImg.svg (100%) rename packages/react-core/src/demos/{ComposableMenu => CustomMenus}/examples/pf-logo-small.svg (100%) diff --git a/packages/react-core/src/components/Dropdown/Dropdown.tsx b/packages/react-core/src/components/Dropdown/Dropdown.tsx index 882decd404d..5c5a7222b42 100644 --- a/packages/react-core/src/components/Dropdown/Dropdown.tsx +++ b/packages/react-core/src/components/Dropdown/Dropdown.tsx @@ -43,7 +43,7 @@ export interface DropdownProps extends MenuProps, OUIAProps { /** Function callback called when user selects item. */ onSelect?: (event?: React.MouseEvent, itemId?: string | number) => void; /** Callback to allow the dropdown component to change the open state of the menu. - * Triggered by clicking outside of the menu, or by pressing either tab or escape (or specificed in onOpenChangeKeys). */ + * Triggered by clicking outside of the menu, or by pressing any keys specificed in onOpenChangeKeys. */ onOpenChange?: (isOpen: boolean) => void; /** Indicates if the menu should be without the outer box-shadow. */ isPlain?: boolean; @@ -59,7 +59,7 @@ export interface DropdownProps extends MenuProps, OUIAProps { zIndex?: number; /** Additional properties to pass to the Popper */ popperProps?: DropdownPopperProps; - /** Keys that trigger onOpenChange, defaults to tab and escape. */ + /** Keys that trigger onOpenChange, defaults to tab and escape. Escape should always be included in the array, while Tab may be omitted if the menu contains non-menu items that are focusable. */ onOpenChangeKeys?: string[]; } diff --git a/packages/react-core/src/components/Menu/MenuContainer.tsx b/packages/react-core/src/components/Menu/MenuContainer.tsx index a04e20c3712..283ebe0e423 100644 --- a/packages/react-core/src/components/Menu/MenuContainer.tsx +++ b/packages/react-core/src/components/Menu/MenuContainer.tsx @@ -35,11 +35,8 @@ export const MenuContainer: React.FunctionComponent = ({ onOpenChange, zIndex = 9999, popperProps, - onOpenChangeKeys = ['Escape', 'Tab'], - ...props + onOpenChangeKeys = ['Escape', 'Tab'] }: MenuContainerProps) => { - const containerRef = React.useRef(); - React.useEffect(() => { const handleMenuKeys = (event: KeyboardEvent) => { // Close the menu on tab or escape if onOpenChange is provided @@ -83,18 +80,15 @@ export const MenuContainer: React.FunctionComponent = ({ }, [isOpen, menuRef, onOpenChange, onOpenChangeKeys, toggleRef]); return ( -
- -
+ ); }; MenuContainer.displayName = 'MenuContainer'; diff --git a/packages/react-core/src/demos/ComposableMenu/examples/OptionsMenuDemo.tsx b/packages/react-core/src/demos/ComposableMenu/examples/OptionsMenuDemo.tsx deleted file mode 100644 index 51828377cca..00000000000 --- a/packages/react-core/src/demos/ComposableMenu/examples/OptionsMenuDemo.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import React from 'react'; -import { MenuToggle, Divider, Dropdown, DropdownList, DropdownItem, DropdownGroup } from '@patternfly/react-core'; - -export const OptionsMenuDemo: React.FunctionComponent = () => { - const [isOpen, setIsOpen] = React.useState(false); - const [selected, setSelected] = React.useState(''); - const menuRef = React.useRef(); - - 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(); - } - }, 0); - setIsOpen(!isOpen); - }; - - return ( - setIsOpen(isOpen)} - toggle={(toggleRef) => ( - - Options menu - - )} - ref={menuRef} - id="options-menu" - selected={selected} - onSelect={(_ev, itemId) => itemId && setSelected(itemId.toString())} - > - - - Option 1 - - - Disabled Option - - - - - - Option 1 - - - Option 2 - - - - - - - - Option 1 - - - Option 2 - - - - - - ); -}; diff --git a/packages/react-core/src/demos/ComposableMenu/ApplicationLauncher.md b/packages/react-core/src/demos/CustomMenus/ApplicationLauncher.md similarity index 100% rename from packages/react-core/src/demos/ComposableMenu/ApplicationLauncher.md rename to packages/react-core/src/demos/CustomMenus/ApplicationLauncher.md diff --git a/packages/react-core/src/demos/ComposableMenu/ContextSelector.md b/packages/react-core/src/demos/CustomMenus/ContextSelector.md similarity index 100% rename from packages/react-core/src/demos/ComposableMenu/ContextSelector.md rename to packages/react-core/src/demos/CustomMenus/ContextSelector.md diff --git a/packages/react-core/src/demos/ComposableMenu/CustomMenus.md b/packages/react-core/src/demos/CustomMenus/CustomMenus.md similarity index 100% rename from packages/react-core/src/demos/ComposableMenu/CustomMenus.md rename to packages/react-core/src/demos/CustomMenus/CustomMenus.md diff --git a/packages/react-core/src/demos/ComposableMenu/OptionsMenu.md b/packages/react-core/src/demos/CustomMenus/OptionsMenu.md similarity index 79% rename from packages/react-core/src/demos/ComposableMenu/OptionsMenu.md rename to packages/react-core/src/demos/CustomMenus/OptionsMenu.md index 1f6726b690e..d645d6fa05c 100644 --- a/packages/react-core/src/demos/ComposableMenu/OptionsMenu.md +++ b/packages/react-core/src/demos/CustomMenus/OptionsMenu.md @@ -7,7 +7,7 @@ source: react-demos import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon'; -As the `OptionsMenu` component is now deprecated, an options menu may now be built using the new suite of `Menu` components. This is showcased below in the example using the new `Dropdpown` component built off of `Menu`. +As the `OptionsMenu` component is now deprecated, an options menu may now be built using the new suite of `Menu` components. This is showcased below in the example using the new `Select` component built off of `Menu`. ### Options menu diff --git a/packages/react-core/src/demos/ComposableMenu/examples/ActionsMenuDemo.tsx b/packages/react-core/src/demos/CustomMenus/examples/ActionsMenuDemo.tsx similarity index 77% rename from packages/react-core/src/demos/ComposableMenu/examples/ActionsMenuDemo.tsx rename to packages/react-core/src/demos/CustomMenus/examples/ActionsMenuDemo.tsx index 8f8e32c26d3..92f36364537 100644 --- a/packages/react-core/src/demos/ComposableMenu/examples/ActionsMenuDemo.tsx +++ b/packages/react-core/src/demos/CustomMenus/examples/ActionsMenuDemo.tsx @@ -1,12 +1,5 @@ import React from 'react'; -import { - MenuToggle, - MenuItemAction, - Dropdown, - DropdownGroup, - DropdownList, - DropdownItem -} from '@patternfly/react-core'; +import { MenuToggle, MenuItemAction, Select, SelectGroup, SelectList, SelectOption } from '@patternfly/react-core'; import BarsIcon from '@patternfly/react-icons/dist/esm/icons/bars-icon'; import ClipboardIcon from '@patternfly/react-icons/dist/esm/icons/clipboard-icon'; import CodeBranchIcon from '@patternfly/react-icons/dist/esm/icons/code-branch-icon'; @@ -29,19 +22,12 @@ export const ActionsMenuDemo: React.FunctionComponent = () => { } }; - const onToggleClick = (ev: React.MouseEvent) => { - ev.stopPropagation(); - setTimeout(() => { - if (menuRef.current) { - const firstElement = menuRef.current.querySelector('li > button:not(:disabled), li > a:not(:disabled)'); - firstElement && (firstElement as HTMLElement).focus(); - } - }, 0); + const onToggleClick = () => { setIsOpen(!isOpen); }; return ( - ( @@ -54,9 +40,9 @@ export const ActionsMenuDemo: React.FunctionComponent = () => { onSelect={onSelect} onOpenChange={(isOpen) => setIsOpen(isOpen)} > - - - + + { itemId={0} > Item 1 - - + } actionId="alert" aria-label="Alert" />} @@ -80,24 +66,24 @@ export const ActionsMenuDemo: React.FunctionComponent = () => { itemId={1} > Item 2 - - + } actionId="copy" aria-label="Copy" />} itemId={2} > Item 3 - - + } actionId="expand" aria-label="Expand" />} description="This is a description" itemId={3} > Item 4 - - - - + + + + ); }; diff --git a/packages/react-core/src/demos/ComposableMenu/examples/ApplicationLauncherDemo.tsx b/packages/react-core/src/demos/CustomMenus/examples/ApplicationLauncherDemo.tsx similarity index 98% rename from packages/react-core/src/demos/ComposableMenu/examples/ApplicationLauncherDemo.tsx rename to packages/react-core/src/demos/CustomMenus/examples/ApplicationLauncherDemo.tsx index c443eee4f3b..35603c3ca1f 100644 --- a/packages/react-core/src/demos/ComposableMenu/examples/ApplicationLauncherDemo.tsx +++ b/packages/react-core/src/demos/CustomMenus/examples/ApplicationLauncherDemo.tsx @@ -23,8 +23,7 @@ export const ApplicationLauncherDemo: React.FunctionComponent = () => { const [filteredIds, setFilteredIds] = React.useState(['*']); const menuRef = React.useRef(null); - const onToggleClick = (ev: React.MouseEvent) => { - ev.stopPropagation(); + const onToggleClick = () => { setTimeout(() => { if (menuRef.current) { const firstElement = menuRef.current.querySelector( diff --git a/packages/react-core/src/demos/ComposableMenu/examples/ComposableDateSelect.tsx b/packages/react-core/src/demos/CustomMenus/examples/ComposableDateSelect.tsx similarity index 100% rename from packages/react-core/src/demos/ComposableMenu/examples/ComposableDateSelect.tsx rename to packages/react-core/src/demos/CustomMenus/examples/ComposableDateSelect.tsx diff --git a/packages/react-core/src/demos/ComposableMenu/examples/ComposableDropdwnVariants.tsx b/packages/react-core/src/demos/CustomMenus/examples/ComposableDropdwnVariants.tsx similarity index 100% rename from packages/react-core/src/demos/ComposableMenu/examples/ComposableDropdwnVariants.tsx rename to packages/react-core/src/demos/CustomMenus/examples/ComposableDropdwnVariants.tsx diff --git a/packages/react-core/src/demos/ComposableMenu/examples/ContextSelectorDemo.tsx b/packages/react-core/src/demos/CustomMenus/examples/ContextSelectorDemo.tsx similarity index 91% rename from packages/react-core/src/demos/ComposableMenu/examples/ContextSelectorDemo.tsx rename to packages/react-core/src/demos/CustomMenus/examples/ContextSelectorDemo.tsx index 9d0c28dfe76..e7ceaaaeaf1 100644 --- a/packages/react-core/src/demos/ComposableMenu/examples/ContextSelectorDemo.tsx +++ b/packages/react-core/src/demos/CustomMenus/examples/ContextSelectorDemo.tsx @@ -60,16 +60,7 @@ export const ContextSelectorDemo: React.FunctionComponent = () => { const menuRef = React.useRef(null); const menuFooterBtnRef = React.useRef(null); - 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(); - } - }, 0); + const onToggleClick = () => { setIsOpen(!isOpen); }; diff --git a/packages/react-core/src/demos/ComposableMenu/examples/DateSelectDemo.tsx b/packages/react-core/src/demos/CustomMenus/examples/DateSelectDemo.tsx similarity index 84% rename from packages/react-core/src/demos/ComposableMenu/examples/DateSelectDemo.tsx rename to packages/react-core/src/demos/CustomMenus/examples/DateSelectDemo.tsx index b5a8949b434..63cb9077eee 100644 --- a/packages/react-core/src/demos/ComposableMenu/examples/DateSelectDemo.tsx +++ b/packages/react-core/src/demos/CustomMenus/examples/DateSelectDemo.tsx @@ -6,14 +6,7 @@ export const DateSelectDemo: React.FunctionComponent = () => { const [selected, setSelected] = React.useState(0); const menuRef = React.useRef(); - const onToggleClick = (ev: React.MouseEvent) => { - ev.stopPropagation(); // Stop handleClickOutside from handling - setTimeout(() => { - if (menuRef.current) { - const firstElement = menuRef.current.querySelector('li > button:not(:disabled)'); - firstElement && (firstElement as HTMLElement).focus(); - } - }, 0); + const onToggleClick = () => { setIsOpen(!isOpen); }; @@ -74,7 +67,10 @@ export const DateSelectDemo: React.FunctionComponent = () => {
)} ref={menuRef} - onSelect={(_ev, itemId) => setSelected(itemId as number)} + onSelect={(_ev, itemId) => { + setSelected(itemId as number); + setIsOpen(false); + }} selected={selected} > diff --git a/packages/react-core/src/demos/ComposableMenu/examples/DrilldownMenuDemo.tsx b/packages/react-core/src/demos/CustomMenus/examples/DrilldownMenuDemo.tsx similarity index 95% rename from packages/react-core/src/demos/ComposableMenu/examples/DrilldownMenuDemo.tsx rename to packages/react-core/src/demos/CustomMenus/examples/DrilldownMenuDemo.tsx index 7b27ad2afc5..7347dc24dbd 100644 --- a/packages/react-core/src/demos/ComposableMenu/examples/DrilldownMenuDemo.tsx +++ b/packages/react-core/src/demos/CustomMenus/examples/DrilldownMenuDemo.tsx @@ -27,14 +27,7 @@ export const DrilldownMenuDemo: React.FunctionComponent = () => { const toggleRef = React.useRef(null); const menuRef = React.useRef(null); - 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)'); - firstElement && (firstElement as HTMLElement).focus(); - } - }, 0); + const onToggleClick = () => { setIsOpen(!isOpen); setMenuDrilledIn([]); setDrilldownPath([]); diff --git a/packages/react-core/src/demos/ComposableMenu/examples/FavoritesDemo.tsx b/packages/react-core/src/demos/CustomMenus/examples/FavoritesDemo.tsx similarity index 92% rename from packages/react-core/src/demos/ComposableMenu/examples/FavoritesDemo.tsx rename to packages/react-core/src/demos/CustomMenus/examples/FavoritesDemo.tsx index 003443b9859..1604a1f8e49 100644 --- a/packages/react-core/src/demos/ComposableMenu/examples/FavoritesDemo.tsx +++ b/packages/react-core/src/demos/CustomMenus/examples/FavoritesDemo.tsx @@ -6,14 +6,7 @@ export const FavoritesDemo: React.FunctionComponent = () => { const [favorites, setFavorites] = React.useState([]); const menuRef = React.useRef(null); - const onToggleClick = (ev: React.MouseEvent) => { - ev.stopPropagation(); - setTimeout(() => { - if (menuRef.current) { - const firstElement = menuRef.current.querySelector('li > button:not(:disabled)'); - firstElement && (firstElement as HTMLElement).focus(); - } - }, 0); + const onToggleClick = () => { setIsOpen(!isOpen); }; diff --git a/packages/react-core/src/demos/ComposableMenu/examples/FlyoutDemo.tsx b/packages/react-core/src/demos/CustomMenus/examples/FlyoutDemo.tsx similarity index 87% rename from packages/react-core/src/demos/ComposableMenu/examples/FlyoutDemo.tsx rename to packages/react-core/src/demos/CustomMenus/examples/FlyoutDemo.tsx index 03ae255d10a..a4bec252174 100644 --- a/packages/react-core/src/demos/ComposableMenu/examples/FlyoutDemo.tsx +++ b/packages/react-core/src/demos/CustomMenus/examples/FlyoutDemo.tsx @@ -40,14 +40,7 @@ export const FlyoutDemo: React.FunctionComponent = () => { curFlyout = {curFlyout}; } - 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)'); - firstElement && (firstElement as HTMLElement).focus(); - } - }, 0); + const onToggleClick = () => { setIsOpen(!isOpen); }; diff --git a/packages/react-core/src/demos/CustomMenus/examples/OptionsMenuDemo.tsx b/packages/react-core/src/demos/CustomMenus/examples/OptionsMenuDemo.tsx new file mode 100644 index 00000000000..a0cc9fa4372 --- /dev/null +++ b/packages/react-core/src/demos/CustomMenus/examples/OptionsMenuDemo.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { MenuToggle, Divider, Select, SelectList, SelectOption, SelectGroup } from '@patternfly/react-core'; + +export const OptionsMenuDemo: React.FunctionComponent = () => { + const [isOpen, setIsOpen] = React.useState(false); + const [selected, setSelected] = React.useState(''); + const menuRef = React.useRef(); + + const onToggleClick = () => { + setIsOpen(!isOpen); + }; + + return ( + + ); +}; diff --git a/packages/react-core/src/demos/ComposableMenu/examples/TreeViewMenuDemo.tsx b/packages/react-core/src/demos/CustomMenus/examples/TreeViewMenuDemo.tsx similarity index 94% rename from packages/react-core/src/demos/ComposableMenu/examples/TreeViewMenuDemo.tsx rename to packages/react-core/src/demos/CustomMenus/examples/TreeViewMenuDemo.tsx index a2de9b0f68d..e46c9dc3837 100644 --- a/packages/react-core/src/demos/ComposableMenu/examples/TreeViewMenuDemo.tsx +++ b/packages/react-core/src/demos/CustomMenus/examples/TreeViewMenuDemo.tsx @@ -166,14 +166,7 @@ export const TreeViewMenuDemo: React.FunctionComponent = () => { ); }; - 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)'); - firstElement && (firstElement as HTMLElement).focus(); - } - }, 0); + const onToggleClick = () => { setIsOpen(!isOpen); }; diff --git a/packages/react-core/src/demos/ComposableMenu/examples/avatarImg.svg b/packages/react-core/src/demos/CustomMenus/examples/avatarImg.svg similarity index 100% rename from packages/react-core/src/demos/ComposableMenu/examples/avatarImg.svg rename to packages/react-core/src/demos/CustomMenus/examples/avatarImg.svg diff --git a/packages/react-core/src/demos/ComposableMenu/examples/pf-logo-small.svg b/packages/react-core/src/demos/CustomMenus/examples/pf-logo-small.svg similarity index 100% rename from packages/react-core/src/demos/ComposableMenu/examples/pf-logo-small.svg rename to packages/react-core/src/demos/CustomMenus/examples/pf-logo-small.svg From a0dcf5742898b376e04464ddb885fec05e76e48d Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Mon, 8 May 2023 16:15:40 -0400 Subject: [PATCH 05/12] add beta to keys --- packages/react-core/src/components/Dropdown/Dropdown.tsx | 2 +- packages/react-core/src/components/Menu/MenuContainer.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-core/src/components/Dropdown/Dropdown.tsx b/packages/react-core/src/components/Dropdown/Dropdown.tsx index 5c5a7222b42..105818abc83 100644 --- a/packages/react-core/src/components/Dropdown/Dropdown.tsx +++ b/packages/react-core/src/components/Dropdown/Dropdown.tsx @@ -59,7 +59,7 @@ export interface DropdownProps extends MenuProps, OUIAProps { zIndex?: number; /** Additional properties to pass to the Popper */ popperProps?: DropdownPopperProps; - /** Keys that trigger onOpenChange, defaults to tab and escape. Escape should always be included in the array, while Tab may be omitted if the menu contains non-menu items that are focusable. */ + /** @beta Keys that trigger onOpenChange, defaults to tab and escape. Escape should always be included in the array, while Tab may be omitted if the menu contains non-menu items that are focusable. */ onOpenChangeKeys?: string[]; } diff --git a/packages/react-core/src/components/Menu/MenuContainer.tsx b/packages/react-core/src/components/Menu/MenuContainer.tsx index 283ebe0e423..c337ae342f2 100644 --- a/packages/react-core/src/components/Menu/MenuContainer.tsx +++ b/packages/react-core/src/components/Menu/MenuContainer.tsx @@ -15,7 +15,7 @@ export interface MenuContainerProps { /** Callback to change the open state of the menu. * Triggered by clicking outside of the menu, or by pressing either tab or escape (or keys specified in onOpenChangeKeys). */ onOpenChange?: (isOpen: boolean) => void; - /** Keys that trigger onOpenChange, defaults to tab and escape. */ + /** @beta Keys that trigger onOpenChange, defaults to tab and escape. */ onOpenChangeKeys?: string[]; /** z-index of the dropdown menu */ zIndex?: number; From 517628b567dc3b54432ad3d513040ad38604a8c4 Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Tue, 9 May 2023 11:02:06 -0400 Subject: [PATCH 06/12] update prop desc --- packages/react-core/src/components/Menu/MenuContainer.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-core/src/components/Menu/MenuContainer.tsx b/packages/react-core/src/components/Menu/MenuContainer.tsx index c337ae342f2..1df7570cd9d 100644 --- a/packages/react-core/src/components/Menu/MenuContainer.tsx +++ b/packages/react-core/src/components/Menu/MenuContainer.tsx @@ -13,9 +13,9 @@ export interface MenuContainerProps { /** Flag to indicate if menu is opened.*/ isOpen: boolean; /** Callback to change the open state of the menu. - * Triggered by clicking outside of the menu, or by pressing either tab or escape (or keys specified in onOpenChangeKeys). */ + * Triggered by clicking outside of the menu, or by pressing any keys specificed in onOpenChangeKeys. */ onOpenChange?: (isOpen: boolean) => void; - /** @beta Keys that trigger onOpenChange, defaults to tab and escape. */ + /** @beta Keys that trigger onOpenChange, defaults to tab and escape. Escape should always be included in the array, while Tab may be omitted if the menu contains non-menu items that are focusable. */ onOpenChangeKeys?: string[]; /** z-index of the dropdown menu */ zIndex?: number; From fbf62d2114b4f493c021ec1ae63b0ea3120f3153 Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Thu, 11 May 2023 13:20:49 -0400 Subject: [PATCH 07/12] add prop tables, fix focusing bug --- packages/react-core/src/components/Menu/Menu.tsx | 4 ++-- .../src/demos/CustomMenus/ApplicationLauncher.md | 13 +++++++++++++ .../src/demos/CustomMenus/ContextSelector.md | 14 ++++++++++++++ .../src/demos/CustomMenus/OptionsMenu.md | 1 + 4 files changed, 30 insertions(+), 2 deletions(-) diff --git a/packages/react-core/src/components/Menu/Menu.tsx b/packages/react-core/src/components/Menu/Menu.tsx index 4361a8177cb..02cd259168a 100644 --- a/packages/react-core/src/components/Menu/Menu.tsx +++ b/packages/react-core/src/components/Menu/Menu.tsx @@ -301,8 +301,8 @@ class MenuBase extends React.Component { (document.activeElement.closest('ol') && document.activeElement.closest('ol').firstChild === element) } getFocusableElement={(navigableElement: Element) => - (navigableElement.tagName === 'DIV' && navigableElement.querySelector('input')) || // for MenuSearchInput - ((navigableElement.firstChild as Element).tagName === 'LABEL' && + (navigableElement?.tagName === 'DIV' && navigableElement.querySelector('input')) || // for MenuSearchInput + ((navigableElement.firstChild as Element)?.tagName === 'LABEL' && navigableElement.querySelector('input')) || // for MenuItem checkboxes (navigableElement.firstChild as Element) } diff --git a/packages/react-core/src/demos/CustomMenus/ApplicationLauncher.md b/packages/react-core/src/demos/CustomMenus/ApplicationLauncher.md index b295d709af2..73c95b9994d 100644 --- a/packages/react-core/src/demos/CustomMenus/ApplicationLauncher.md +++ b/packages/react-core/src/demos/CustomMenus/ApplicationLauncher.md @@ -3,6 +3,19 @@ id: Application launcher section: components subsection: menus source: react-demos +propComponents: + [ + 'MenuToggle', + 'MenuSearch', + 'MenuSearchInput', + 'Tooltip', + 'Divider', + 'SearchInput', + 'Dropdown', + 'DropdownGroup', + 'DropdownList', + 'DropdownItem' + ] --- import ThIcon from '@patternfly/react-icons/dist/esm/icons/th-icon'; diff --git a/packages/react-core/src/demos/CustomMenus/ContextSelector.md b/packages/react-core/src/demos/CustomMenus/ContextSelector.md index dac4600597d..2ca9c175ee9 100644 --- a/packages/react-core/src/demos/CustomMenus/ContextSelector.md +++ b/packages/react-core/src/demos/CustomMenus/ContextSelector.md @@ -3,6 +3,20 @@ id: Context selector section: components subsection: menus source: react-demos +propComponents: + [ + 'MenuToggle', + 'MenuFooter', + 'MenuSearch', + 'MenuSearchInput', + 'Divider', + 'InputGroup', + 'SearchInput', + 'Dropdown', + 'DropdownGroup', + 'DropdownList', + 'DropdownItem' + ] --- import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon'; diff --git a/packages/react-core/src/demos/CustomMenus/OptionsMenu.md b/packages/react-core/src/demos/CustomMenus/OptionsMenu.md index d645d6fa05c..4b87b37ce28 100644 --- a/packages/react-core/src/demos/CustomMenus/OptionsMenu.md +++ b/packages/react-core/src/demos/CustomMenus/OptionsMenu.md @@ -3,6 +3,7 @@ id: Options menu section: components subsection: menus source: react-demos +propComponents: ['MenuToggle', 'Divider', 'Select', 'SelectList', 'SelectOption', 'SelectGroup'] --- import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon'; From 99ec946f909242428a3f361ab5ae572f305b45b3 Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Mon, 15 May 2023 10:16:38 -0400 Subject: [PATCH 08/12] content updates --- .../react-core/src/demos/CustomMenus/ApplicationLauncher.md | 2 +- .../react-core/src/demos/CustomMenus/ContextSelector.md | 2 +- packages/react-core/src/demos/CustomMenus/CustomMenus.md | 6 +++--- packages/react-core/src/demos/CustomMenus/OptionsMenu.md | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/react-core/src/demos/CustomMenus/ApplicationLauncher.md b/packages/react-core/src/demos/CustomMenus/ApplicationLauncher.md index 73c95b9994d..02ca8be6706 100644 --- a/packages/react-core/src/demos/CustomMenus/ApplicationLauncher.md +++ b/packages/react-core/src/demos/CustomMenus/ApplicationLauncher.md @@ -21,7 +21,7 @@ propComponents: import ThIcon from '@patternfly/react-icons/dist/esm/icons/th-icon'; import pfIcon from './examples/pf-logo-small.svg'; -As the `ApplicationLauncher` component is now deprecated, an application launcher may now be built using the new suite of `Menu` components. This is showcased below in the example using the new `Dropdpown` component built off of `Menu`. +As the application launcher component is now deprecated, an application launcher may now be built using the new suite of menu components. This is showcased in the following example, which uses the new dropdown component that is built off of menu. ### Application launcher diff --git a/packages/react-core/src/demos/CustomMenus/ContextSelector.md b/packages/react-core/src/demos/CustomMenus/ContextSelector.md index 2ca9c175ee9..e2a160c8101 100644 --- a/packages/react-core/src/demos/CustomMenus/ContextSelector.md +++ b/packages/react-core/src/demos/CustomMenus/ContextSelector.md @@ -21,7 +21,7 @@ propComponents: import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon'; -As the `ContextSelector` component is now deprecated, a context selector may now be built using the new suite of `Menu` components. This is showcased below in the example using the new `Dropdpown` component built off of `Menu`. +As the context selector component is now deprecated, a context selector may now be built using the new suite of menu components. This is showcased in the following example, which uses the new dropdown component that is built off of menu. ### Context selector diff --git a/packages/react-core/src/demos/CustomMenus/CustomMenus.md b/packages/react-core/src/demos/CustomMenus/CustomMenus.md index 3d7f774faae..6c2c7e5989d 100644 --- a/packages/react-core/src/demos/CustomMenus/CustomMenus.md +++ b/packages/react-core/src/demos/CustomMenus/CustomMenus.md @@ -27,7 +27,7 @@ import styles from '@patternfly/react-styles/css/components/Menu/menu'; ## Demos -Custom menus can be constructed using a composable approach by combining the [Menu](/components/menus/menu) and [Menu toggle](/components/menus/menu-toggle) components in unique ways. [Dropdown](/components/menus/dropdown), [Select](/components/menus/select), or [MenuContainer](/components/menus/menu#menucontainer) may be used in combination with menu components to handle basic keyboard inputs, or menu components may be connected manually through our undocumented internal [popper.js](https://popper.js.org/) wrapper component called Popper. +Custom menus can be constructed using a composable approach by combining the [menu](/components/menus/menu) and [menu toggle](/components/menus/menu-toggle) components in unique ways. To handle basic keyboard inputs, [dropdown](/components/menus/dropdown), [select](/components/menus/select), or [``](/components/menus/menu#menucontainer) components may be used in combination with menu components. Additionally, menu components may be connected to each other manually through our undocumented internal [popper.js](https://popper.js.org/) wrapper component called Popper. ### Actions menu @@ -49,7 +49,7 @@ Custom menus can be constructed using a composable approach by combining the [Me ### Tree view menu -When rendering a menu-like element that does not contain MenuItem components, [Panel](/components/panel) allows more flexible control and customization. +When rendering a menu-like element that does not contain `` components, [panel](/components/panel) allows more flexible control and customization. ```ts file="./examples/TreeViewMenuDemo.tsx" @@ -57,7 +57,7 @@ When rendering a menu-like element that does not contain MenuItem components, [P ### Flyout menu -The flyout will automatically position to the left or top if it would otherwise go outside the window. The menu must be placed in a container outside the main content like Popper, [Popover](/components/popover) or [Tooltip](/components/tooltip) since it may go over the side nav. +The flyout will automatically position to the left or top if it would otherwise go outside the window. The menu must be placed in a container outside the main content like Popper, [popover](/components/popover) or [tooltip](/components/tooltip), since it may go over the side nav. ```ts isBeta file="./examples/FlyoutDemo.tsx" diff --git a/packages/react-core/src/demos/CustomMenus/OptionsMenu.md b/packages/react-core/src/demos/CustomMenus/OptionsMenu.md index 4b87b37ce28..32dd6d47454 100644 --- a/packages/react-core/src/demos/CustomMenus/OptionsMenu.md +++ b/packages/react-core/src/demos/CustomMenus/OptionsMenu.md @@ -8,7 +8,7 @@ propComponents: ['MenuToggle', 'Divider', 'Select', 'SelectList', 'SelectOption' import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon'; -As the `OptionsMenu` component is now deprecated, an options menu may now be built using the new suite of `Menu` components. This is showcased below in the example using the new `Select` component built off of `Menu`. +As the `` component is now deprecated, an options menu may now be built using the new suite of menu components. This is showcased in the following example, which uses the new select component that is built off of menu. ### Options menu From d041ff1e272ea6257b856fcf29175ca28aa1a267 Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Wed, 17 May 2023 12:39:20 -0400 Subject: [PATCH 09/12] content updates --- .../demos/CustomMenus/ApplicationLauncher.md | 4 ++-- .../src/demos/CustomMenus/ContextSelector.md | 4 ++-- .../src/demos/CustomMenus/CustomMenus.md | 18 ++++++++++-------- .../src/demos/CustomMenus/OptionsMenu.md | 2 +- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/react-core/src/demos/CustomMenus/ApplicationLauncher.md b/packages/react-core/src/demos/CustomMenus/ApplicationLauncher.md index 02ca8be6706..252a0348a88 100644 --- a/packages/react-core/src/demos/CustomMenus/ApplicationLauncher.md +++ b/packages/react-core/src/demos/CustomMenus/ApplicationLauncher.md @@ -21,9 +21,9 @@ propComponents: import ThIcon from '@patternfly/react-icons/dist/esm/icons/th-icon'; import pfIcon from './examples/pf-logo-small.svg'; -As the application launcher component is now deprecated, an application launcher may now be built using the new suite of menu components. This is showcased in the following example, which uses the new dropdown component that is built off of menu. +As the application launcher component is now deprecated, an application launcher may now be built using the new suite of menu components. This is showcased in the following demo, which uses the new [dropdown](/components/menus/dropdown) component that is built off of menu. -### Application launcher +### Application launcher menu ```ts file="./examples/ApplicationLauncherDemo.tsx" diff --git a/packages/react-core/src/demos/CustomMenus/ContextSelector.md b/packages/react-core/src/demos/CustomMenus/ContextSelector.md index e2a160c8101..2f3fb252a98 100644 --- a/packages/react-core/src/demos/CustomMenus/ContextSelector.md +++ b/packages/react-core/src/demos/CustomMenus/ContextSelector.md @@ -21,9 +21,9 @@ propComponents: import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon'; -As the context selector component is now deprecated, a context selector may now be built using the new suite of menu components. This is showcased in the following example, which uses the new dropdown component that is built off of menu. +As the context selector component is now deprecated, a context selector may now be built using the new suite of menu components. This is showcased in the following demo, which uses the new [dropdown](/components/menus/dropdown) component that is built off of menu. -### Context selector +### Context selector menu ```ts file="./examples/ContextSelectorDemo.tsx" diff --git a/packages/react-core/src/demos/CustomMenus/CustomMenus.md b/packages/react-core/src/demos/CustomMenus/CustomMenus.md index 6c2c7e5989d..03531f3b6af 100644 --- a/packages/react-core/src/demos/CustomMenus/CustomMenus.md +++ b/packages/react-core/src/demos/CustomMenus/CustomMenus.md @@ -25,23 +25,25 @@ import avatarImg from './examples/avatarImg.svg'; import { css } from '@patternfly/react-styles'; import styles from '@patternfly/react-styles/css/components/Menu/menu'; -## Demos +## Examples -Custom menus can be constructed using a composable approach by combining the [menu](/components/menus/menu) and [menu toggle](/components/menus/menu-toggle) components in unique ways. To handle basic keyboard inputs, [dropdown](/components/menus/dropdown), [select](/components/menus/select), or [``](/components/menus/menu#menucontainer) components may be used in combination with menu components. Additionally, menu components may be connected to each other manually through our undocumented internal [popper.js](https://popper.js.org/) wrapper component called Popper. +Custom menus can be constructed using a composable approach by combining the [menu](/components/menus/menu) and [menu toggle](/components/menus/menu-toggle) components in unique ways. To handle basic keyboard inputs, [dropdown](/components/menus/dropdown), [select](/components/menus/select), or [``](/components/menus/menu#menucontainer) components may be used in combination with menu components. -### Actions menu +Additionally, menu components may be connected to each other manually through our undocumented internal [popper.js](https://popper.js.org/) wrapper component called Popper. + +### With actions ```ts file="./examples/ActionsMenuDemo.tsx" ``` -### Favorites menu +### With favorites ```ts file="./examples/FavoritesDemo.tsx" ``` -### Drilldown menu +### With drilldown ```ts isBeta file="./examples/DrilldownMenuDemo.tsx" @@ -63,19 +65,19 @@ The flyout will automatically position to the left or top if it would otherwise ``` -### Application launcher +### Application launcher menu ```ts file="./examples/ApplicationLauncherDemo.tsx" ``` -### Context selector +### Context selector menu ```ts file="./examples/ContextSelectorDemo.tsx" ``` -### Date select +### Date select menu ```ts file="./examples/DateSelectDemo.tsx" diff --git a/packages/react-core/src/demos/CustomMenus/OptionsMenu.md b/packages/react-core/src/demos/CustomMenus/OptionsMenu.md index 32dd6d47454..d68e28fd488 100644 --- a/packages/react-core/src/demos/CustomMenus/OptionsMenu.md +++ b/packages/react-core/src/demos/CustomMenus/OptionsMenu.md @@ -8,7 +8,7 @@ propComponents: ['MenuToggle', 'Divider', 'Select', 'SelectList', 'SelectOption' import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon'; -As the `` component is now deprecated, an options menu may now be built using the new suite of menu components. This is showcased in the following example, which uses the new select component that is built off of menu. +As the `` component is now deprecated, an options menu may now be built using the new suite of menu components. This is showcased in the following demo, which uses the new [select](/components/menus/select) component that is built off of menu. ### Options menu From 2d90d99043313e440310c6ccb50d93f893d20787 Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Wed, 17 May 2023 17:18:39 -0400 Subject: [PATCH 10/12] move prop, add popper props --- .../src/components/Dropdown/Dropdown.tsx | 4 ++-- .../src/components/Menu/MenuContainer.tsx | 18 ++++++++++++++++-- .../src/components/Menu/examples/Menu.md | 3 ++- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/packages/react-core/src/components/Dropdown/Dropdown.tsx b/packages/react-core/src/components/Dropdown/Dropdown.tsx index 105818abc83..6e46b345ec6 100644 --- a/packages/react-core/src/components/Dropdown/Dropdown.tsx +++ b/packages/react-core/src/components/Dropdown/Dropdown.tsx @@ -45,6 +45,8 @@ export interface DropdownProps extends MenuProps, OUIAProps { /** Callback to allow the dropdown component to change the open state of the menu. * Triggered by clicking outside of the menu, or by pressing any keys specificed in onOpenChangeKeys. */ onOpenChange?: (isOpen: boolean) => void; + /** @beta Keys that trigger onOpenChange, defaults to tab and escape. Escape should always be included in the array, while Tab may be omitted if the menu contains non-menu items that are focusable. */ + onOpenChangeKeys?: string[]; /** Indicates if the menu should be without the outer box-shadow. */ isPlain?: boolean; /** Indicates if the menu should be scrollable. */ @@ -59,8 +61,6 @@ export interface DropdownProps extends MenuProps, OUIAProps { zIndex?: number; /** Additional properties to pass to the Popper */ popperProps?: DropdownPopperProps; - /** @beta Keys that trigger onOpenChange, defaults to tab and escape. Escape should always be included in the array, while Tab may be omitted if the menu contains non-menu items that are focusable. */ - onOpenChangeKeys?: string[]; } const DropdownBase: React.FunctionComponent = ({ diff --git a/packages/react-core/src/components/Menu/MenuContainer.tsx b/packages/react-core/src/components/Menu/MenuContainer.tsx index 1df7570cd9d..b8caead0ef4 100644 --- a/packages/react-core/src/components/Menu/MenuContainer.tsx +++ b/packages/react-core/src/components/Menu/MenuContainer.tsx @@ -1,6 +1,20 @@ import React from 'react'; -import { Popper, PopperProps } from '../../helpers/Popper/Popper'; +import { Popper } from '../../helpers/Popper/Popper'; +export interface MenuPopperProps { + /** Vertical direction of the popper. If enableFlip is set to true, this will set the initial direction before the popper flips. */ + direction?: 'up' | 'down'; + /** Horizontal position of the popper */ + position?: 'right' | 'left' | 'center'; + /** Custom width of the popper. If the value is "trigger", it will set the width to the dropdown toggle's width */ + width?: string | 'trigger'; + /** Minimum width of the popper. If the value is "trigger", it will set the min width to the dropdown toggle's width */ + minWidth?: string | 'trigger'; + /** Maximum width of the popper. If the value is "trigger", it will set the max width to the dropdown toggle's width */ + maxWidth?: string | 'trigger'; + /** Enable to flip the popper when it reaches the boundary */ + enableFlip?: boolean; +} export interface MenuContainerProps { /** Menu to be rendered */ menu: React.ReactElement>; @@ -20,7 +34,7 @@ export interface MenuContainerProps { /** z-index of the dropdown menu */ zIndex?: number; /** Additional properties to pass to the Popper */ - popperProps?: Partial; + popperProps?: MenuPopperProps; } /** diff --git a/packages/react-core/src/components/Menu/examples/Menu.md b/packages/react-core/src/components/Menu/examples/Menu.md index f9c7579dd1f..6b309cbb60e 100644 --- a/packages/react-core/src/components/Menu/examples/Menu.md +++ b/packages/react-core/src/components/Menu/examples/Menu.md @@ -13,7 +13,8 @@ propComponents: 'MenuSearch', 'MenuSearchInput', 'MenuGroup', - 'MenuContainer' + 'MenuContainer', + 'MenuPopperProps' ] ouia: true --- From c8c2ea901ca58e21aa26aa810b8fe0d72e93631a Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Wed, 17 May 2023 17:29:34 -0400 Subject: [PATCH 11/12] update wording --- packages/react-core/src/components/Dropdown/Dropdown.tsx | 2 +- packages/react-core/src/components/Menu/MenuContainer.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-core/src/components/Dropdown/Dropdown.tsx b/packages/react-core/src/components/Dropdown/Dropdown.tsx index 6e46b345ec6..74f139b8d01 100644 --- a/packages/react-core/src/components/Dropdown/Dropdown.tsx +++ b/packages/react-core/src/components/Dropdown/Dropdown.tsx @@ -45,7 +45,7 @@ export interface DropdownProps extends MenuProps, OUIAProps { /** Callback to allow the dropdown component to change the open state of the menu. * Triggered by clicking outside of the menu, or by pressing any keys specificed in onOpenChangeKeys. */ onOpenChange?: (isOpen: boolean) => void; - /** @beta Keys that trigger onOpenChange, defaults to tab and escape. Escape should always be included in the array, while Tab may be omitted if the menu contains non-menu items that are focusable. */ + /** @beta Keys that trigger onOpenChange, defaults to tab and escape. It is highly recommended to include Escape in the array, while Tab may be omitted if the menu contains non-menu items that are focusable. */ onOpenChangeKeys?: string[]; /** Indicates if the menu should be without the outer box-shadow. */ isPlain?: boolean; diff --git a/packages/react-core/src/components/Menu/MenuContainer.tsx b/packages/react-core/src/components/Menu/MenuContainer.tsx index b8caead0ef4..3f05a80bd98 100644 --- a/packages/react-core/src/components/Menu/MenuContainer.tsx +++ b/packages/react-core/src/components/Menu/MenuContainer.tsx @@ -29,7 +29,7 @@ export interface MenuContainerProps { /** Callback to change the open state of the menu. * Triggered by clicking outside of the menu, or by pressing any keys specificed in onOpenChangeKeys. */ onOpenChange?: (isOpen: boolean) => void; - /** @beta Keys that trigger onOpenChange, defaults to tab and escape. Escape should always be included in the array, while Tab may be omitted if the menu contains non-menu items that are focusable. */ + /** @beta Keys that trigger onOpenChange, defaults to tab and escape. It is highly recommended to include Escape in the array, while Tab may be omitted if the menu contains non-menu items that are focusable. */ onOpenChangeKeys?: string[]; /** z-index of the dropdown menu */ zIndex?: number; From 42cf3a492da69fb553ba205d50abef9562dfa7d2 Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Thu, 18 May 2023 14:07:22 -0400 Subject: [PATCH 12/12] add beta note to prop table --- packages/react-core/src/components/Menu/MenuContainer.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/react-core/src/components/Menu/MenuContainer.tsx b/packages/react-core/src/components/Menu/MenuContainer.tsx index 3f05a80bd98..16f002561f3 100644 --- a/packages/react-core/src/components/Menu/MenuContainer.tsx +++ b/packages/react-core/src/components/Menu/MenuContainer.tsx @@ -29,7 +29,7 @@ export interface MenuContainerProps { /** Callback to change the open state of the menu. * Triggered by clicking outside of the menu, or by pressing any keys specificed in onOpenChangeKeys. */ onOpenChange?: (isOpen: boolean) => void; - /** @beta Keys that trigger onOpenChange, defaults to tab and escape. It is highly recommended to include Escape in the array, while Tab may be omitted if the menu contains non-menu items that are focusable. */ + /** Keys that trigger onOpenChange, defaults to tab and escape. It is highly recommended to include Escape in the array, while Tab may be omitted if the menu contains non-menu items that are focusable. */ onOpenChangeKeys?: string[]; /** z-index of the dropdown menu */ zIndex?: number; @@ -38,7 +38,8 @@ export interface MenuContainerProps { } /** - * Container that links a menu and menu toggle together, to handle basic keyboard input and control the opening and closing of a menu + * Container that links a menu and menu toggle together, to handle basic keyboard input and control the opening and closing of a menu. + * This component is currently in beta and is subject to change. */ export const MenuContainer: React.FunctionComponent = ({ menu,