-
Notifications
You must be signed in to change notification settings - Fork 0
feat: 툴팁 컴포넌트 구현 및 기능 개선 #45
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
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| .tooltipWrapper { | ||
| position: relative; | ||
| display: inline-block; | ||
| } | ||
|
|
||
| .tooltip { | ||
| position: absolute; | ||
| z-index: 10; | ||
| padding: 14px 16px; | ||
| border-radius: 10px; | ||
| font-size: 14px; | ||
| font-weight: 400; | ||
| box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); | ||
| white-space: nowrap; | ||
| transition: opacity 0.2s; | ||
| opacity: 0; | ||
| pointer-events: none; | ||
| } | ||
|
|
||
| .tooltipVisible { | ||
| opacity: 1; | ||
| pointer-events: auto; | ||
| } | ||
|
|
||
| .tooltipBlack { | ||
| background: #181f24; | ||
| color: #fff; | ||
| } | ||
|
|
||
| .tooltipWhite { | ||
| background: #fff; | ||
| color: #181f24; | ||
| border: 1px solid #e5e7eb; | ||
| } | ||
|
|
||
| .arrow { | ||
| position: absolute; | ||
| width: 0; | ||
| height: 0; | ||
| } | ||
|
|
||
|
|
||
| .tooltipTop { | ||
| bottom: 100%; | ||
| left: 50%; | ||
| transform: translateX(-50%); | ||
| margin-bottom: 12px; | ||
| } | ||
|
|
||
| .arrowTop { | ||
| top: 100%; | ||
| left: 50%; | ||
| transform: translateX(-50%); | ||
| border-left: 10px solid transparent; | ||
| border-right: 10px solid transparent; | ||
| border-top: 12px solid #181f24; | ||
| } | ||
|
|
||
| .tooltipWhite .arrowTop { | ||
| border-top-color: #fff; | ||
| } | ||
|
|
||
|
|
||
| .tooltipBottom { | ||
| top: 100%; | ||
| left: 50%; | ||
| transform: translateX(-50%); | ||
| margin-top: 12px; | ||
| } | ||
|
|
||
| .arrowBottom { | ||
| bottom: 100%; | ||
| left: 50%; | ||
| transform: translateX(-50%); | ||
| border-left: 10px solid transparent; | ||
| border-right: 10px solid transparent; | ||
| border-bottom: 12px solid #181f24; | ||
| } | ||
|
|
||
| .tooltipWhite .arrowBottom { | ||
| border-bottom-color: #fff; | ||
| } | ||
|
|
||
|
|
||
| .tooltipLeft { | ||
| right: 100%; | ||
| top: 50%; | ||
| transform: translateY(-50%); | ||
| margin-right: 12px; | ||
| } | ||
|
|
||
| .arrowLeft { | ||
| left: 100%; | ||
| top: 50%; | ||
| transform: translateY(-50%); | ||
| border-top: 10px solid transparent; | ||
| border-bottom: 10px solid transparent; | ||
| border-left: 12px solid #181F24; | ||
| margin-left: -1px; | ||
| } | ||
|
|
||
| .tooltipWhite .arrowLeft { | ||
| border-left-color: #fff; | ||
| } | ||
|
|
||
| /* right: 툴팁이 오른쪽에 위치 */ | ||
| .tooltipRight { | ||
| left: 100%; | ||
| top: 50%; | ||
| transform: translateY(-50%); | ||
| margin-left: 12px; | ||
| } | ||
|
|
||
| .arrowRight { | ||
| right: 100%; | ||
| top: 50%; | ||
| transform: translateY(-50%); | ||
| border-top: 10px solid transparent; | ||
| border-bottom: 10px solid transparent; | ||
| border-right: 12px solid #181F24; | ||
| margin-right: -1px; | ||
| } | ||
|
|
||
| .tooltipWhite .arrowRight { | ||
| border-right-color: #fff; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| import React from 'react'; | ||
| import type { Meta, StoryObj } from '@storybook/react'; | ||
| import { Tooltip } from './Tooltip'; | ||
|
|
||
| const meta: Meta<typeof Tooltip> = { | ||
| title: 'libs/Tooltip', | ||
| component: Tooltip, | ||
| tags: ['autodocs'], | ||
| decorators: [ | ||
| (Story) => ( | ||
| <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', padding: '40px', minHeight: '200px' }}> | ||
| <Story /> | ||
| </div> | ||
| ), | ||
| ], | ||
| argTypes: { | ||
| content: { control: 'text', description: '툴팁에 표시될 내용입니다.' }, | ||
| position: { | ||
| control: 'select', | ||
| options: ['top', 'bottom', 'left', 'right'], | ||
| description: '툴팁의 위치를 선택합니다.' | ||
| }, | ||
| color: { | ||
| control: 'radio', | ||
| options: ['black', 'white'], | ||
| description: '툴팁의 색상을 선택합니다.' | ||
| }, | ||
| children: { control: false, description: '툴팁을 트리거할 요소입니다.' }, | ||
| }, | ||
| }; | ||
|
|
||
| export default meta; | ||
|
|
||
| type Story = StoryObj<typeof Tooltip>; | ||
|
|
||
| export const Default: Story = { | ||
| args: { | ||
| content: '가장 기본적인 툴팁입니다.', | ||
| children: <button>기본 버튼</button>, | ||
| position: 'top', | ||
| color: 'black', | ||
| }, | ||
| }; | ||
|
|
||
| export const Positions: Story = { | ||
| // name 속성 제거 | ||
| render: () => ( | ||
| <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gridTemplateRows: '1fr 1fr', gap: '32px 80px', placeItems: 'center' }}> | ||
| <div style={{ gridColumn: '1 / -1' }}> | ||
| <Tooltip content="상단 툴팁입니다." position="top"><button>Top</button></Tooltip> | ||
| </div> | ||
| <Tooltip content="왼쪽 툴팁입니다." position="left"><button>Left</button></Tooltip> | ||
| <div /> | ||
| <Tooltip content="오른쪽 툴팁입니다." position="right"><button>Right</button></Tooltip> | ||
| <div style={{ gridColumn: '1 / -1' }}> | ||
| <Tooltip content="하단 툴팁입니다." position="bottom"><button>Bottom</button></Tooltip> | ||
| </div> | ||
| </div> | ||
| ), | ||
| }; | ||
|
|
||
| export const Colors: Story = { | ||
| // name 속성 제거 | ||
| render: () => ( | ||
| <div style={{ display: 'flex', gap: 32 }}> | ||
| <Tooltip content="Black 타입 툴팁입니다." color="black"><button>Black</button></Tooltip> | ||
| <Tooltip content="White 타입 툴팁입니다." color="white"><button>White</button></Tooltip> | ||
| </div> | ||
| ), | ||
| parameters: { | ||
| backgrounds: { default: 'dark' }, | ||
| }, | ||
| }; | ||
|
|
||
| export const LongContent: Story = { | ||
| // name 속성 제거 | ||
| args: { | ||
| content: '이것은 콘텐츠가 매우 길어질 경우 어떻게 보이는지 테스트하기 위한 툴팁입니다. white-space: nowrap 스타일 때문에 한 줄로 길게 표시됩니다.', | ||
| children: <button>긴 콘텐츠</button>, | ||
| }, | ||
| }; | ||
|
|
||
| export const RichContent: Story = { | ||
| // name 속성 제거 | ||
| args: { | ||
| content: ( | ||
| <div style={{ textAlign: 'center' }}> | ||
| <h4>안녕하세요!</h4> | ||
| <p>툴팁 안에 <b>HTML</b>과 <i>컴포넌트</i>를<br /> 자유롭게 넣을 수 있습니다.</p> | ||
| <a href="https://storybook.js.org/" target="_blank" rel="noopener noreferrer">여기를 클릭하세요</a> | ||
| </div> | ||
| ), | ||
| children: <button>리치 콘텐츠</button>, | ||
| }, | ||
| parameters: { | ||
| notes: '`TooltipProps`의 `content` 타입을 `string`에서 `React.ReactNode`로 변경해야 합니다.', | ||
| }, | ||
| }; | ||
|
|
||
| export const OnDisabledElement: Story = { | ||
| // name 속성 제거 | ||
| render: () => ( | ||
| <Tooltip content="이 버튼은 현재 비활성화 상태입니다."> | ||
| <span style={{ display: 'inline-block', cursor: 'not-allowed' }}> | ||
| <button disabled style={{ pointerEvents: 'none' }}>비활성화 버튼</button> | ||
| </span> | ||
| </Tooltip> | ||
| ), | ||
| }; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,50 @@ | ||||||||||||||||||||
| import React, { useState, useRef } from 'react'; | ||||||||||||||||||||
| import styles from './Tooltip.module.css'; | ||||||||||||||||||||
| import type { TooltipProps } from './Tooltip.type'; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| export const Tooltip: React.FC<TooltipProps> = ({ | ||||||||||||||||||||
| content, | ||||||||||||||||||||
| position = 'top', | ||||||||||||||||||||
| color = 'black', | ||||||||||||||||||||
| children, | ||||||||||||||||||||
| className = '', | ||||||||||||||||||||
| style, | ||||||||||||||||||||
| }) => { | ||||||||||||||||||||
| const [visible, setVisible] = useState(false); | ||||||||||||||||||||
| const wrapperRef = useRef<HTMLDivElement>(null); | ||||||||||||||||||||
|
||||||||||||||||||||
| const wrapperRef = useRef<HTMLDivElement>(null); | |
| // Removed unused wrapperRef to clean up the code. |
Copilot
AI
Jul 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For proper accessibility, add a unique id to this tooltip and set aria-describedby on the trigger element to link them.
| > | |
| {children} | |
| {visible && ( | |
| <div className={tooltipClass} style={style} role="tooltip"> | |
| aria-describedby={visible ? tooltipId : undefined} | |
| > | |
| {children} | |
| {visible && ( | |
| <div id={tooltipId} className={tooltipClass} style={style} role="tooltip"> |
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,11 @@ | ||||||||||||||
| export type TooltipPosition = 'top' | 'bottom' | 'left' | 'right'; | ||||||||||||||
| export type TooltipColor = 'black' | 'white'; | ||||||||||||||
|
|
||||||||||||||
| export interface TooltipProps { | ||||||||||||||
| content: string; | ||||||||||||||
|
Comment on lines
+3
to
+5
|
||||||||||||||
| export interface TooltipProps { | |
| content: string; | |
| import React from 'react'; | |
| export interface TooltipProps { | |
| content: React.ReactNode; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Hex color codes are mixed case; consider normalizing to lowercase (
#181f24) to match the rest of the stylesheet.