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

fix: TooltipHost supports an accessible method to implement overflow tooltips #27653

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🕵 fluentuiv8 Open the Visual Regressions report to inspect the 2 screenshots

✅ There was 0 screenshots added, 2 screenshots removed, 1045 screenshots unchanged, 0 screenshots with different dimensions and 0 screenshots with visible difference.

unknown 2 screenshots
Image Name Diff(in Pixels) Image Type
Pivot - Overflow.Root.Narrow - Last tab selected.chromium.png 0 Removed
Pivot - Overflow.Root.Narrow - Overflow menu.chromium.png 0 Removed

"type": "minor",
"comment": "fix: TooltipHost supports an accessible way to implement overflow tooltips\"",
"packageName": "@fluentui/react",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ const contentParent =
const contentSelf =
"If the TooltipHost's content overflows, hovering here will show a tooltip (anchored to the TooltipHost).";

const contentLink =
"If the link's content overflows, hovering or focusing here will show a tooltip (anchored to the TooltipHost).";

// The TooltipHost uses display: inline by default, which causes issues with this example's
// styling and layout. Use display: block instead. (other styles are just to look nice)
const theme = getTheme();
Expand All @@ -32,12 +35,16 @@ const classNames = mergeStyleSets({
border: '2px dashed ' + theme.palette.neutralTertiary,
selectors: { '> *:last-child': { marginTop: 10 } },
},
// links are inline by default, so this allows the link to have overflow styles
link: {
display: 'inline-block',
},
});

export const TooltipOverflowExample: React.FunctionComponent = () => {
const parentTooltipId = useId('text-tooltip');
const [shouldOverflow, setShouldOverflow] = React.useState(false);
const [isParentTooltipVisible, setIsParentTooltipVisible] = React.useState(false);
const linkRef = React.useRef<HTMLAnchorElement>(null);

const onOverflowChange = React.useCallback(() => setShouldOverflow(!shouldOverflow), [shouldOverflow]);

Expand All @@ -48,6 +55,10 @@ export const TooltipOverflowExample: React.FunctionComponent = () => {
{/* Example of TooltipOverflowMode.Parent */}
<div className={classNames.example}>
<Label>Show tooltip when parent's content overflows</Label>
<p>
Warning! This is not keyboard accessible, and should only be done when users have another method to access the
content.
</p>

{/* This parent element will overflow */}
<div className={css(classNames.parent, shouldOverflow && classNames.overflow)}>
Expand All @@ -56,34 +67,49 @@ export const TooltipOverflowExample: React.FunctionComponent = () => {
overflowMode={TooltipOverflowMode.Parent}
// In a case like this, you should usually display the non-truncated content in the tooltip.
content={contentParent}
// If targeting the tooltip to the parent, it's necessary to manually set and remove
// aria-describedby for the content when the tooltip is shown/hidden
onTooltipToggle={setIsParentTooltipVisible}
id={parentTooltipId}
styles={hostStyles}
>
This is the TooltipHost area.{' '}
<span aria-describedby={isParentTooltipVisible ? parentTooltipId : undefined}>{contentParent}</span>
This is the TooltipHost area. {contentParent}
</TooltipHost>
</div>
</div>

{/* Example of TooltipOverflowMode.Self */}
<div className={classNames.example}>
<Label>Show tooltip when TooltipHost's content overflows</Label>
<p>
Warning! This is not keyboard accessible, and should only be done when users have another method to access the
content.
</p>

<TooltipHost
overflowMode={TooltipOverflowMode.Self}
// The TooltipHost itself will overflow
hostClassName={css(shouldOverflow && classNames.overflow)}
content={contentSelf}
onTooltipToggle={setIsParentTooltipVisible}
styles={hostStyles}
// In this mode, aria-describedby is automatically added/removed based on tooltip visibility
>
This is the TooltipHost area. {contentSelf}
</TooltipHost>
</div>

{/* Example of TooltipOverflowMode.Custom */}
<div className={classNames.example}>
<Label>Show tooltip when a child link's content overflows</Label>
<p>Note: This is the only way to create an overflow tooltip that is keyboard-accessible.</p>

<TooltipHost
overflowMode={TooltipOverflowMode.Custom}
customOverflowTarget={linkRef.current}
content={contentSelf}
styles={hostStyles}
>
<a href="#" ref={linkRef} className={css(classNames.link, shouldOverflow && classNames.overflow)}>
This is a link in the TooltipHost area. {contentLink}
</a>
</TooltipHost>
</div>
</div>
);
};
2 changes: 2 additions & 0 deletions packages/react/etc/react.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -9469,6 +9469,7 @@ export interface ITooltipHostProps extends Omit<React_2.HTMLAttributes<HTMLDivEl
closeDelay?: number;
componentRef?: IRefObject<ITooltipHost>;
content?: string | JSX.Element | JSX.Element[];
customOverflowTarget?: HTMLElement | null;
delay?: TooltipDelay;
directionalHint?: DirectionalHint;
directionalHintForRTL?: DirectionalHint;
Expand Down Expand Up @@ -11300,6 +11301,7 @@ export class TooltipHostBase extends React_2.Component<ITooltipHostProps, IToolt

// @public (undocumented)
export enum TooltipOverflowMode {
Custom = 2,
Parent = 0,
Self = 1
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ export class TooltipHostBase extends React.Component<ITooltipHostProps, ITooltip
return undefined;
}

const { overflowMode } = this.props;
const { overflowMode, customOverflowTarget } = this.props;

// Select target element based on overflow mode. For parent mode, you want to position the tooltip relative
// to the parent element, otherwise it might look off.
Expand All @@ -160,6 +160,11 @@ export class TooltipHostBase extends React.Component<ITooltipHostProps, ITooltip

case TooltipOverflowMode.Self:
return this._tooltipHost.current;

case TooltipOverflowMode.Custom:
if (customOverflowTarget) {
return customOverflowTarget;
}
}
}

Expand Down
12 changes: 12 additions & 0 deletions packages/react/src/components/Tooltip/TooltipHost.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ export enum TooltipOverflowMode {
* Note that this does not check the children for overflow, only the TooltipHost root.
*/
Self,

/**
* Only show tooltip if a custom element's content is overflowing.
* The custom element must be passed in via the `customOverflowTarget` prop.
*/
Custom,
}

/**
Expand Down Expand Up @@ -65,6 +71,12 @@ export interface ITooltipHostProps extends Omit<React.HTMLAttributes<HTMLDivElem
*/
content?: string | JSX.Element | JSX.Element[];

/**
* Define a custom element to use as the overflow target.
* Note: this will only be used if `overflowMode` is set to `TooltipOverflowMode.Custom`.
*/
customOverflowTarget?: HTMLElement | null;

/**
* Length of delay before showing the tooltip on hover.
* @defaultvalue TooltipDelay.medium
Expand Down