Skip to content

Commit

Permalink
[@mantine/core] Integrate Floating UI tooltip middleware in Tooltip, …
Browse files Browse the repository at this point in the history
…Popover and other components that are based on Popover (#2521)

* fix: Tooltip arrow postion

* fix: prettier code style

* feat: add arrow postion story in Tooltip & Popover / update floating-ui to the latest

* fix: typo
  • Loading branch information
lawff committed Sep 24, 2022
1 parent 3efdd88 commit 067eca1
Show file tree
Hide file tree
Showing 16 changed files with 188 additions and 62 deletions.
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -126,7 +126,7 @@
"@emotion/react": "11.9.3",
"@emotion/server": "11.4.0",
"@emotion/styled": "^11.10.0",
"@floating-ui/react-dom-interactions": "0.6.6",
"@floating-ui/react-dom-interactions": "^0.10.1",
"@radix-ui/react-scroll-area": "1.0.0",
"@tabler/icons": "^1.68.0",
"clsx": "1.1.1",
Expand Down
2 changes: 1 addition & 1 deletion src/mantine-core/package.json
Expand Up @@ -36,7 +36,7 @@
"@mantine/styles": "5.4.0",
"@radix-ui/react-scroll-area": "1.0.0",
"react-textarea-autosize": "8.3.4",
"@floating-ui/react-dom-interactions": "0.6.6"
"@floating-ui/react-dom-interactions": "^0.10.1"
},
"devDependencies": {}
}
52 changes: 26 additions & 26 deletions src/mantine-core/src/Floating/FloatingArrow/FloatingArrow.tsx
@@ -1,4 +1,4 @@
import React from 'react';
import React, { forwardRef } from 'react';
import { useMantineTheme } from '@mantine/styles';
import { getArrowPositionStyles } from './get-arrow-position-styles';
import { FloatingPosition } from '../types';
Expand All @@ -8,35 +8,35 @@ interface FloatingArrowProps extends React.ComponentPropsWithoutRef<'div'> {
position: FloatingPosition;
arrowSize: number;
arrowOffset: number;
arrowX: number;
arrowY: number;
visible: boolean;
}

export function FloatingArrow({
withBorder,
position,
arrowSize,
arrowOffset,
visible,
...others
}: FloatingArrowProps) {
const theme = useMantineTheme();
export const FloatingArrow = forwardRef<HTMLDivElement, FloatingArrowProps>(
({ withBorder, position, arrowSize, arrowOffset, visible, arrowX, arrowY, ...others }, ref) => {
const theme = useMantineTheme();

if (!visible) {
return null;
}
if (!visible) {
return null;
}

return (
<div
{...others}
style={getArrowPositionStyles({
withBorder,
position,
arrowSize,
arrowOffset,
dir: theme.dir,
})}
/>
);
}
return (
<div
{...others}
ref={ref}
style={getArrowPositionStyles({
withBorder,
position,
arrowSize,
arrowOffset,
dir: theme.dir,
arrowX,
arrowY,
})}
/>
);
}
);

FloatingArrow.displayName = '@mantine/core/FloatingArrow';
Expand Up @@ -2,11 +2,11 @@ import type { FloatingPosition, FloatingSide, FloatingPlacement } from '../types

function horizontalSide(
placement: FloatingPlacement | 'center',
arrowSize: number,
arrowY: number,
arrowOffset: number
) {
if (placement === 'center') {
return { top: `calc(50% - ${arrowSize / 2}px)` };
return { top: arrowY };
}

if (placement === 'end') {
Expand All @@ -22,12 +22,12 @@ function horizontalSide(

function verticalSide(
placement: FloatingPlacement | 'center',
arrowSize: number,
arrowX: number,
arrowOffset: number,
dir: 'rtl' | 'ltr'
) {
if (placement === 'center') {
return { left: `calc(50% - ${arrowSize / 2}px)` };
return { [dir === 'ltr' ? 'left' : 'right']: arrowX };
}

if (placement === 'end') {
Expand All @@ -46,12 +46,16 @@ export function getArrowPositionStyles({
withBorder,
arrowSize,
arrowOffset,
arrowX,
arrowY,
dir,
}: {
position: FloatingPosition;
withBorder: boolean;
arrowSize: number;
arrowOffset: number;
arrowX: number;
arrowY: number;
dir: 'rtl' | 'ltr';
}) {
const [side, placement = 'center'] = position.split('-') as [FloatingSide, FloatingPlacement];
Expand All @@ -67,7 +71,7 @@ export function getArrowPositionStyles({
if (side === 'left') {
return {
...baseStyles,
...horizontalSide(placement, arrowSize, arrowOffset),
...horizontalSide(placement, arrowY, arrowOffset),
right: arrowPosition,
borderLeft: 0,
borderBottom: 0,
Expand All @@ -77,7 +81,7 @@ export function getArrowPositionStyles({
if (side === 'right') {
return {
...baseStyles,
...horizontalSide(placement, arrowSize, arrowOffset),
...horizontalSide(placement, arrowY, arrowOffset),
left: arrowPosition,
borderRight: 0,
borderTop: 0,
Expand All @@ -87,7 +91,7 @@ export function getArrowPositionStyles({
if (side === 'top') {
return {
...baseStyles,
...verticalSide(placement, arrowSize, arrowOffset, dir),
...verticalSide(placement, arrowX, arrowOffset, dir),
bottom: arrowPosition,
borderTop: 0,
[dir === 'ltr' ? 'borderLeft' : 'borderRight']: 0,
Expand All @@ -97,7 +101,7 @@ export function getArrowPositionStyles({
if (side === 'bottom') {
return {
...baseStyles,
...verticalSide(placement, arrowSize, arrowOffset, dir),
...verticalSide(placement, arrowX, arrowOffset, dir),
top: arrowPosition,
borderBottom: 0,
[dir === 'ltr' ? 'borderRight' : 'borderLeft']: 0,
Expand Down
3 changes: 3 additions & 0 deletions src/mantine-core/src/Popover/Popover.context.ts
Expand Up @@ -9,6 +9,9 @@ import { PopoverWidth } from './Popover.types';
interface PopoverContext {
x: number;
y: number;
arrowX: number;
arrowY: number;
arrowRef: React.RefObject<HTMLDivElement>;
opened: boolean;
transition: MantineTransition;
transitionDuration: number;
Expand Down
14 changes: 14 additions & 0 deletions src/mantine-core/src/Popover/Popover.story.tsx
Expand Up @@ -20,6 +20,20 @@ export function Uncontrolled() {
);
}

export function WithArrow() {
return (
<div style={{ padding: 40 }}>
<Popover withArrow width={400}>
<Popover.Target>
<Button>arrow popover</Button>
</Popover.Target>

<Popover.Dropdown>Dropdown with arrow</Popover.Dropdown>
</Popover>
</div>
);
}

export function Usage() {
const [opened, setState] = useState(false);

Expand Down
7 changes: 6 additions & 1 deletion src/mantine-core/src/Popover/Popover.tsx
@@ -1,6 +1,6 @@
/* eslint-disable react/no-unused-prop-types */

import React from 'react';
import React, { useRef } from 'react';
import { useId, useClickOutside } from '@mantine/hooks';
import {
useMantineTheme,
Expand Down Expand Up @@ -138,6 +138,7 @@ const defaultProps: Partial<PopoverProps> = {
};

export function Popover(props: PopoverProps) {
const arrowRef = useRef<HTMLDivElement | null>(null);
const {
children,
position,
Expand Down Expand Up @@ -181,6 +182,7 @@ export function Popover(props: PopoverProps) {
width,
position: getFloatingPosition(theme.dir, position),
offset: offset + (withArrow ? arrowSize / 2 : 0),
arrowRef,
onPositionChange,
positionDependencies,
opened,
Expand Down Expand Up @@ -209,7 +211,10 @@ export function Popover(props: PopoverProps) {
floating: popover.floating.floating,
x: popover.floating.x,
y: popover.floating.y,
arrowX: popover.floating?.middlewareData?.arrow?.x,
arrowY: popover.floating?.middlewareData?.arrow?.y,
opened: popover.opened,
arrowRef,
transition,
transitionDuration,
exitTransitionDuration,
Expand Down
Expand Up @@ -73,6 +73,9 @@ export function PopoverDropdown({ style, className, children, ...others }: Popov
{children}

<FloatingArrow
ref={ctx.arrowRef}
arrowX={ctx.arrowX}
arrowY={ctx.arrowY}
visible={ctx.withArrow}
withBorder
position={ctx.placement}
Expand Down
4 changes: 4 additions & 0 deletions src/mantine-core/src/Popover/use-popover.ts
Expand Up @@ -3,6 +3,7 @@ import {
useFloating,
shift,
flip,
arrow,
offset,
size,
Middleware,
Expand All @@ -22,6 +23,7 @@ interface UsePopoverOptions {
onOpen?(): void;
width: PopoverWidth;
middlewares: PopoverMiddlewares;
arrowRef: React.RefObject<HTMLDivElement>;
}

function getPopoverMiddlewares(options: UsePopoverOptions) {
Expand All @@ -35,6 +37,8 @@ function getPopoverMiddlewares(options: UsePopoverOptions) {
middlewares.push(flip());
}

middlewares.push(arrow({ element: options.arrowRef }));

return middlewares;
}

Expand Down
9 changes: 9 additions & 0 deletions src/mantine-core/src/Tooltip/Tooltip.story.tsx
Expand Up @@ -92,3 +92,12 @@ export const WithinGroup = () => (
</Tooltip>
</Group>
);

export const WithArrow = () => (
<Tooltip
withArrow
label="Tooltip button with arrow Tooltip button with arrow Tooltip button with arrow"
>
<Button type="button">Tooltip button with arrow</Button>
</Tooltip>
);
7 changes: 6 additions & 1 deletion src/mantine-core/src/Tooltip/Tooltip.tsx
@@ -1,4 +1,4 @@
import React, { cloneElement, forwardRef } from 'react';
import React, { cloneElement, forwardRef, useRef } from 'react';
import { isElement, ForwardRefWithStaticComponents } from '@mantine/utils';
import { useMergedRef } from '@mantine/hooks';
import { getDefaultZIndex, useComponentDefaultProps } from '@mantine/styles';
Expand Down Expand Up @@ -67,6 +67,7 @@ const defaultProps: Partial<TooltipProps> = {
};

const _Tooltip = forwardRef<HTMLElement, TooltipProps>((props, ref) => {
const arrowRef = useRef<HTMLDivElement | null>(null);
const {
children,
position,
Expand Down Expand Up @@ -114,6 +115,7 @@ const _Tooltip = forwardRef<HTMLElement, TooltipProps>((props, ref) => {
onPositionChange,
opened,
events,
arrowRef,
offset: offset + (withArrow ? arrowSize / 2 : 0),
positionDependencies: [...positionDependencies, children],
});
Expand Down Expand Up @@ -150,6 +152,9 @@ const _Tooltip = forwardRef<HTMLElement, TooltipProps>((props, ref) => {
{label}

<FloatingArrow
ref={arrowRef}
arrowX={tooltip.arrowX}
arrowY={tooltip.arrowY}
visible={withArrow}
withBorder={false}
position={tooltip.placement}
Expand Down
25 changes: 23 additions & 2 deletions src/mantine-core/src/Tooltip/use-tooltip.ts
Expand Up @@ -2,6 +2,7 @@ import { useState, useCallback } from 'react';
import {
useFloating,
flip,
arrow,
offset,
shift,
useInteractions,
Expand All @@ -23,6 +24,7 @@ interface UseTooltip {
onPositionChange?(position: FloatingPosition): void;
opened?: boolean;
offset: number;
arrowRef?: React.RefObject<HTMLDivElement>;
events: { hover: boolean; focus: boolean; touch: boolean };
positionDependencies: any[];
}
Expand All @@ -47,11 +49,28 @@ export function useTooltip(settings: UseTooltip) {
[setCurrentId, uid]
);

const { x, y, reference, floating, context, refs, update, placement } = useFloating({
const {
x,
y,
reference,
floating,
context,
refs,
update,
placement,
middlewareData: { arrow: { x: arrowX, y: arrowY } = {} },
} = useFloating({
placement: settings.position,
open: opened,
onOpenChange: onChange,
middleware: [offset(settings.offset), shift({ padding: 8 }), flip()],
middleware: [
offset(settings.offset),
shift({ padding: 8 }),
flip(),
arrow({
element: settings.arrowRef,
}),
],
});

const { getReferenceProps, getFloatingProps } = useInteractions([
Expand Down Expand Up @@ -82,6 +101,8 @@ export function useTooltip(settings: UseTooltip) {
return {
x,
y,
arrowX,
arrowY,
reference,
floating,
getFloatingProps,
Expand Down

0 comments on commit 067eca1

Please sign in to comment.