Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 30 additions & 6 deletions packages/react-core/src/components/Dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<Element, MouseEvent>, itemId?: string | number) => void;
onSelect?: (
event?: React.MouseEvent<Element, MouseEvent>,
itemId?: string | number,
toggleRef?: React.RefObject<any>
) => 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;
Expand All @@ -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>;
popperProps?: DropdownPopperProps;
}

const DropdownBase: React.FunctionComponent<DropdownProps> = ({
Expand Down Expand Up @@ -59,10 +81,12 @@ const DropdownBase: React.FunctionComponent<DropdownProps> = ({
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();
}
Expand Down Expand Up @@ -101,7 +125,7 @@ const DropdownBase: React.FunctionComponent<DropdownProps> = ({
<Menu
className={css(className)}
ref={menuRef}
onSelect={(event, itemId) => onSelect && onSelect(event, itemId)}
onSelect={(event, itemId) => onSelect && onSelect(event, itemId, toggleRef)}
isPlain={isPlain}
isScrollable={isScrollable}
{...props}
Expand Down
3 changes: 3 additions & 0 deletions packages/react-core/src/components/Dropdown/DropdownGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<MenuGroupProps, 'ref'> {
/** Anything which can be rendered in a dropdown group. */
children: React.ReactNode;
Expand Down
13 changes: 12 additions & 1 deletion packages/react-core/src/components/Dropdown/DropdownItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<MenuItemProps, 'ref'>, 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<HTMLAnchorElement | HTMLButtonElement>;
/** Description of the dropdown item */
description?: React.ReactNode;
/** Render item as disabled option */
Expand All @@ -22,7 +27,7 @@ export interface DropdownItemProps extends Omit<MenuItemProps, 'ref'>, OUIAProps
ouiaSafe?: boolean;
}

export const DropdownItem: React.FunctionComponent<MenuItemProps> = ({
const DropdownItemBase: React.FunctionComponent<MenuItemProps> = ({
children,
className,
description,
Expand All @@ -31,11 +36,13 @@ export const DropdownItem: React.FunctionComponent<MenuItemProps> = ({
onClick,
ouiaId,
ouiaSafe,
innerRef,
...props
}: DropdownItemProps) => {
const ouiaProps = useOUIAProps(DropdownItem.displayName, ouiaId, ouiaSafe);
return (
<MenuItem
ref={innerRef}
className={css(className)}
description={description}
isDisabled={isDisabled}
Expand All @@ -48,4 +55,8 @@ export const DropdownItem: React.FunctionComponent<MenuItemProps> = ({
</MenuItem>
);
};
export const DropdownItem = React.forwardRef((props: DropdownItemProps, ref: React.Ref<any>) => (
<DropdownItemBase {...props} innerRef={ref} />
));

DropdownItem.displayName = 'DropdownItem';
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,15 @@ export const DropdownBasic: React.FunctionComponent = () => {
setIsOpen(!isOpen);
};

const onSelect = (_event: React.MouseEvent<Element, MouseEvent> | undefined, itemId: string | number | undefined) => {
const onSelect = (
_event: React.MouseEvent<Element, MouseEvent> | undefined,
itemId: string | number | undefined,
toggleRef: React.RefObject<any>
) => {
// eslint-disable-next-line no-console
console.log('selected', itemId);
setIsOpen(false);
toggleRef?.current.focus();
};

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,15 @@ export const DropdownWithDescriptions: React.FunctionComponent = () => {
setIsOpen(!isOpen);
};

const onSelect = (_event: React.MouseEvent<Element, MouseEvent> | undefined, itemId: string | number | undefined) => {
const onSelect = (
_event: React.MouseEvent<Element, MouseEvent> | undefined,
itemId: string | number | undefined,
toggleRef: React.RefObject<any>
) => {
// eslint-disable-next-line no-console
console.log('selected', itemId);
setIsOpen(false);
toggleRef?.current.focus();
};

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,15 @@ export const DropdownWithGroups: React.FunctionComponent = () => {
setIsOpen(!isOpen);
};

const onSelect = (_event: React.MouseEvent<Element, MouseEvent> | undefined, itemId: string | number | undefined) => {
const onSelect = (
_event: React.MouseEvent<Element, MouseEvent> | undefined,
itemId: string | number | undefined,
toggleRef: React.RefObject<any>
) => {
// eslint-disable-next-line no-console
console.log('selected', itemId);
setIsOpen(false);
toggleRef?.current.focus();
};

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,15 @@ export const DropdownWithKebab: React.FunctionComponent = () => {
setIsOpen(!isOpen);
};

const onSelect = (_event: React.MouseEvent<Element, MouseEvent> | undefined, itemId: string | number | undefined) => {
const onSelect = (
_event: React.MouseEvent<Element, MouseEvent> | undefined,
itemId: string | number | undefined,
toggleRef: React.RefObject<any>
) => {
// eslint-disable-next-line no-console
console.log('selected', itemId);
setIsOpen(false);
toggleRef?.current.focus();
};

return (
Expand Down