Skip to content

Commit

Permalink
feat(tooltip) : add more placemnet
Browse files Browse the repository at this point in the history
  • Loading branch information
Huinno-ParkJinHyun committed Dec 1, 2024
1 parent 7d61448 commit 5504bf3
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 49 deletions.
41 changes: 31 additions & 10 deletions packages/tooltip/src/Tooltip.module.css
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
.wrapper {
position: relative;
display: inline-block;
cursor: pointer;
}

.tooltip {
position: fixed;
background-color: #000000;
Expand All @@ -17,14 +11,13 @@
max-width: 250px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
z-index: 1000;

top: -9999px;
left: -9999px;

opacity: 0;
transform: scale(0.95);
transition: opacity 0.3s ease, transform 0.3s ease;
pointer-events: none;

top: -9999px;
left: -9999px;
}

.tooltip.visible {
Expand All @@ -41,6 +34,34 @@
border-style: solid;
}

.tooltip.top-left::after {
bottom: -6px;
left: 8px;
border-width: 6px 6px 0 6px;
border-color: #000000 transparent transparent transparent;
}

.tooltip.top-right::after {
bottom: -6px;
right: 8px;
border-width: 6px 6px 0 6px;
border-color: #000000 transparent transparent transparent;
}

.tooltip.bottom-left::after {
top: -6px;
left: 8px;
border-width: 0 6px 6px 6px;
border-color: transparent transparent #000000 transparent;
}

.tooltip.bottom-right::after {
top: -6px;
right: 8px;
border-width: 0 6px 6px 6px;
border-color: transparent transparent #000000 transparent;
}

.tooltip.top::after {
bottom: -6px;
left: 50%;
Expand Down
40 changes: 38 additions & 2 deletions packages/tooltip/src/Tooltip.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,18 +110,32 @@ export const AllPlacements = () => (
padding: '50px',
}}
>
<Tooltip tooltipContent="Top Left" placement="top-left">
<button type="button">Top Left</button>
</Tooltip>
<Tooltip tooltipContent="Top Left" placement="top-left" asChild={false}>
<button type="button">Top Left</button>
</Tooltip>

<Tooltip tooltipContent="Top" placement="top">
<button type="button">Top</button>
</Tooltip>
<Tooltip tooltipContent="Top" placement="top" asChild={false}>
<button type="button">Top</button>
</Tooltip>

<Tooltip tooltipContent="Top Right" placement="top-right">
<button type="button">Top Right</button>
</Tooltip>
<Tooltip tooltipContent="Top Right" placement="top-right" asChild={false}>
<button type="button">Top Right</button>
</Tooltip>

<Tooltip tooltipContent="Left" placement="left">
<h1>Left</h1>
<button type="button">Left</button>
</Tooltip>
<Tooltip tooltipContent="Left" placement="left" asChild={false}>
<h1>Left</h1>
<button type="button">Left</button>
</Tooltip>

<Tooltip tooltipContent="Right" placement="right">
Expand All @@ -131,11 +145,33 @@ export const AllPlacements = () => (
<button type="button">Right</button>
</Tooltip>

<Tooltip tooltipContent="Bottom Left" placement="bottom-left">
<button type="button">Bottom Left</button>
</Tooltip>
<Tooltip
tooltipContent="Bottom Left"
placement="bottom-left"
asChild={false}
>
<button type="button">Bottom Left</button>
</Tooltip>

<Tooltip tooltipContent="Bottom" placement="bottom">
<button type="button">Bottom</button>
</Tooltip>
<Tooltip tooltipContent="Bottom" placement="bottom" asChild={false}>
<button type="button">Bottom</button>
</Tooltip>

<Tooltip tooltipContent="Bottom Right" placement="bottom-right">
<button type="button">Bottom Right</button>
</Tooltip>
<Tooltip
tooltipContent="Bottom Right"
placement="bottom-right"
asChild={false}
>
<button type="button">Bottom Right</button>
</Tooltip>
</div>
);
91 changes: 57 additions & 34 deletions packages/tooltip/src/Tooltip.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { useEffect, useState } from 'react';
import { describe, expect, test } from 'vitest';
import { Tooltip } from './Tooltip';
import { Tooltip, type TooltipPosition } from './Tooltip';

describe('Tooltip 기본 동작 테스트', () => {
test('Tooltip은 초기 상태에서 보이지 않아야 한다.', () => {
Expand Down Expand Up @@ -82,23 +82,46 @@ describe('Tooltip 기본 동작 테스트', () => {
});

describe('Tooltip 위치 테스트', () => {
test.each([
['top', 'top'],
['bottom', 'bottom'],
['left', 'left'],
['right', 'right'],
])('Tooltip이 %s 위치에 렌더링된다.', async (placement, expectedClass) => {
test('Tooltip이 기본적으로 top 위치에 렌더링된다.', async () => {
render(
<Tooltip tooltipContent="This is a tooltip" placement={placement as any}>
<Tooltip tooltipContent="This is a tooltip">
<button type="button">Hover me</button>
</Tooltip>,
);

const trigger = screen.getByText('Hover me');
await userEvent.hover(trigger);

const tooltip = screen.getByText('This is a tooltip');
expect(tooltip.className).toContain(expectedClass);
const tooltip = await screen.findByText('This is a tooltip');
expect(tooltip).toBeInTheDocument();
expect(tooltip.className).toContain('top');
});

test.each([
['top-left'],
['top'],
['top-right'],
['bottom-left'],
['bottom'],
['bottom-right'],
['left'],
['right'],
])('Tooltip이 %s 위치에 올바르게 렌더링된다.', async (placement) => {
render(
<Tooltip
tooltipContent="Tooltip content"
placement={placement as TooltipPosition}
>
<button type="button">Trigger</button>
</Tooltip>,
);

const trigger = screen.getByText('Trigger');
await userEvent.hover(trigger);

const tooltip = await screen.findByText('Tooltip content');
expect(tooltip).toBeInTheDocument();
expect(tooltip.className).toContain(placement as string);
});
});

Expand Down Expand Up @@ -191,37 +214,37 @@ describe('Tooltip 스타일 테스트', () => {
});
});

describe('Tooltip 비동기 데이터 테스트', () => {
test('Tooltip이 비동기 데이터로 업데이트된다.', async () => {
const fetchTooltipContent = (): Promise<string> =>
new Promise((resolve) =>
setTimeout(() => resolve('Fetched Content'), 500),
);
test('Tooltip이 비동기 데이터로 업데이트된다.', async () => {
const fetchMockData = async () => {
return new Promise<string>((resolve) =>
setTimeout(() => resolve('Fetched Content'), 500),
);
};

const AsyncTooltip = () => {
const [content, setContent] = useState('Loading...');
const AsyncTooltip = () => {
const [content, setContent] = useState('Loading...');
useEffect(() => {
fetchMockData().then((data) => setContent(data));
}, []);

useEffect(() => {
fetchTooltipContent().then((data) => setContent(data as string));
}, []);
return (
<Tooltip tooltipContent={content}>
<button type="button">Hover me</button>
</Tooltip>
);
};

return (
<Tooltip tooltipContent={content}>
<button type="button">Hover me</button>
</Tooltip>
);
};
render(<AsyncTooltip />);

render(<AsyncTooltip />);
const trigger = screen.getByText('Hover me');

const trigger = screen.getByText('Hover me');
await userEvent.hover(trigger);
await userEvent.hover(trigger);

expect(screen.getByText('Loading...')).toBeInTheDocument();
const loadingTooltip = await screen.findByText('Loading...');
expect(loadingTooltip).toBeInTheDocument();

await new Promise((resolve) => setTimeout(resolve, 500));
expect(screen.getByText('Fetched Content')).toBeInTheDocument();
});
const updatedTooltip = await screen.findByText('Fetched Content');
expect(updatedTooltip).toBeInTheDocument();
});

describe('Tooltip asChild 속성 테스트', () => {
Expand Down
10 changes: 9 additions & 1 deletion packages/tooltip/src/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,15 @@ import { createPortal } from 'react-dom';
import styles from './Tooltip.module.css';
import { useTooltip } from './hooks/useTooltip';

export type TooltipPosition = 'top' | 'bottom' | 'left' | 'right';
export type TooltipPosition =
| 'top-left'
| 'top'
| 'top-right'
| 'bottom-left'
| 'bottom'
| 'bottom-right'
| 'left'
| 'right';

export interface TooltipProps extends ComponentProps<'div'> {
tooltipContent: ReactNode;
Expand Down
26 changes: 24 additions & 2 deletions packages/tooltip/src/hooks/useTooltip/useTooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,14 +124,30 @@ function calculateTooltipPosition({
let left = 0;

switch (placement) {
case 'top-left':
top = wrapperRect.top - tooltipRect.height - gap;
left = wrapperRect.left;
break;
case 'top':
top = wrapperRect.top - tooltipRect.height - gap;
left = wrapperRect.left + wrapperRect.width / 2 - tooltipRect.width / 2;
break;
case 'top-right':
top = wrapperRect.top - tooltipRect.height - gap;
left = wrapperRect.right - tooltipRect.width;
break;
case 'bottom-left':
top = wrapperRect.bottom + gap;
left = wrapperRect.left;
break;
case 'bottom':
top = wrapperRect.bottom + gap;
left = wrapperRect.left + wrapperRect.width / 2 - tooltipRect.width / 2;
break;
case 'bottom-right':
top = wrapperRect.bottom + gap;
left = wrapperRect.right - tooltipRect.width;
break;
case 'left':
top = wrapperRect.top + wrapperRect.height / 2 - tooltipRect.height / 2;
left = wrapperRect.left - tooltipRect.width - gap;
Expand All @@ -142,8 +158,14 @@ function calculateTooltipPosition({
break;
}

top = Math.max(top, gap);
left = Math.max(left, gap);
top = Math.max(
gap,
Math.min(top, window.innerHeight - tooltipRect.height - gap),
);
left = Math.max(
gap,
Math.min(left, window.innerWidth - tooltipRect.width - gap),
);

return { top, left };
}

0 comments on commit 5504bf3

Please sign in to comment.