Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/fix-tooltip-arrow-flip.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@storefront-ui/react': patch
---

**[FIXED]** `useTooltip` (and `SfTooltip`) now places the arrow on the side facing the trigger after the `flip` middleware moves the tooltip. The arrow side was computed from the requested `placement` instead of the placement Floating UI resolved, so a tooltip flipped from `top` to `bottom` drew its arrow on the far edge. `usePopover` now also returns the resolved `placement`, matching the Vue composable.
11 changes: 9 additions & 2 deletions packages/sfui/frameworks/react/hooks/usePopover/usePopover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@ export function usePopover(options: UsePopoverOptions) {
strategy: initialStrategy = SfPopoverStrategy.absolute,
} = options;

const { refs, strategy, x, y, middlewareData } = useFloating<HTMLElement>({
const {
refs,
strategy,
x,
y,
middlewareData,
placement: floatingPlacement,
} = useFloating<HTMLElement>({
strategy: initialStrategy,
placement,
open: isOpen,
Expand All @@ -23,5 +30,5 @@ export function usePopover(options: UsePopoverOptions) {
left: x,
};

return { refs, style, middlewareData };
return { refs, style, middlewareData, placement: floatingPlacement };
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export function useTooltip(options?: UseTooltipOptions) {
refs,
style: floatingStyle,
middlewareData,
placement: floatingPlacement,
} = usePopover({
isOpen,
placement,
Expand All @@ -34,7 +35,7 @@ export function useTooltip(options?: UseTooltipOptions) {
function arrowStyle() {
if (middlewareData.arrow) {
const { x: arrowX, y: arrowY } = middlewareData.arrow;
const basePlacement = placement.split('-')[0] as SfPopoverBasePlacement<typeof placement>;
const basePlacement = floatingPlacement.split('-')[0] as SfPopoverBasePlacement<typeof floatingPlacement>;
return {
position: 'absolute',
width: arrowSize,
Expand Down
21 changes: 21 additions & 0 deletions packages/tests/components/SfTooltip/SfTooltip.PageObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,27 @@ export default class SfTooltipObject extends BasePage {
return this;
}

isTooltipBelowTrigger() {
this.container.then(($trigger) => {
this.tooltip.should(($tooltip) => {
expect($tooltip[0].getBoundingClientRect().top).to.be.greaterThan($trigger[0].getBoundingClientRect().top);
});
});
return this;
}

hasArrowOnTopEdge() {
this.tooltip.should(($tooltip) => {
const arrow = $tooltip.find('span')[0];
expect(arrow, 'arrow').to.exist;
const tooltipRect = $tooltip[0].getBoundingClientRect();
const arrowRect = arrow.getBoundingClientRect();
expect(arrowRect.top, 'arrow top').to.be.lessThan(tooltipRect.top);
expect(arrowRect.bottom, 'arrow bottom').to.be.lessThan(tooltipRect.bottom);
});
return this;
}

get tooltip() {
return this.container.get('[role="tooltip"]');
}
Expand Down
9 changes: 9 additions & 0 deletions packages/tests/components/SfTooltip/SfTooltip.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,15 @@ describe('SfTooltip', () => {
});
});

describe('When showArrow=true and there is no room above the trigger', () => {
it('Should flip the tooltip below and keep the arrow pointing at the trigger', () => {
const props = { showArrow: true };
initializeComponent(props);

page().mouseOver().isTooltipVisible().isTooltipBelowTrigger().hasArrowOnTopEdge();
});
});

describe('When no label', () => {
it('Should not show the tooltip', () => {
const props = { label: '' };
Expand Down