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
2 changes: 1 addition & 1 deletion packages/modals/demo/stories/TooltipDialogStory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export const TooltipDialogStory: Story<IArgs> = ({
{!!hasClose && <TooltipDialog.Close aria-label={closeAriaLabel} />}
</TooltipDialog>
<Grid>
<Grid.Row style={{ height: 'calc(100vh - 80px)' }}>
<Grid.Row style={{ height: 'calc(100vh - 200px)' }}>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Offset story content to allow for overlay flip.

{[...Array(count)].map((_, index) => (
<Grid.Col key={index} md={4} textAlign="center" alignSelf="center">
<IconButton
Expand Down
2 changes: 2 additions & 0 deletions packages/modals/demo/tooltipDialog.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useArgs } from '@storybook/client-api';
import { TooltipDialog } from '@zendeskgarden/react-modals';
import { TooltipDialogStory } from './stories/TooltipDialogStory';
import { TOOLTIP_DIALOG_BODY as BODY } from './stories/data';
import { PLACEMENT } from '../src/types';
import README from '../README.md';

<Meta
Expand Down Expand Up @@ -41,6 +42,7 @@ import README from '../README.md';
}}
argTypes={{
referenceElement: { control: false },
fallbackPlacements: { control: 'multi-select', options: PLACEMENT.filter(p => p !== 'auto') },
hasBody: { name: 'TooltipDialog.Body', table: { category: 'Story' } },
hasClose: { name: 'TooltipDialog.Close', table: { category: 'Story' } },
hasFooter: { name: 'TooltipDialog.Footer', table: { category: 'Story' } },
Expand Down
23 changes: 18 additions & 5 deletions packages/modals/src/elements/TooltipDialog/TooltipDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@ import React, { HTMLAttributes, useState, useContext, useEffect, useRef } from '
import PropTypes from 'prop-types';
import { ThemeContext } from 'styled-components';
import { CSSTransition } from 'react-transition-group';
import { autoPlacement, autoUpdate, offset, platform, useFloating } from '@floating-ui/react-dom';
import {
autoPlacement,
autoUpdate,
flip,
offset,
platform,
useFloating
} from '@floating-ui/react-dom';
import { useModal } from '@zendeskgarden/container-modal';
import { mergeRefs } from 'react-merge-refs';
import { TooltipDialogContext } from '../../utils/useTooltipDialogContext';
Expand All @@ -18,7 +25,7 @@ import {
StyledTooltipDialog,
StyledTooltipDialogBackdrop
} from '../../styled';
import { ITooltipDialogProps } from '../../types';
import { ITooltipDialogProps, PLACEMENT } from '../../types';
import { Title } from './Title';
import { Body } from './Body';
import { Close } from './Close';
Expand All @@ -35,6 +42,7 @@ const TooltipDialogComponent = React.forwardRef<HTMLDivElement, ITooltipDialogPr
appendToNode,
referenceElement,
placement: _placement,
fallbackPlacements: _fallbackPlacements,
offset: _offset,
onClose,
hasArrow,
Expand Down Expand Up @@ -64,9 +72,10 @@ const TooltipDialogComponent = React.forwardRef<HTMLDivElement, ITooltipDialogPr
restoreFocus: false
});

const [floatingPlacement] = getFloatingPlacements(
const [floatingPlacement, fallbackPlacements] = getFloatingPlacements(
Copy link
Contributor Author

@ze-flo ze-flo May 13, 2025

Choose a reason for hiding this comment

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

if props.fallbackPlacements is undefined, getFloatingPlacements will generate opposite fallbackPlacements.

const toFallbackPlacements = (
primaryPlacement: FloatingPlacement,
theme: IGardenTheme,
fallbackPlacements?: Placement[]
): FloatingPlacement[] => {
if (Array.isArray(fallbackPlacements) && fallbackPlacements.length > 0) {
return fallbackPlacements.map(fallbackPlacement =>
toFloatingPlacement(fallbackPlacement, theme)
);
}
const side = primaryPlacement.split('-')[0];
const sameSideFallbackPlacements = [...SIDE_FALLBACKS_MAP[side]];
const oppositeSideFallbackPlacements = SIDE_FALLBACKS_MAP[SIDE_OPPOSITE_MAP[side]];
// Remove the primary placement from the list of same-side fallbacks to
// prevent extra work for Floating-UI
sameSideFallbackPlacements.splice(sameSideFallbackPlacements.indexOf(primaryPlacement), 1);
return [...sameSideFallbackPlacements, ...oppositeSideFallbackPlacements];
};

Should we set a sensible default (e.g.: ['bottom']) to both Tooltip and TooltipDialog?

Copy link
Member

Choose a reason for hiding this comment

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

I wouldn't due to the fact that floating-ui already attempts to position within available space

theme,
_placement === 'auto' ? PLACEMENT_DEFAULT : _placement!
_placement === 'auto' ? PLACEMENT_DEFAULT : _placement!,
_fallbackPlacements
);

const {
Expand All @@ -83,7 +92,7 @@ const TooltipDialogComponent = React.forwardRef<HTMLDivElement, ITooltipDialogPr
placement: floatingPlacement,
middleware: [
offset(_offset === undefined ? theme.space.base * 3 : _offset),
_placement === 'auto' ? autoPlacement() : undefined
_placement === 'auto' ? autoPlacement() : flip({ fallbackPlacements })
]
});

Expand Down Expand Up @@ -194,6 +203,10 @@ TooltipDialogComponent.propTypes = {
appendToNode: PropTypes.any,
referenceElement: PropTypes.any,
placement: PropTypes.any,
// @ts-expect-error Validation error due to incorrect type inference when component is wrapped in forwardRef
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Correct type inference for RFCs.
Screenshot 2025-05-13 at 7 12 48 AM

Incorrect type inference for components wrapped in forwardRef.
Screenshot 2025-05-13 at 7 13 28 AM

🤔

Copy link
Member

Choose a reason for hiding this comment

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

Probably why George went with fallbackPlacements: PropTypes.arrayOf(PropTypes.any) on Menu.

fallbackPlacements: PropTypes.arrayOf(
PropTypes.oneOf(PLACEMENT.filter(placement => placement !== 'auto'))
),
isAnimated: PropTypes.bool,
hasArrow: PropTypes.bool,
zIndex: PropTypes.number,
Expand Down
12 changes: 8 additions & 4 deletions packages/modals/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,19 +76,23 @@ export interface IDrawerHeaderProps extends HTMLAttributes<HTMLDivElement> {

export interface ITooltipDialogProps extends Omit<IModalProps, 'isCentered' | 'isLarge'> {
/**
* Positions the modal relative to the provided `HTMLElement`
* Provides a list of acceptable fallback placements
*/
referenceElement?: HTMLElement | null;
fallbackPlacements?: Exclude<Placement, 'auto'>[];
/**
* Adds an arrow to the tooltop
*/
hasArrow?: boolean;
/** @ignore Modifies the placement offset from the reference element (internal only) */
offset?: number;
/**
* Adjusts the placement of the tooltip
**/
placement?: Placement;
/**
* Adds an arrow to the tooltop
* Positions the modal relative to the provided `HTMLElement`
*/
hasArrow?: boolean;
referenceElement?: HTMLElement | null;
/**
* Sets the `z-index` of the tooltip
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/tooltips/demo/stories/TooltipStory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ interface IArgs extends Omit<ITooltipProps, 'content'> {

export const TooltipStory: StoryFn<IArgs> = ({ content, ...args }: IArgs) => (
<Grid>
<Grid.Row style={{ height: 'calc(100vh - 80px)' }}>
<Grid.Row style={{ height: 'calc(100vh - 200px)' }}>
<Grid.Col textAlign="center" alignSelf="center">
<Tooltip
{...args}
Expand Down
4 changes: 3 additions & 1 deletion packages/tooltips/demo/tooltip.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Tooltip, Title, Paragraph } from '@zendeskgarden/react-tooltips';
import { TooltipStory } from './stories/TooltipStory';
import { TOOLTIP_CONTENT as CONTENT } from './stories/data';
import README from '../README.md';
import { PLACEMENT } from '../src/types';

<Meta
title="Packages/Tooltips/Tooltip"
Expand All @@ -29,7 +30,8 @@ import README from '../README.md';
}}
argTypes={{
isVisible: { control: 'boolean' },
appendToNode: { control: false }
appendToNode: { control: false },
fallbackPlacements: { control: 'multi-select', options: PLACEMENT.filter(p => p !== 'auto') }
}}
parameters={{
design: {
Expand Down
13 changes: 9 additions & 4 deletions packages/tooltips/src/elements/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { useTooltip } from '@zendeskgarden/container-tooltip';
import { composeEventHandlers, getControlledValue } from '@zendeskgarden/container-utilities';
import { StyledTooltipWrapper, StyledTooltip } from '../styled';
import { ITooltipProps, PLACEMENT, SIZE, TYPE } from '../types';
import { autoPlacement, autoUpdate, platform, useFloating } from '@floating-ui/react-dom';
import { autoPlacement, autoUpdate, flip, platform, useFloating } from '@floating-ui/react-dom';
import { DEFAULT_THEME, getFloatingPlacements } from '@zendeskgarden/react-theming';
import { toSize } from './utils';
import { Paragraph } from './Paragraph';
Expand All @@ -29,6 +29,7 @@ export const TooltipComponent = ({
content,
refKey,
placement: _placement,
fallbackPlacements: _fallbackPlacements,
children,
hasArrow,
size,
Expand All @@ -51,9 +52,10 @@ export const TooltipComponent = ({
});

const controlledIsVisible = getControlledValue(externalIsVisible, isVisible);
const [floatingPlacement] = getFloatingPlacements(
const [floatingPlacement, fallbackPlacements] = getFloatingPlacements(
theme,
_placement === 'auto' ? PLACEMENT_DEFAULT : _placement!
_placement === 'auto' ? PLACEMENT_DEFAULT : _placement!,
_fallbackPlacements
);

const {
Expand All @@ -68,7 +70,7 @@ export const TooltipComponent = ({
},
elements: { reference: triggerRef?.current, floating: floatingRef?.current },
placement: floatingPlacement,
middleware: _placement === 'auto' ? [autoPlacement()] : undefined
middleware: _placement === 'auto' ? [autoPlacement()] : [flip({ fallbackPlacements })]
});

useEffect(() => {
Expand Down Expand Up @@ -135,6 +137,9 @@ TooltipComponent.propTypes = {
id: PropTypes.string,
content: PropTypes.node.isRequired,
placement: PropTypes.oneOf(PLACEMENT),
fallbackPlacements: PropTypes.arrayOf(
PropTypes.oneOf(PLACEMENT.filter(placement => placement !== 'auto'))
),
size: PropTypes.oneOf(SIZE),
type: PropTypes.oneOf(TYPE),
zIndex: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
Expand Down
2 changes: 2 additions & 0 deletions packages/tooltips/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export interface ITooltipProps extends Omit<HTMLAttributes<HTMLDivElement>, 'con
delayMS?: number;
/** Defines the content of the tooltip */
content: ReactNode;
/** Provides a list of acceptable fallback placements */
fallbackPlacements?: Exclude<GardenPlacement, 'auto'>[];
/** Adjusts the placement of the tooltip */
placement?: GardenPlacement;
/** Adjusts the padding and font size */
Expand Down