diff --git a/packages/react-core/src/components/Dropdown/Dropdown.tsx b/packages/react-core/src/components/Dropdown/Dropdown.tsx index 0ed560ff053..fa2688d35f8 100644 --- a/packages/react-core/src/components/Dropdown/Dropdown.tsx +++ b/packages/react-core/src/components/Dropdown/Dropdown.tsx @@ -1,9 +1,27 @@ import React from 'react'; import { css } from '@patternfly/react-styles'; import { Menu, MenuContent, MenuProps } from '../Menu'; -import { Popper, PopperProps } from '../../helpers/Popper/Popper'; +import { Popper } from '../../helpers/Popper/Popper'; import { useOUIAProps, OUIAProps } from '../../helpers'; +export interface DropdownPopperProps { + /** 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; +} + +/** + * See the Menu documentation for additional props that may be passed. + */ export interface DropdownProps extends MenuProps, OUIAProps { /** Anything which can be rendered in a dropdown. */ children?: React.ReactNode; @@ -14,7 +32,11 @@ export interface DropdownProps extends MenuProps, OUIAProps { /** Flag to indicate if menu is opened.*/ isOpen?: boolean; /** Function callback called when user selects item. */ - onSelect?: (event?: React.MouseEvent, itemId?: string | number) => void; + onSelect?: ( + event?: React.MouseEvent, + itemId?: string | number, + toggleRef?: React.RefObject + ) => 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. */ onOpenChange?: (isOpen: boolean) => void; @@ -31,7 +53,7 @@ export interface DropdownProps extends MenuProps, OUIAProps { /** z-index of the dropdown menu */ zIndex?: number; /** Additional properties to pass to the Popper */ - popperProps?: Partial; + popperProps?: DropdownPopperProps; } const DropdownBase: React.FunctionComponent = ({ @@ -59,10 +81,12 @@ const DropdownBase: React.FunctionComponent = ({ 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) + isOpen && + onOpenChange && + (menuRef.current?.contains(event.target as Node) || toggleRef.current?.contains(event.target as Node)) ) { if (event.key === 'Escape' || event.key === 'Tab') { + event.preventDefault(); onOpenChange(false); toggleRef.current?.focus(); } @@ -101,7 +125,7 @@ const DropdownBase: React.FunctionComponent = ({ onSelect && onSelect(event, itemId)} + onSelect={(event, itemId) => onSelect && onSelect(event, itemId, toggleRef)} isPlain={isPlain} isScrollable={isScrollable} {...props} diff --git a/packages/react-core/src/components/Dropdown/DropdownGroup.tsx b/packages/react-core/src/components/Dropdown/DropdownGroup.tsx index 8edb734a2da..721e6216071 100644 --- a/packages/react-core/src/components/Dropdown/DropdownGroup.tsx +++ b/packages/react-core/src/components/Dropdown/DropdownGroup.tsx @@ -2,6 +2,9 @@ import React from 'react'; import { css } from '@patternfly/react-styles'; import { MenuGroupProps, MenuGroup } from '../Menu'; +/** + * See the MenuGroup section of the Menu documentation for additional props that may be passed. + */ export interface DropdownGroupProps extends Omit { /** Anything which can be rendered in a dropdown group. */ children: React.ReactNode; diff --git a/packages/react-core/src/components/Dropdown/DropdownItem.tsx b/packages/react-core/src/components/Dropdown/DropdownItem.tsx index f7c5cad9eec..f2c97e6b245 100644 --- a/packages/react-core/src/components/Dropdown/DropdownItem.tsx +++ b/packages/react-core/src/components/Dropdown/DropdownItem.tsx @@ -3,11 +3,16 @@ import { css } from '@patternfly/react-styles'; import { MenuItemProps, MenuItem } from '../Menu'; import { useOUIAProps, OUIAProps } from '../../helpers'; +/** + * See the MenuItem section of the Menu documentation for additional props that may be passed. + */ export interface DropdownItemProps extends Omit, OUIAProps { /** Anything which can be rendered in a dropdown item */ children?: React.ReactNode; /** Classes applied to root element of dropdown item */ className?: string; + /** @hide Forwarded ref */ + innerRef?: React.Ref; /** Description of the dropdown item */ description?: React.ReactNode; /** Render item as disabled option */ @@ -22,7 +27,7 @@ export interface DropdownItemProps extends Omit, OUIAProps ouiaSafe?: boolean; } -export const DropdownItem: React.FunctionComponent = ({ +const DropdownItemBase: React.FunctionComponent = ({ children, className, description, @@ -31,11 +36,13 @@ export const DropdownItem: React.FunctionComponent = ({ onClick, ouiaId, ouiaSafe, + innerRef, ...props }: DropdownItemProps) => { const ouiaProps = useOUIAProps(DropdownItem.displayName, ouiaId, ouiaSafe); return ( = ({ ); }; +export const DropdownItem = React.forwardRef((props: DropdownItemProps, ref: React.Ref) => ( + +)); + DropdownItem.displayName = 'DropdownItem'; diff --git a/packages/react-core/src/components/Dropdown/examples/Dropdown.md b/packages/react-core/src/components/Dropdown/examples/Dropdown.md index b8a696ff553..cd28d0bf0d4 100644 --- a/packages/react-core/src/components/Dropdown/examples/Dropdown.md +++ b/packages/react-core/src/components/Dropdown/examples/Dropdown.md @@ -3,13 +3,15 @@ id: Dropdown section: components subsection: menus cssPrefix: pf-c-menu -propComponents: ['Dropdown', DropdownGroup, 'DropdownItem', 'DropdownList', 'MenuToggle'] +propComponents: ['Dropdown', DropdownGroup, 'DropdownItem', 'DropdownList', 'MenuToggle', 'DropdownPopperProps'] --- import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon'; ## Examples +`Dropdown` builds off of the Menu component suite to wrap commonly used properties and functions for a dropdown menu. See the [Menu documentation](/components/menus/menu) for a full list of properties that may be passed through `Dropdown` to further customize the dropdown menu, or the [custom menu examples](/components/menus/custom-menus) for additional examples of fully functional menus. + ### Basic dropdowns Basic dropdowns present users with a menu of items upon clicking a dropdown toggle. diff --git a/packages/react-core/src/components/Dropdown/examples/DropdownBasic.tsx b/packages/react-core/src/components/Dropdown/examples/DropdownBasic.tsx index 5137dc71577..f472964130c 100644 --- a/packages/react-core/src/components/Dropdown/examples/DropdownBasic.tsx +++ b/packages/react-core/src/components/Dropdown/examples/DropdownBasic.tsx @@ -8,10 +8,15 @@ export const DropdownBasic: React.FunctionComponent = () => { setIsOpen(!isOpen); }; - const onSelect = (_event: React.MouseEvent | undefined, itemId: string | number | undefined) => { + const onSelect = ( + _event: React.MouseEvent | undefined, + itemId: string | number | undefined, + toggleRef: React.RefObject + ) => { // eslint-disable-next-line no-console console.log('selected', itemId); setIsOpen(false); + toggleRef?.current.focus(); }; return ( diff --git a/packages/react-core/src/components/Dropdown/examples/DropdownWithDescriptions.tsx b/packages/react-core/src/components/Dropdown/examples/DropdownWithDescriptions.tsx index 9f756bbfa84..ce30700be51 100644 --- a/packages/react-core/src/components/Dropdown/examples/DropdownWithDescriptions.tsx +++ b/packages/react-core/src/components/Dropdown/examples/DropdownWithDescriptions.tsx @@ -8,10 +8,15 @@ export const DropdownWithDescriptions: React.FunctionComponent = () => { setIsOpen(!isOpen); }; - const onSelect = (_event: React.MouseEvent | undefined, itemId: string | number | undefined) => { + const onSelect = ( + _event: React.MouseEvent | undefined, + itemId: string | number | undefined, + toggleRef: React.RefObject + ) => { // eslint-disable-next-line no-console console.log('selected', itemId); setIsOpen(false); + toggleRef?.current.focus(); }; return ( diff --git a/packages/react-core/src/components/Dropdown/examples/DropdownWithGroups.tsx b/packages/react-core/src/components/Dropdown/examples/DropdownWithGroups.tsx index 080c3f151f2..a846e06e089 100644 --- a/packages/react-core/src/components/Dropdown/examples/DropdownWithGroups.tsx +++ b/packages/react-core/src/components/Dropdown/examples/DropdownWithGroups.tsx @@ -16,10 +16,15 @@ export const DropdownWithGroups: React.FunctionComponent = () => { setIsOpen(!isOpen); }; - const onSelect = (_event: React.MouseEvent | undefined, itemId: string | number | undefined) => { + const onSelect = ( + _event: React.MouseEvent | undefined, + itemId: string | number | undefined, + toggleRef: React.RefObject + ) => { // eslint-disable-next-line no-console console.log('selected', itemId); setIsOpen(false); + toggleRef?.current.focus(); }; return ( diff --git a/packages/react-core/src/components/Dropdown/examples/DropdownWithKebabToggle.tsx b/packages/react-core/src/components/Dropdown/examples/DropdownWithKebabToggle.tsx index c71ae8bc1b8..a3fb6202e75 100644 --- a/packages/react-core/src/components/Dropdown/examples/DropdownWithKebabToggle.tsx +++ b/packages/react-core/src/components/Dropdown/examples/DropdownWithKebabToggle.tsx @@ -9,10 +9,15 @@ export const DropdownWithKebab: React.FunctionComponent = () => { setIsOpen(!isOpen); }; - const onSelect = (_event: React.MouseEvent | undefined, itemId: string | number | undefined) => { + const onSelect = ( + _event: React.MouseEvent | undefined, + itemId: string | number | undefined, + toggleRef: React.RefObject + ) => { // eslint-disable-next-line no-console console.log('selected', itemId); setIsOpen(false); + toggleRef?.current.focus(); }; return (