Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Menu): Added support for tooltips to menu #9382

Merged
merged 10 commits into from
Jul 20, 2023
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) {
tlabaj marked this conversation as resolved.
Show resolved Hide resolved
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}
thatblindgeye marked this conversation as resolved.
Show resolved Hide resolved
>
{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
Loading