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

[Feat] ToolTip 컴포넌트 구현 #285

Merged
merged 13 commits into from
Oct 24, 2024
3 changes: 3 additions & 0 deletions src/common/asset/svg/ic_tooltip_arrow.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
84 changes: 84 additions & 0 deletions src/common/component/ToolTip/ToolTip.style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { css } from '@emotion/react';

import { ToolTipProps } from '@/common/component/ToolTip/ToolTip';
import { theme } from '@/common/style/theme/theme';

export const containerStyle = css({
position: 'relative',
border: '1px solid',
});

export const messageStyle = (isVisible: boolean) =>
css({
position: 'absolute',

width: 'max-content',
padding: '1rem',
borderRadius: '8px',

backgroundColor: `${theme.colors.gray_900}`,
font: `${theme.text.body08}`,
color: `${theme.colors.white}`,

visibility: isVisible ? 'visible' : 'hidden',
transitionDelay: '0.2s',
pointerEvents: 'none',
});

export const positionStyle = (position: Required<ToolTipProps>['position'], gap: number) => {
const style = {
top: css({
left: '50%',
bottom: '100%',
transform: `translateX(-50%) translateY(calc(-${gap}rem - 8px) )`,
}),
bottom: css({
left: '50%',
up: '100%',
transform: `translateX(-50%) translateY(calc(${gap}rem + 8px))`,
}),
left: css({
top: '50%',
right: '100%',
transform: `translateY(-50%) translateX(calc(-${gap}rem - 8px))`,
}),
right: css({
top: '50%',
left: '100%',
transform: `translateY(-50%) translateX(calc(${gap}rem + 8px))`,
}),
};

return style[position];
};

export const arrowStyle = css({
position: 'absolute',
});
Comment on lines +55 to +57
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

툴팁 내용이 많아지는 경우 svg크기가 줄어들더라고요 !
flexShrink: 0으로 해결할 수 있을 것 같은데 한 번 수정해보시면 좋을 것 같아요


export const arrowPositionStyle = (position: Required<ToolTipProps>['position']) => {
const style = {
top: css({
left: '50%',
top: 'calc(100%)',
transform: `translateX(-50%) translateY(-1px) rotate(270deg)`,
}),
bottom: css({
left: '50%',
bottom: 'calc(100%)',
transform: `translateX(-50%) translateY(1px) rotate(90deg)`,
}),
left: css({
top: '50%',
left: 'calc(100%)',
transform: `translateY(-50%) translateX(-1px) rotate(180deg)`,
}),
right: css({
top: '50%',
right: 'calc(100%)',
transform: `translateY(-50%) translateX(1px)`,
}),
};

return style[position];
};
53 changes: 53 additions & 0 deletions src/common/component/ToolTip/ToolTip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { HTMLAttributes, useState } from 'react';

import Arrow from '@/common/asset/svg/ic_tooltip_arrow.svg?react';
import {
arrowPositionStyle,
arrowStyle,
containerStyle,
messageStyle,
positionStyle,
} from '@/common/component/ToolTip/ToolTip.style';

export interface ToolTipProps extends HTMLAttributes<HTMLSpanElement> {
position?: 'top' | 'right' | 'bottom' | 'left';
message: string;
gap?: number;
}

const ToolTip = ({ position = 'right', message, gap = 0, children, ...props }: ToolTipProps) => {
const [isVisible, setIsVisible] = useState(false);

const handleToolTipVisible = () => {
setIsVisible(true);
};
const handleToolTipHidden = () => {
setIsVisible(false);
};
const handleKeyDown = (event: React.KeyboardEvent) => {
if (event.key === 'Escape') {
handleToolTipHidden();
}
};

return (
<div
aria-describedby={message}
role="button"
css={containerStyle}
tabIndex={0}
onMouseEnter={handleToolTipVisible}
onMouseLeave={handleToolTipHidden}
onKeyDown={handleKeyDown}
onFocus={handleToolTipVisible}
onBlur={handleToolTipHidden}>
{children}
<span role="tooltip" css={[messageStyle(isVisible), positionStyle(position, gap)]} {...props}>
<Arrow css={[arrowStyle, arrowPositionStyle(position)]} />
{message}
</span>
</div>
);
};

export default ToolTip;
16 changes: 10 additions & 6 deletions src/shared/component/SideNavBar/LeftSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import TikiLogo from '@/common/asset/svg/logo_symbol.svg?react';
import Menu from '@/common/component/Menu/Menu';
import MenuItem from '@/common/component/Menu/MenuItem/MenuItem';
import MenuList from '@/common/component/Menu/MenuList/MenuList';
import ToolTip from '@/common/component/ToolTip/ToolTip';
import { useOverlay } from '@/common/hook';
import { useOutsideClick } from '@/common/hook/useOutsideClick';

Expand Down Expand Up @@ -77,12 +78,15 @@ const LeftSidebar = () => {
<nav>
<TikiLogo css={tikiLogoStyle} />
<ul css={leftSidebarMenuStyle}>
<LeftSidebarMenuItem
isClicked={selectedId === 'showcase'}
logoUrl={earthUrl}
onClick={() => handleItemClick('showcase', PATH.SHOWCASE)}>
Showcase
</LeftSidebarMenuItem>
<ToolTip message="가나다라마바사아자차카파타파" position="right" gap={0.8}>
<LeftSidebarMenuItem
isClicked={selectedId === 'showcase'}
logoUrl={earthUrl}
onClick={() => handleItemClick('showcase', PATH.SHOWCASE)}>
Showcase
</LeftSidebarMenuItem>
</ToolTip>

{data?.data.belongTeamGetResponses.map((data: Team) => {
return (
<LeftSidebarMenuItem
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
clubLogoStyle,
leftSidebarMenuItemStyle,
} from '@/shared/component/SideNavBar/LeftSidebarItem/LeftSidebarMenuItem.style';
import PageIndicatorStick from '@/shared/component/SideNavBar/LeftSidebarItem/PageIndicatorStick/PageIndicatorStick';

interface LeftSidebarMenuItemProps {
isClicked: boolean;
Expand All @@ -16,7 +15,7 @@ interface LeftSidebarMenuItemProps {
onClick: () => void;
}

const LeftSidebarMenuItem = ({ isClicked, children = '', logoUrl, onClick }: LeftSidebarMenuItemProps) => {
const LeftSidebarMenuItem = ({ children = '', logoUrl, onClick }: LeftSidebarMenuItemProps) => {
const handleEnterKeyDown = (e: React.KeyboardEvent<HTMLLIElement>) => {
if (e.key === 'Enter') {
onClick();
Expand All @@ -25,7 +24,6 @@ const LeftSidebarMenuItem = ({ isClicked, children = '', logoUrl, onClick }: Lef
return (
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-to-interactive-role
<li role="button" tabIndex={0} css={leftSidebarMenuItemStyle} onClick={onClick} onKeyDown={handleEnterKeyDown}>
<PageIndicatorStick isClicked={isClicked} />
<Flex css={clubInfoStyle}>
<img src={logoUrl} alt={`${children?.toString()} icon`} css={clubLogoStyle} />
</Flex>
Expand Down
47 changes: 47 additions & 0 deletions src/story/common/ToolTip.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import type { Meta, StoryObj } from '@storybook/react';

import Flex from '@/common/component/Flex/Flex';
import ToolTip from '@/common/component/ToolTip/ToolTip';
import { theme } from '@/common/style/theme/theme';

const meta = {
title: 'Common/ToolTip',
component: ToolTip,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
position: {
control: { type: 'radio' },
options: ['top', 'bottom', 'left', 'right'],
},
message: {
control: { type: 'text' },
},
},
args: {
position: 'top',
message: 'ToolTip',
gap: 0.8,
},
} satisfies Meta<typeof ToolTip>;

export default meta;
type Story = StoryObj<typeof meta>;

export const DefaultToolTip: Story = {
args: {
children: (
<Flex
style={{
width: '36px',
height: '36px',
backgroundColor: theme.colors.purple_200,
}}
styles={{ justify: 'center', align: 'center' }}>
호버
</Flex>
),
},
};
Loading