Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[@mantine/core] Floating Tooltip: position bug at the extremeties #1219

Merged
merged 15 commits into from
Apr 10, 2022
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
5 changes: 0 additions & 5 deletions src/mantine-core/src/components/Popper/Popper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,6 @@ export interface SharedPopperProps {

/** Mount/unmount transition timing function, defaults to theme.transitionTimingFunction */
transitionTimingFunction?: string;

/** Exact coordinates where to position popper on page */
coordinates?: { x: number; y: number };
}

export interface PopperProps<T extends HTMLElement> extends SharedPopperProps {
Expand Down Expand Up @@ -128,7 +125,6 @@ export function Popper<T extends HTMLElement = HTMLDivElement>({
modifiers = [],
onTransitionEnd,
withinPortal = true,
coordinates,
}: PopperProps<T>) {
const padding = withArrow ? gutter + arrowSize : gutter;
const { classes, cx, theme } = useStyles({ arrowSize, arrowDistance }, { name: 'Popper' });
Expand Down Expand Up @@ -175,7 +171,6 @@ export function Popper<T extends HTMLElement = HTMLDivElement>({
style={{
...styles.popper,
pointerEvents: 'none',
...(coordinates && { top: coordinates.y || 0, left: coordinates.x || 0 }),
}}
{...attributes.popper}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ export default createStyles((theme, { color, radius }: FloatingTooltipStylesPara
borderRadius: theme.fn.radius(radius),
padding: `${theme.spacing.xs / 2}px ${theme.spacing.xs}px`,
color: theme.colorScheme === 'dark' ? theme.colors.dark[9] : theme.white,
position: 'relative',
position: 'absolute',
overflow: 'hidden',
textOverflow: 'ellipsis',
pointerEvents: 'none',
},
}));
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { itRendersChildren, itSupportsSystemProps, renderWithAct, actAsync } fro
import { FloatingTooltip, FloatingTooltipProps } from './FloatingTooltip';

const defaultProps: FloatingTooltipProps = {
withinPortal: false,
label: 'test-tooltip',
children: 'test-target',
transitionDuration: 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,13 @@ import {
} from '@mantine/styles';
import { useMergedRef, useMouse } from '@mantine/hooks';
import { Box } from '../../Box';
import { Popper, SharedPopperProps } from '../../Popper';
import { MantineTransition, Transition } from '../../Transition';
import useStyles from './FloatingTooltip.styles';

export type FloatingTooltipStylesNames = ClassNames<typeof useStyles>;

export interface FloatingTooltipProps
extends DefaultProps<FloatingTooltipStylesNames>,
SharedPopperProps,
React.ComponentPropsWithoutRef<'div'> {
/** Tooltip content */
label: React.ReactNode;
Expand Down Expand Up @@ -54,14 +53,28 @@ export interface FloatingTooltipProps
/** useEffect dependencies to force update tooltip position */
positionDependencies?: any[];

/** Whether to render the target element in a Portal */
withinPortal?: boolean;
/** Position of the tooltip relative to the cursor */
position?: 'top' | 'left' | 'bottom' | 'right';

/** Customize mount/unmount transition */
transition?: MantineTransition;

/** Mount transition duration in ms */
transitionDuration?: number;

/** Unmount transition duration in ms */
exitTransitionDuration?: number;

/** Mount/unmount transition timing function, defaults to theme.transitionTimingFunction */
transitionTimingFunction?: string;

/** Tooltip z-index */
zIndex?: number;
}

const defaultProps: Partial<FloatingTooltipProps> = {
openDelay: 0,
closeDelay: 0,
gutter: 0,
color: 'gray',
disabled: false,
position: 'right',
Expand All @@ -71,7 +84,6 @@ const defaultProps: Partial<FloatingTooltipProps> = {
width: 'auto',
wrapLines: false,
positionDependencies: [],
withinPortal: true,
};

export const FloatingTooltip = forwardRef<HTMLDivElement, FloatingTooltipProps>(
Expand All @@ -82,7 +94,6 @@ export const FloatingTooltip = forwardRef<HTMLDivElement, FloatingTooltipProps>(
children,
openDelay,
closeDelay,
gutter,
color,
radius,
disabled,
Expand All @@ -94,7 +105,6 @@ export const FloatingTooltip = forwardRef<HTMLDivElement, FloatingTooltipProps>(
width,
wrapLines,
positionDependencies,
withinPortal,
tooltipRef,
tooltipId,
classNames,
Expand All @@ -115,27 +125,43 @@ export const FloatingTooltip = forwardRef<HTMLDivElement, FloatingTooltipProps>(
const [opened, setOpened] = useState(false);
const { ref: mouseRef, x, y } = useMouse();
const visible = opened && !disabled;
const [referenceElement, setReferenceElement] = useState(null);
const mergedRefs = useMergedRef(ref, setReferenceElement, mouseRef);
const mergedRefs = useMergedRef(ref, mouseRef);
const coordinates = useMemo(() => {
// There's no way to get the exact size of the
// cursor using JS, however most OS's use 32x32
const estimatedCursorSize = 32;
const tooltipWidth = _tooltipRef.current?.offsetWidth || 0;
const tooltipWidth =
typeof width === 'number' ? width : _tooltipRef.current?.offsetWidth || 0;
const tooltipHeight = _tooltipRef.current?.offsetHeight || 0;

switch (position) {
case 'top':
return { x: x - tooltipWidth / 2, y: y - estimatedCursorSize };
return {
left: x - tooltipWidth / 2,
top: y - tooltipHeight,
};
case 'left':
return { x: x - estimatedCursorSize / 2 - tooltipWidth, y };
return {
left: x - tooltipWidth - estimatedCursorSize / 2,
top: y,
};
case 'right':
return { x: x + estimatedCursorSize / 2, y };
return {
left: x + estimatedCursorSize / 2,
top: y,
};
case 'bottom':
return { x: x - tooltipWidth / 2, y: y + estimatedCursorSize };
return {
left: x - tooltipWidth / 2,
top: y + tooltipHeight,
};
default:
return { x: x || 0, y: y || 0 };
return {
left: x || 0,
top: y || 0,
};
}
}, [x, y]);
}, [x, y, ...positionDependencies]);

const handleOpen = () => {
window.clearTimeout(closeTimeoutRef.current);
Expand Down Expand Up @@ -185,30 +211,29 @@ export const FloatingTooltip = forwardRef<HTMLDivElement, FloatingTooltipProps>(
ref={mergedRefs}
{...others}
>
<Popper
referenceElement={referenceElement}
transitionDuration={transitionDuration}
transition={transition}
<Transition
mounted={visible}
position="top"
placement="start" // fixed because we are following the cursor
gutter={gutter}
zIndex={zIndex}
forceUpdateDependencies={[color, radius, ...positionDependencies]}
withinPortal={withinPortal}
coordinates={coordinates}
duration={transitionDuration}
transition={transition}
exitDuration={transitionDuration}
timingFunction={transitionTimingFunction}
>
<Box
className={classes.body}
ref={mergedTooltipRefs}
sx={{
whiteSpace: wrapLines ? 'normal' : 'nowrap',
width,
}}
>
{label}
</Box>
</Popper>
{(transitionStyles) => (
<div style={{ ...transitionStyles, zIndex, position: 'relative' }}>
<Box
className={classes.body}
ref={mergedTooltipRefs}
sx={{
whiteSpace: wrapLines ? 'normal' : 'nowrap',
width,
...coordinates,
}}
>
{label}
</Box>
</div>
)}
</Transition>
{children}
</Box>
);
Expand Down