Skip to content

Commit

Permalink
refactor: placement에 따른 highlight tooltip 화살표 모양 수정
Browse files Browse the repository at this point in the history
  • Loading branch information
dmswl98 committed Jan 8, 2024
1 parent e1e5c7e commit 0d96fce
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 32 deletions.
12 changes: 4 additions & 8 deletions src/components/Tooltip/Tooltip.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,11 @@ export const Minimal: Story = {
},
args: {
children: 'Minimal',
hasArrow: false,
placement: 'bottom',
},
argTypes: {
hasArrow: { control: false },
},
render: (args) => (
<Tooltip placement={args.placement}>
<Tooltip hasArrow={args.hasArrow} placement={args.placement}>
<Tooltip.Trigger>
<div>{args.children}</div>
</Tooltip.Trigger>
Expand All @@ -96,12 +94,10 @@ export const Highlight: Story = {
args: {
children: 'Highlight',
hasArrow: true,
},
argTypes: {
placement: { control: false },
placement: 'bottom',
},
render: (args) => (
<Tooltip hasArrow={args.hasArrow}>
<Tooltip hasArrow={args.hasArrow} placement={args.placement}>
<Tooltip.Trigger>
<div>{args.children}</div>
</Tooltip.Trigger>
Expand Down
25 changes: 9 additions & 16 deletions src/components/Tooltip/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,21 @@ import TooltipContent from './TooltipContent';
import TooltipTrigger from './TooltipTrigger';

const INIT_POSITION = { top: 0, left: 0 };
const MINIMAL_TOOLTIP_PADDING = 8;

export type Placement = 'top' | 'bottom';
export interface TooltipShape {
hasArrow?: boolean;
placement?: 'top' | 'bottom';
}

interface TooltipContextProps {
interface TooltipContextProps extends TooltipShape {
tooltipRef: Ref<HTMLDivElement>;
isVisible: boolean;
hasArrow?: boolean;
placement?: Placement;
position: typeof INIT_POSITION;
onOpenTooltip: () => void;
onCloseTooltip: () => void;
}

interface TooltipProps
extends Pick<TooltipContextProps, 'hasArrow' | 'placement'> {}
interface TooltipProps extends TooltipShape {}

export const TooltipContext = createContext<TooltipContextProps | null>(null);

Expand All @@ -41,16 +40,9 @@ const TooltipRoot = ({
return;
}

const { top, left } = getPosition(tooltipRef.current, placement);
const { top, left } = getPosition(tooltipRef.current, hasArrow, placement);

if (!hasArrow && placement === 'top') {
setPosition({
top: top - MINIMAL_TOOLTIP_PADDING,
left,
});
} else {
setPosition({ top, left });
}
setPosition({ top, left });
}, [isVisible, hasArrow, placement]);

const handleTooltipOpen = () => {
Expand All @@ -67,6 +59,7 @@ const TooltipRoot = ({
tooltipRef,
isVisible,
hasArrow,
placement,
position,
onOpenTooltip: handleTooltipOpen,
onCloseTooltip: handleTooltipClose,
Expand Down
13 changes: 11 additions & 2 deletions src/components/Tooltip/TooltipContent.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
import type { PropsWithChildren } from 'react';
import { assignInlineVars } from '@vanilla-extract/dynamic';
import clsx from 'clsx';

import { useTooltipContext } from '@/src/hooks/useTooltipContext';

import * as styles from './style.css';
import TooltipPortal from './TooltipPortal';

const ARROW_STYLE = {
top: styles.bottomArrow,
bottom: styles.topArrow,
};

const TooltipContent = ({ children }: PropsWithChildren) => {
const { isVisible, hasArrow, position } = useTooltipContext();
const { isVisible, hasArrow, placement, position } = useTooltipContext();

return (
<>
{isVisible && (
<TooltipPortal>
<div
className={styles.content({ hasArrow })}
className={clsx(
styles.content({ hasArrow }),
hasArrow && ARROW_STYLE[placement],
)}
style={assignInlineVars({
[styles.top]: `${position.top}px`,
[styles.left]: `${position.left}px`,
Expand Down
21 changes: 18 additions & 3 deletions src/components/Tooltip/style.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,36 @@ export const content = recipe({
padding: '8px 12px',
},
true: {
marginTop: '6px',
padding: '16px',

'::before': {
content: '',
width: 0,
height: 0,
position: 'absolute',
top: '-12px',
left: '50%',
transform: 'translateX(-50%)',
border: `6px solid ${COLORS['Grey/White']}`,
borderBottomColor: COLORS['Dim/70'],
},
},
},
},
});

export const topArrow = style({
marginTop: '6px',

'::before': {
top: '-12px',
borderBottomColor: COLORS['Dim/70'],
},
});

export const bottomArrow = style({
marginBottom: '6px',

'::before': {
bottom: '-12px',
borderTopColor: COLORS['Dim/70'],
},
});
13 changes: 10 additions & 3 deletions src/utils/getPosition.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
import type { Placement } from '../components/Tooltip/Tooltip';
import type { TooltipShape } from '../components/Tooltip/Tooltip';

const TOOLTIP_MARGIN = {
MINIMAL: 8,
HIGHLIGHT: 32,
};

export const getPosition = (
triggerElement: HTMLDivElement,
placement: Placement,
hasArrow: TooltipShape['hasArrow'],
placement: TooltipShape['placement'],
) => {
const { x, y, width, height } = triggerElement.getBoundingClientRect();
const { scrollX, scrollY } = window;

const margin = hasArrow ? TOOLTIP_MARGIN.HIGHLIGHT : TOOLTIP_MARGIN.MINIMAL;
const left = x + scrollX + width / 2;

switch (placement) {
case 'top':
return { top: y + scrollY - height, left };
return { top: y + scrollY - height - margin, left };
case 'bottom':
return { top: y + scrollY + height, left };
default:
Expand Down

0 comments on commit 0d96fce

Please sign in to comment.