Skip to content

Commit

Permalink
feature, Update Grid responsiveness behavior and fix Tooltips overflo…
Browse files Browse the repository at this point in the history
…w cuts
  • Loading branch information
Passanelli committed Nov 14, 2024
1 parent f5a5e37 commit 56f3865
Show file tree
Hide file tree
Showing 13 changed files with 262 additions and 190 deletions.
90 changes: 59 additions & 31 deletions packages/polaris-viz/src/components/Grid/Grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
DEFAULT_TEXT_COLOR,
SMALL_CONTAINER_WIDTH,
SMALL_CONTAINER_HEIGHT,
DEFAULT_BOUNDS,
} from './utilities/constants';
import type {
CellGroup,
Expand All @@ -50,8 +51,8 @@ export function Grid(props: GridProps) {
const [hoveredGroup, setHoveredGroup] = useState<CellGroup | null>(null);
const {setRef, entry} = useResizeObserver();
const [tooltipInfo, setTooltipInfo] = useState<TooltipInfo | null>(null);
const [tooltipHeight, setTooltipHeight] = useState(TOOLTIP_HEIGHT);
const [isSmallContainer, setIsSmallContainer] = useState(false);
const [groupSelected, setGroupSelected] = useState<CellGroup | null>(null);

const {
cellGroups = [],
Expand Down Expand Up @@ -89,11 +90,10 @@ export function Grid(props: GridProps) {
};

const getTooltipInfo = useCallback(
(
group: CellGroup,
event: React.MouseEvent | React.KeyboardEvent | React.FocusEvent,
): TooltipInfo | null => {
const rect = event.currentTarget.getBoundingClientRect();
(group: CellGroup): TooltipInfo | null => {
const rect =
document.getElementById(group.id)?.getBoundingClientRect() ||
DEFAULT_BOUNDS;
const containerRect = entry?.target?.getBoundingClientRect();

if (!containerRect) return null;
Expand All @@ -113,12 +113,12 @@ export function Grid(props: GridProps) {
TOOLTIP_HORIZONTAL_OFFSET;
y = rect.top - containerRect.top;
placement = 'left';
} else if (bottomSpace >= tooltipHeight) {
x = rect.left - containerRect.left + TOOLTIP_HORIZONTAL_OFFSET;
} else if (bottomSpace >= TOOLTIP_HEIGHT) {
x = rect.left - containerRect.left;
y = rect.bottom - containerRect.top + TOOLTIP_HORIZONTAL_OFFSET;
placement = 'bottom';
} else {
x = rect.left - containerRect.left + TOOLTIP_HORIZONTAL_OFFSET;
x = rect.left - containerRect.left;
y = rect.top - containerRect.top - TOOLTIP_VERTICAL_OFFSET;
placement = 'top';
}
Expand All @@ -127,25 +127,20 @@ export function Grid(props: GridProps) {
x,
y,
placement,
groupName: group.name,
groupDescription: group.description || '',
groupGoal: group.goal || '',
group,
};
},
[entry, tooltipHeight],
[entry],
);

const handleGroupHover = useCallback(
(
group: CellGroup | null,
event: React.MouseEvent | React.KeyboardEvent | React.FocusEvent,
) => {
(group: CellGroup | null) => {
if (!isSmallContainer) {
if (group) {
const activeGroups = getActiveGroups(group);
setHoveredGroups(activeGroups);
setHoveredGroup(group);
const tooltipInfo = getTooltipInfo(group, event);
const tooltipInfo = getTooltipInfo(group);
if (tooltipInfo) {
setTooltipInfo(tooltipInfo);
}
Expand All @@ -159,6 +154,18 @@ export function Grid(props: GridProps) {
[getTooltipInfo, isSmallContainer],
);

const handleSelectGroup = useCallback(
(group: CellGroup | null) => {
if (!isSmallContainer) {
const actualGroupSelected =
groupSelected?.id === group?.id ? null : group;
setGroupSelected(actualGroupSelected);
handleGroupHover(actualGroupSelected);
}
},
[handleGroupHover, groupSelected?.id],
);

const rawChartPositions = useChartPositions({
height: Math.max(fullChartHeight, 1),
width: Math.max(fullChartWidth, 1),
Expand Down Expand Up @@ -241,8 +248,28 @@ export function Grid(props: GridProps) {
}
}, [entry]);

const handleKeyDown = useCallback(
(event: React.KeyboardEvent) => {
if (event.key === 'Tab') {
event.preventDefault();
const currentIndex = cellGroups.findIndex(
(group) => group.id === groupSelected?.id,
);
const nextIndex =
currentIndex === -1 ? 0 : (currentIndex + 1) % cellGroups.length;
const nextGroup = cellGroups[nextIndex];
setGroupSelected(nextGroup);
handleGroupHover(nextGroup);
} else if (event.key === 'Escape') {
setGroupSelected(null);
handleGroupHover(null);
}
},
[cellGroups, groupSelected, handleGroupHover],
);

return (
<div ref={setRef} className={styles.Container}>
<div ref={setRef} className={styles.Container} onKeyDown={handleKeyDown}>
<svg width="100%" height="100%">
<YAxisLabels
yTicks={yTicks}
Expand Down Expand Up @@ -273,6 +300,8 @@ export function Grid(props: GridProps) {
containerWidth={dimensions.width}
containerHeight={dimensions.height}
isAnimated={isAnimated}
groupSelected={groupSelected}
handleSelectGroup={handleSelectGroup}
/>
))}
<Arrows
Expand All @@ -292,19 +321,18 @@ export function Grid(props: GridProps) {
xAxisOptions={xAxisOptions}
setXAxisHeight={setXAxisHeight}
/>

{tooltipInfo && (
<Tooltip
groupName={tooltipInfo.groupName}
groupDescription={tooltipInfo.groupDescription}
groupGoal={tooltipInfo.groupGoal}
x={tooltipInfo.x}
y={tooltipInfo.y}
tooltipHeight={tooltipHeight}
setTooltipHeight={setTooltipHeight}
/>
)}
</svg>
{tooltipInfo && (
<Tooltip
x={tooltipInfo.x}
y={tooltipInfo.y}
group={groupSelected || hoveredGroup}
onBlur={() => {
setGroupSelected(null);
handleGroupHover(null);
}}
/>
)}
</div>
);
}
Expand Down
25 changes: 17 additions & 8 deletions packages/polaris-viz/src/components/Grid/components/Arrows.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import type {ScaleLinear} from 'd3-scale';

import {
ARROW_X_POSITION_OFFSET,
ARROW_LENGTH,
ARROW_LENGTH_SMALL,
ARROW_HEAD_SIZE_RATIO,
ARROW_Y_OFFSET,
} from '../utilities/constants';

import styles from './Arrows.scss';
Expand Down Expand Up @@ -58,7 +59,7 @@ export function Arrows({
);
return {
x: (startX + endX) / 2,
y: (group1.end.row + 1) * cellHeight,
y: (group1.end.row + 1) * cellHeight + ARROW_Y_OFFSET,
sourceEdge: 'bottom',
targetEdge: 'top',
};
Expand All @@ -75,7 +76,10 @@ export function Arrows({
);
return {
x: (startX + endX) / 2,
y: group1.start.row * cellHeight + ARROW_X_POSITION_OFFSET,
y:
group1.start.row * cellHeight +
ARROW_X_POSITION_OFFSET -
ARROW_Y_OFFSET,
sourceEdge: 'top',
targetEdge: 'bottom',
};
Expand Down Expand Up @@ -128,7 +132,12 @@ export function Arrows({
const sharedEdgeInfo = getSharedEdgeCenter(sourceGroup, targetGroup);
if (!sharedEdgeInfo) return null;

const sourcePoint = {x: sharedEdgeInfo.x, y: sharedEdgeInfo.y};
const sourceXOffset = sharedEdgeInfo.sourceEdge === 'right' ? 10 : 0;

const sourcePoint = {
x: sharedEdgeInfo.x + sourceXOffset,
y: sharedEdgeInfo.y,
};

let targetPoint: {x: number; y: number};
if (
Expand All @@ -139,15 +148,15 @@ export function Arrows({
x: sourcePoint.x,
y:
sharedEdgeInfo.sourceEdge === 'bottom'
? sourcePoint.y + ARROW_LENGTH
: sourcePoint.y - ARROW_LENGTH,
? sourcePoint.y + ARROW_LENGTH_SMALL
: sourcePoint.y - ARROW_LENGTH_SMALL,
};
} else {
targetPoint = {
x:
sharedEdgeInfo.sourceEdge === 'right'
? sourcePoint.x + ARROW_LENGTH
: sourcePoint.x - ARROW_LENGTH,
? sourcePoint.x + ARROW_LENGTH_SMALL
: sourcePoint.x - ARROW_LENGTH_SMALL,
y: sourcePoint.y,
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,15 @@

.GroupCellContainer {
transition: opacity 200ms ease-in-out;
outline: none;
outline: 0;
}

.GroupCellContainerSelected {
// stylelint-disable declaration-no-important
outline: 2px solid $color-blue-100 !important;
outline-offset: -1px;
border-radius: 4px;
// stylelint-enable declaration-no-important
}

@keyframes FadeInScale {
Expand Down
55 changes: 29 additions & 26 deletions packages/polaris-viz/src/components/Grid/components/GroupCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
HIDE_NAME_AND_SECONDARY_VALUE_HEIGHT_THRESHOLD,
HIDE_NAME_AND_SECONDARY_VALUE_WIDTH_THRESHOLD,
} from '../utilities/constants';
import {classNames} from '../../../utilities';

import styles from './GroupCell.scss';
import {Background} from './Background';
Expand All @@ -18,10 +19,9 @@ interface GroupCellProps {
cellWidth: number;
isSmallContainer: boolean;
hoveredGroups: Set<string>;
handleGroupHover: (
group: CellGroup | null,
event: React.MouseEvent | React.KeyboardEvent | React.FocusEvent,
) => void;
handleGroupHover: (group: CellGroup | null) => void;
groupSelected: CellGroup | null;
handleSelectGroup: (group: CellGroup | null) => void;
getColors: (group: CellGroup) => {bgColor: string; textColor: string};
containerWidth: number;
containerHeight: number;
Expand All @@ -37,6 +37,8 @@ export const GroupCell: React.FC<GroupCellProps> = ({
isSmallContainer,
hoveredGroups,
handleGroupHover,
groupSelected,
handleSelectGroup,
getColors,
containerWidth,
containerHeight,
Expand Down Expand Up @@ -67,9 +69,7 @@ export const GroupCell: React.FC<GroupCellProps> = ({
const showNameAndSecondaryValue =
containerWidth > HIDE_NAME_AND_SECONDARY_VALUE_WIDTH_THRESHOLD &&
containerHeight > HIDE_NAME_AND_SECONDARY_VALUE_HEIGHT_THRESHOLD;
const mainFontSize = showNameAndSecondaryValue
? 20
: Math.min(groupWidth, cellHeight) / 4;
const mainFontSize = showNameAndSecondaryValue ? 20 : 16;
const secondaryFontSize = mainFontSize * 0.6;

const groupX = xScale(group.start.col);
Expand All @@ -86,36 +86,39 @@ export const GroupCell: React.FC<GroupCellProps> = ({
}
: null;

const handleKeyDown = useCallback((event: React.KeyboardEvent) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
const handleMouseLeave = useCallback(() => {
if (!isSmallContainer) {
handleGroupHover(null);
}
}, []);

const handleMouseLeave = useCallback(
(event: React.MouseEvent) => {
if (!isSmallContainer) {
handleGroupHover(null, event);
}
},
[isSmallContainer, handleGroupHover],
);
}, [isSmallContainer, handleGroupHover]);

const ariaLabel = `${group.name}, ${group.value}, ${
group.secondaryValue
}${`, ${group.description || ''}${group.goal ? `, ${group.goal}` : ''}`}`;

return (
<g
className={styles.GroupCellContainer}
className={classNames(
styles.GroupCellContainer,
groupSelected?.id === group.id && styles.GroupCellContainerSelected,
)}
id={group.id}
style={{opacity}}
role="button"
data-testid="group-cell"
onMouseEnter={(event) => handleGroupHover(group, event)}
onMouseLeave={handleMouseLeave}
onFocus={(event) => handleGroupHover(group, event)}
onBlur={(event) => handleGroupHover(null, event)}
onKeyDown={handleKeyDown}
onClick={() => {
handleSelectGroup(group);
}}
onMouseEnter={() => {
if (!groupSelected) {
handleGroupHover(group);
}
}}
onMouseLeave={() => {
if (!groupSelected) {
handleMouseLeave();
}
}}
aria-label={ariaLabel}
tabIndex={0}
>
Expand Down
25 changes: 12 additions & 13 deletions packages/polaris-viz/src/components/Grid/components/GroupInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@ import {useChartContext} from '@shopify/polaris-viz-core';
import {truncateText} from '../utilities/truncate-text';
import {
LABEL_FONT_SIZE,
PRIMARY_VALUE_WIDTH_RATIO,
PRIMARY_VALUE_WIDTH_RATIO_SOLO,
SECONDARY_VALUE_WIDTH_RATIO,
GROUP_NAME_WIDTH_MULTIPLIER,
TEXT_Y_OFFSET_WITH_SECONDARY,
VALUES_DIVIDER,
} from '../utilities/constants';

interface GroupInfoProps {
Expand Down Expand Up @@ -53,17 +51,18 @@ export const GroupInfo: React.FC<GroupInfoProps> = ({
[characterWidths],
);

const divider = showNameAndSecondaryValue
? PRIMARY_VALUE_WIDTH_RATIO
: PRIMARY_VALUE_WIDTH_RATIO_SOLO;
const valueAndSecondaryValue = showNameAndSecondaryValue
? `${groupValue}${VALUES_DIVIDER}${groupSecondaryValue}`
: groupValue;
const truncatedValueAndSecondaryValue = getTruncatedText(
valueAndSecondaryValue,
groupWidth,
);
const truncatedValue =
truncatedValueAndSecondaryValue.split(VALUES_DIVIDER)[0];
const truncatedSecondaryValue =
truncatedValueAndSecondaryValue.split(VALUES_DIVIDER)[1];

const truncatedValue = getTruncatedText(groupValue, groupWidth / divider);
const truncatedSecondaryValue = showNameAndSecondaryValue
? getTruncatedText(
groupSecondaryValue,
groupWidth / SECONDARY_VALUE_WIDTH_RATIO,
)
: '';
const truncatedGroupName = showNameAndSecondaryValue
? getTruncatedText(group.name, groupWidth * GROUP_NAME_WIDTH_MULTIPLIER)
: '';
Expand Down
Loading

0 comments on commit 56f3865

Please sign in to comment.