Skip to content
9 changes: 9 additions & 0 deletions packages/react-core/src/components/Dropdown/DropdownItem.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import { css } from '@patternfly/react-styles';
import { MenuItemProps, MenuItem } from '../Menu';
import { TooltipProps } from '../Tooltip';
import { useOUIAProps, OUIAProps } from '../../helpers';

/**
Expand All @@ -17,6 +18,8 @@ export interface DropdownItemProps extends Omit<MenuItemProps, 'ref'>, OUIAProps
description?: React.ReactNode;
/** Render item as disabled option */
isDisabled?: boolean;
/** Render item as aria-disabled option */
isAriaDisabled?: boolean;
/** Identifies the component in the dropdown onSelect callback */
value?: any;
/** Callback for item click */
Expand All @@ -25,18 +28,22 @@ export interface DropdownItemProps extends Omit<MenuItemProps, 'ref'>, OUIAProps
ouiaId?: number | string;
/** Set the value of data-ouia-safe. Only set to true when the component is in a static state, i.e. no animations are occurring. At all other times, this value must be false. */
ouiaSafe?: boolean;
/** Props for adding a tooltip to a menu item */
tooltipProps?: TooltipProps;
}

const DropdownItemBase: React.FunctionComponent<DropdownItemProps> = ({
children,
className,
description,
isDisabled,
isAriaDisabled,
value,
onClick,
ouiaId,
ouiaSafe,
innerRef,
tooltipProps,
...props
}: DropdownItemProps) => {
const ouiaProps = useOUIAProps(DropdownItem.displayName, ouiaId, ouiaSafe);
Expand All @@ -45,8 +52,10 @@ const DropdownItemBase: React.FunctionComponent<DropdownItemProps> = ({
className={css(className)}
description={description}
isDisabled={isDisabled}
isAriaDisabled={isAriaDisabled}
itemId={value}
onClick={onClick}
tooltipProps={tooltipProps}
ref={innerRef}
{...ouiaProps}
{...props}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ propComponents:
'DropdownList',
'MenuToggle',
'DropdownToggleProps',
'DropdownPopperProps'
'DropdownPopperProps',
'TooltipProps'
]
---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,14 @@ export const DropdownBasic: React.FunctionComponent = () => {
<DropdownItem value={3} isDisabled key="disabled link" to="#default-link4">
Disabled Link
</DropdownItem>
<DropdownItem value={4} isAriaDisabled key="aria-disabled link" to="#default-link5" tooltipProps={{content: "aria-disabled link", position: "top"}}>
Aria-disabled Link
</DropdownItem>
<Divider component="li" key="separator" />
<DropdownItem value={4} key="separated action">
<DropdownItem value={5} key="separated action">
Separated Action
</DropdownItem>
<DropdownItem value={5} key="separated link" to="#default-link6" onClick={(ev) => ev.preventDefault()}>
<DropdownItem value={6} key="separated link" to="#default-link6" onClick={(ev) => ev.preventDefault()}>
Separated Link
</DropdownItem>
</DropdownList>
Expand Down
2 changes: 2 additions & 0 deletions packages/react-core/src/components/Menu/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,8 @@ class MenuBase extends React.Component<MenuProps, MenuState> {
(navigableElement?.tagName === 'DIV' && navigableElement.querySelector('input')) || // for MenuSearchInput
((navigableElement.firstChild as Element)?.tagName === 'LABEL' &&
navigableElement.querySelector('input')) || // for MenuItem checkboxes
((navigableElement.firstChild as Element)?.tagName === 'DIV' &&
navigableElement.querySelector('a, button, input')) || // For aria-disabled element that is rendered inside a div with "display: contents" styling
(navigableElement.firstChild as Element)
}
noHorizontalArrowHandling={
Expand Down
89 changes: 61 additions & 28 deletions packages/react-core/src/components/Menu/MenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import CheckIcon from '@patternfly/react-icons/dist/esm/icons/check-icon';
import { Checkbox } from '../Checkbox';
import { MenuContext, MenuItemContext } from './MenuContext';
import { MenuItemAction } from './MenuItemAction';
import { Tooltip, TooltipProps } from '../Tooltip';
import { canUseDOM } from '../../helpers/util';
import { useIsomorphicLayoutEffect } from '../../helpers/useIsomorphicLayout';
import { GenerateId } from '../../helpers/GenerateId/GenerateId';
Expand Down Expand Up @@ -40,6 +41,10 @@ export interface MenuItemProps extends Omit<React.HTMLProps<HTMLLIElement>, 'onC
component?: React.ElementType<any> | React.ComponentType<any>;
/** Render item as disabled option */
isDisabled?: boolean;
/** Render item as aria-disabled option */
isAriaDisabled?: boolean;
/** Props for adding a tooltip to a menu item */
tooltipProps?: TooltipProps;
/** Render item with icon */
icon?: React.ReactNode;
/** Render item with one or more actions */
Expand Down Expand Up @@ -94,6 +99,7 @@ const MenuItemBase: React.FunctionComponent<MenuItemProps> = ({
onClick = () => {},
component = 'button',
isDisabled = false,
isAriaDisabled = false,
isExternalLink = false,
isSelected = null,
isFocused,
Expand All @@ -106,6 +112,7 @@ const MenuItemBase: React.FunctionComponent<MenuItemProps> = ({
innerRef,
id,
'aria-label': ariaLabel,
tooltipProps,
...props
}: MenuItemProps) => {
const {
Expand Down Expand Up @@ -225,10 +232,12 @@ const MenuItemBase: React.FunctionComponent<MenuItemProps> = ({
};

const onItemSelect = (event: any, onSelect: any) => {
// Trigger callback for Menu onSelect
onSelect && onSelect(event, itemId);
// Trigger callback for item onClick
onClick && onClick(event);
if (!isAriaDisabled) {
// Trigger callback for Menu onSelect
onSelect && onSelect(event, itemId);
// Trigger callback for item onClick
onClick && onClick(event);
}
};
const _isOnPath = (isOnPath && isOnPath) || (drilldownItemPath && drilldownItemPath.includes(itemId)) || false;
let drill: (event: React.KeyboardEvent | React.MouseEvent) => void;
Expand All @@ -252,16 +261,18 @@ const MenuItemBase: React.FunctionComponent<MenuItemProps> = ({
if (Component === 'a') {
additionalProps = {
href: to,
'aria-disabled': isDisabled ? true : null,
'aria-disabled': isDisabled || isAriaDisabled ? true : null,
// prevent invalid 'disabled' attribute on <a> tags
disabled: null,
target: isExternalLink ? '_blank' : null
};
} else if (Component === 'button') {
additionalProps = {
type: 'button'
type: 'button',
'aria-disabled': isAriaDisabled ? true : null
};
}

if (isOnPath) {
additionalProps['aria-expanded'] = true;
} else if (hasFlyout) {
Expand Down Expand Up @@ -300,25 +311,8 @@ const MenuItemBase: React.FunctionComponent<MenuItemProps> = ({
};
const isSelectMenu = menuRole === 'listbox';

return (
<li
className={css(
styles.menuListItem,
isDisabled && styles.modifiers.disabled,
_isOnPath && styles.modifiers.currentPath,
isLoadButton && styles.modifiers.load,
isLoading && styles.modifiers.loading,
isFocused && styles.modifiers.focus,
isDanger && styles.modifiers.danger,
className
)}
onMouseOver={onMouseOver}
{...(flyoutMenu && { onKeyDown: handleFlyout })}
ref={ref}
role={!hasCheckbox ? 'none' : 'menuitem'}
{...(hasCheckbox && { 'aria-label': ariaLabel })}
{...props}
>
const renderItem = (
<>
<GenerateId>
{(randomId) => (
<Component
Expand All @@ -332,9 +326,13 @@ const MenuItemBase: React.FunctionComponent<MenuItemProps> = ({
ref={innerRef}
{...(!hasCheckbox && {
onClick: (event: React.KeyboardEvent | React.MouseEvent) => {
onItemSelect(event, onSelect);
drill && drill(event);
flyoutMenu && handleFlyout(event);
if (!isAriaDisabled) {
onItemSelect(event, onSelect);
drill && drill(event);
flyoutMenu && handleFlyout(event);
} else {
event.preventDefault();
}
}
})}
{...(hasCheckbox && { htmlFor: randomId })}
Expand All @@ -355,6 +353,7 @@ const MenuItemBase: React.FunctionComponent<MenuItemProps> = ({
isChecked={isSelected || false}
onChange={(event) => onItemSelect(event, onSelect)}
isDisabled={isDisabled}
aria-disabled={isAriaDisabled}
/>
</span>
)}
Expand Down Expand Up @@ -402,6 +401,40 @@ const MenuItemBase: React.FunctionComponent<MenuItemProps> = ({
/>
)}
</MenuItemContext.Provider>
</>
);

return (
<li
className={css(
styles.menuListItem,
isDisabled && styles.modifiers.disabled,
isAriaDisabled && styles.modifiers.ariaDisabled,
_isOnPath && styles.modifiers.currentPath,
isLoadButton && styles.modifiers.load,
isLoading && styles.modifiers.loading,
isFocused && styles.modifiers.focus,
isDanger && styles.modifiers.danger,
className
)}
onMouseOver={() => {
if (!isAriaDisabled) {
onMouseOver();
}
}}
{...(flyoutMenu && !isAriaDisabled && { onKeyDown: handleFlyout })}
ref={ref}
role={!hasCheckbox ? 'none' : 'menuitem'}
{...(hasCheckbox && { 'aria-label': ariaLabel })}
{...props}
>
{tooltipProps ? (
<Tooltip {...tooltipProps}>
{renderItem}
</Tooltip>
) : (
renderItem
)}
</li>
);
};
Expand Down
3 changes: 2 additions & 1 deletion packages/react-core/src/components/Menu/examples/Menu.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ propComponents:
'MenuSearchInput',
'MenuGroup',
'MenuContainer',
'MenuPopperProps'
'MenuPopperProps',
'TooltipProps'
]
ouia: true
---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ export const MenuBasic: React.FunctionComponent = () => {
<MenuItem isDisabled to="#default-link4">
Disabled link
</MenuItem>
<MenuItem isAriaDisabled tooltipProps={{content: "aria-disabled action", position: "top"}}>
Aria-disabled action
</MenuItem>
<MenuItem isAriaDisabled to="#default-link5" tooltipProps={{content: "aria-disabled link", position: "top"}}>
Aria-disabled link
</MenuItem>
</MenuList>
</MenuContent>
</Menu>
Expand Down
6 changes: 3 additions & 3 deletions packages/react-table/src/components/Table/ActionsColumn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ const ActionsColumnBase: React.FunctionComponent<ActionsColumnProps> = ({
<DropdownList>
{items
.filter((item) => !item.isOutsideDropdown)
.map(({ title, itemKey, onClick, tooltip, tooltipProps, isSeparator, ...props }, index) => {
.map(({ title, itemKey, onClick, tooltipProps, isSeparator, ...props }, index) => {
if (isSeparator) {
return <Divider key={itemKey || index} data-key={itemKey || index} />;
}
Expand All @@ -133,9 +133,9 @@ const ActionsColumnBase: React.FunctionComponent<ActionsColumnProps> = ({
</DropdownItem>
);

if (tooltip) {
if (tooltipProps?.content) {
return (
<Tooltip key={itemKey || index} content={tooltip} {...tooltipProps}>
<Tooltip key={itemKey || index} {...tooltipProps}>
{item}
</Tooltip>
);
Expand Down
9 changes: 5 additions & 4 deletions packages/react-table/src/components/Table/TableTypes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { DropdownDirection, DropdownPosition } from '@patternfly/react-core/dist
import * as React from 'react';
import { CustomActionsToggleProps } from './ActionsColumn';
import { ButtonProps } from '@patternfly/react-core/dist/esm/components/Button';
import { TooltipProps } from '@patternfly/react-core/dist/esm/components/Tooltip';

export enum TableGridBreakpoint {
none = '',
Expand Down Expand Up @@ -154,10 +155,10 @@ export interface IAction extends Omit<DropdownItemProps, 'title' | 'onClick'>, P
itemKey?: string;
/** Content to display in the actions menu item */
title?: string | React.ReactNode;
/** Tooltip to display when hovered over the item */
tooltip?: React.ReactNode;
/** Additional props forwarded to the tooltip component */
tooltipProps?: any;
/** Render item as aria-disabled option */
isAriaDisabled?: boolean;
/** Props for adding a tooltip to a menu item. This is used to display tooltip when hovered over the item */
tooltipProps?: TooltipProps;
/** Click handler for the actions menu item */
onClick?: (event: React.MouseEvent, rowIndex: number, rowData: IRowData, extraData: IExtraData) => void;
/** Flag indicating this action should be placed outside the actions menu, beside the toggle */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ propComponents:
'ThSelectType',
'TdTreeRowType',
'ActionsColumn',
'IActions',
'TdCompoundExpandType',
'TdFavoritesType',
'TdDraggableType',
Expand Down