Skip to content

Commit

Permalink
Merge pull request #1761 from Shopify/passanelli/fix_tooltip_position…
Browse files Browse the repository at this point in the history
…_grid

fix, Tooltip should be render inside `polaris_viz_tooltip_root`
  • Loading branch information
mollerjorge authored Nov 20, 2024
2 parents da1d0c1 + 9608735 commit ec9b395
Show file tree
Hide file tree
Showing 9 changed files with 165 additions and 28 deletions.
5 changes: 5 additions & 0 deletions packages/polaris-viz/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## Unreleased

### Fixed

- Tooltip positioning when the grid is inside a container with overflow: hidden.
- Tooltip positioning when the grid has multiple instances.

### Changed

- Changed `TOUCH_FONT_SIZE` constant to `12px`.
Expand Down
51 changes: 30 additions & 21 deletions packages/polaris-viz/src/components/Grid/Grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type {LabelFormatter} from '@shopify/polaris-viz-core';
import {
DEFAULT_CHART_PROPS,
useChartPositions,
useUniqueId,
} from '@shopify/polaris-viz-core';
import {scaleLinear} from 'd3-scale';

Expand All @@ -19,7 +20,6 @@ import {
TOOLTIP_WIDTH,
TOOLTIP_HEIGHT,
TOOLTIP_HORIZONTAL_OFFSET,
TOOLTIP_VERTICAL_OFFSET,
Y_AXIS_LABEL_WIDTH,
X_AXIS_HEIGHT,
DEFAULT_GROUP_COLOR,
Expand Down Expand Up @@ -72,11 +72,23 @@ export function Grid(props: GridProps) {
[entry],
);

const gridId = useUniqueId('grid');

const uniqueGroups = useMemo(() => {
return cellGroups.map((group) => ({
...group,
id: `${group.id}-${gridId}`,
connectedGroups: group.connectedGroups?.map(
(connectedId) => `${connectedId}-${gridId}`,
),
}));
}, [cellGroups, gridId]);

const gridDimensions = useMemo(() => {
const maxRow = Math.max(...cellGroups.map((group) => group.end.row)) + 1;
const maxCol = Math.max(...cellGroups.map((group) => group.end.col)) + 1;
const maxRow = Math.max(...uniqueGroups.map((group) => group.end.row)) + 1;
const maxCol = Math.max(...uniqueGroups.map((group) => group.end.col)) + 1;
return {rows: maxRow, cols: maxCol};
}, [cellGroups]);
}, [uniqueGroups]);

const fullChartWidth = dimensions.width - Y_AXIS_LABEL_WIDTH;
const fullChartHeight = dimensions.height - X_AXIS_HEIGHT;
Expand Down Expand Up @@ -106,20 +118,16 @@ export function Grid(props: GridProps) {
let placement: Placement;

if (leftSpace >= TOOLTIP_WIDTH) {
x =
rect.left -
containerRect.left -
TOOLTIP_WIDTH -
TOOLTIP_HORIZONTAL_OFFSET;
y = rect.top - containerRect.top;
x = rect.left - TOOLTIP_WIDTH - TOOLTIP_HORIZONTAL_OFFSET;
y = rect.top;
placement = 'left';
} else if (bottomSpace >= TOOLTIP_HEIGHT) {
x = rect.left - containerRect.left;
y = rect.bottom - containerRect.top + TOOLTIP_HORIZONTAL_OFFSET;
x = rect.left;
y = rect.bottom + TOOLTIP_HORIZONTAL_OFFSET;
placement = 'bottom';
} else {
x = rect.left - containerRect.left;
y = rect.top - containerRect.top - TOOLTIP_VERTICAL_OFFSET;
x = rect.left;
y = rect.top - TOOLTIP_HEIGHT;
placement = 'top';
}

Expand Down Expand Up @@ -252,20 +260,20 @@ export function Grid(props: GridProps) {
(event: React.KeyboardEvent) => {
if (event.key === 'Tab') {
event.preventDefault();
const currentIndex = cellGroups.findIndex(
const currentIndex = uniqueGroups.findIndex(
(group) => group.id === groupSelected?.id,
);
const nextIndex =
currentIndex === -1 ? 0 : (currentIndex + 1) % cellGroups.length;
const nextGroup = cellGroups[nextIndex];
currentIndex === -1 ? 0 : (currentIndex + 1) % uniqueGroups.length;
const nextGroup = uniqueGroups[nextIndex];
setGroupSelected(nextGroup);
handleGroupHover(nextGroup);
} else if (event.key === 'Escape') {
setGroupSelected(null);
handleGroupHover(null);
}
},
[cellGroups, groupSelected, handleGroupHover],
[uniqueGroups, groupSelected, handleGroupHover],
);

return (
Expand All @@ -285,10 +293,10 @@ export function Grid(props: GridProps) {
cellHeight={cellHeight}
xScale={xScale}
/>
{cellGroups.map((group, index) => (
{uniqueGroups.map((group, index) => (
<GroupCell
index={index}
key={`group-${index}`}
key={group.id}
group={group}
xScale={xScale}
cellHeight={cellHeight}
Expand All @@ -307,7 +315,7 @@ export function Grid(props: GridProps) {
))}
<Arrows
hoveredGroup={hoveredGroup}
cellGroups={cellGroups}
cellGroups={uniqueGroups}
xScale={xScale}
cellHeight={cellHeight}
/>
Expand All @@ -323,6 +331,7 @@ export function Grid(props: GridProps) {
setXAxisHeight={setXAxisHeight}
/>
</svg>

{tooltipInfo && (
<Tooltip
x={tooltipInfo.x}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@

.Tooltip {
font-size: 12px;
font-family: Inter, -apple-system, system-ui, 'San Francisco', 'Segoe UI',
Roboto, 'Helvetica Neue', sans-serif;
line-height: 1.2;
padding: 12px;
background: $color-white;
Expand All @@ -57,6 +59,8 @@
.TooltipContainer {
position: absolute;
z-index: 1;
top: 0;
left: 0;
width: 250px;
outline: none;
@container (max-width: #{$grid-small-breakpoint}) {
Expand Down
15 changes: 12 additions & 3 deletions packages/polaris-viz/src/components/Grid/components/Tooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import {createPortal} from 'react-dom';

import type {CellGroup} from '../types';
import {useRootContainer} from '../../../hooks/useRootContainer';
import {TOOLTIP_ID} from '../../../constants';

import styles from './Tooltip.scss';

Expand All @@ -9,9 +13,13 @@ interface TooltipProps {
}

export function Tooltip({x, y, group}: TooltipProps) {
if (!group) return null;
const container = useRootContainer(TOOLTIP_ID);

if (!group) {
return null;
}

return (
return createPortal(
<div
className={styles.TooltipContainer}
style={{
Expand Down Expand Up @@ -49,6 +57,7 @@ export function Tooltip({x, y, group}: TooltipProps) {
</div>
)}
</div>
</div>
</div>,
container,
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import type {Story} from '@storybook/react';

import {Grid} from '../../Grid';
import type {GridProps} from '../../Grid';
import {CELL_GROUPS} from '../data';
import {META} from '../meta';

export default {
...META,
title: `${META.title}/Playground`,
decorators: [],
};

function GridCard(args: GridProps) {
return (
<div
style={{
height: 400,
width: 800,
background: 'white',
borderRadius: '8px',
padding: 10,
}}
>
<Grid {...args} />
</div>
);
}

const Template: Story<GridProps> = (args: GridProps) => {
return (
<div style={{display: 'flex', gap: '20px'}}>
<GridCard {...args} />
<GridCard {...args} />
</div>
);
};

export const MultipleGridsTooltipPosition = Template.bind({});

MultipleGridsTooltipPosition.args = {
cellGroups: CELL_GROUPS,
xAxisOptions: {
label: 'Recency',
},
yAxisOptions: {
label: 'Frequency',
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import type {Story} from '@storybook/react';

import {Grid} from '../../Grid';
import type {GridProps} from '../../Grid';
import {CELL_GROUPS} from '../data';
import {META} from '../meta';

export default {
...META,
title: `${META.title}/Playground`,
decorators: [],
};

function GridCard(args: GridProps) {
return (
<div
style={{
height: 400,
width: 800,
background: 'white',
border: '1px solid #e0e0e0',
borderRadius: '8px',
padding: 10,
overflow: 'hidden',
position: 'relative',
}}
>
<Grid {...args} />
</div>
);
}

const Template: Story<GridProps> = (args: GridProps) => {
return (
<div style={{padding: '50px'}}>
<div style={{display: 'flex', gap: '20px'}}>
<GridCard {...args} />
</div>
</div>
);
};

export const TooltipOverflow = Template.bind({});

TooltipOverflow.args = {
cellGroups: CELL_GROUPS,
xAxisOptions: {
label: 'Recency',
},
yAxisOptions: {
label: 'Frequency',
},
};

TooltipOverflow.parameters = {
docs: {
description: {
story:
'This story tests that tooltips render correctly when the Grid is inside a container with overflow: hidden.',
},
},
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
export const TOOLTIP_WIDTH = 250;
export const TOOLTIP_HEIGHT = 120;
export const TOOLTIP_HEIGHT = 155;
export const TOOLTIP_HORIZONTAL_OFFSET = 10;
export const TOOLTIP_VERTICAL_OFFSET = 155;

export const Y_LABEL_OFFSET = 25;
export const Y_AXIS_LABEL_WIDTH = 50;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,14 @@ import {createPortal} from 'react-dom';
import {useRootContainer} from '../../hooks/useRootContainer';
import type {Margin} from '../../types';
import {SwallowErrors} from '../SwallowErrors';
import {TOOLTIP_ID} from '../../constants';

import {shouldBlockTooltipEvents} from './utilities/shouldBlockTooltipEvents';
import type {TooltipPosition, TooltipPositionParams} from './types';
import {DEFAULT_TOOLTIP_POSITION} from './constants';
import {TooltipAnimatedContainer} from './components/TooltipAnimatedContainer';
import type {AlteredPosition} from './utilities';

const TOOLTIP_ID = 'polaris_viz_tooltip_root';

interface BaseProps {
chartBounds: BoundingRect;
getMarkup: (index: number) => ReactNode;
Expand Down
1 change: 1 addition & 0 deletions packages/polaris-viz/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,4 @@ export const WARN_FOR_DEVELOPMENT = IS_DEVELOPMENT && !IS_TEST;
export const HOVER_TARGET_ZONE = 48;
export const CROSSHAIR_ID = 'Crosshair';
export const DEFAULT_ANIMATION_DELAY = 100;
export const TOOLTIP_ID = 'polaris_viz_tooltip_root';

0 comments on commit ec9b395

Please sign in to comment.