Skip to content

Comments

Feat(client): tooltip component 구현#262

Merged
jjangminii merged 7 commits intodevelopfrom
feat/#255/tooltip-component
Feb 22, 2026
Merged

Feat(client): tooltip component 구현#262
jjangminii merged 7 commits intodevelopfrom
feat/#255/tooltip-component

Conversation

@jjangminii
Copy link
Collaborator

@jjangminii jjangminii commented Feb 21, 2026

📌 Related Issues

관련된 Issue를 태그해주세요. (e.g. - close #25)

📄 Tasks

  • 툴팁 컴포넌트 구현 -> 말풍선 느낌이 있어 Balloon이라 이름 지었어요
  • 기존에 있던 tooltip 컴포넌트 이름에 충돌 생길거같아 tooltipCard로 이름 변경하였습니다

⭐ PR Point (To Reviewer)

사용 방법

  • variant: 'gray' | 'main'
  • side: 'top' | 'bottom' | 'left' | 'right'
  • onClose prop 전달 시 닫기 버튼 표시

shadecn Tooltip이 아닌 Balloon을 새로 만든이유

이번 말풍선 UI는 기존 shadcn Tooltip과 목적과 책임이 다르기 때문에 별도의 Balloon 컴포넌트로 분리하여 구현했습니다. shadcn Tooltip은 hover 또는 focus 기반으로 동작하는 짧은 설명용 보조 UI에 적합하며, 접근성 측면에서 보조 정보를 제공하는 역할을 합니다. 하지만 핀백에서 구현한 말풍선은 이벤트나 상태 변화에 따라 노출되는 안내 UI로 이미지, 다중 텍스트, 서로 다른 폰트 크기, 닫기 버튼 등을 포함하는 보다 복합적인 구조를 포함하고있습니다.

Tooltip과 Balloon은 사용 목적과 복잡도가 다르다고 판단하여 별도의 Balloon 컴포넌트를 도입했습니다.

📷 Screenshot

image

@jjangminii jjangminii self-assigned this Feb 21, 2026
@jjangminii jjangminii linked an issue Feb 21, 2026 that may be closed by this pull request
@vercel
Copy link

vercel bot commented Feb 21, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
pinback-client-client Ready Ready Preview, Comment Feb 21, 2026 7:26am
pinback-client-landing Ready Ready Preview, Comment Feb 21, 2026 7:26am

@github-actions github-actions bot added the feat 기능 개발하라 개발 달려라 달려 label Feb 21, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 21, 2026

Warning

Rate limit exceeded

@jjangminii has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 7 minutes and 23 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

Walkthrough

기존 Tooltip 컴포넌트를 TooltipCard로 이름 변경하고, 새로운 Balloon 컴포넌트를 추가하여 UI 컴포넌트 라이브러리를 확장했습니다. JobPins 페이지에서 Balloon 컴포넌트를 활용한 리치 UI로 업데이트했습니다.

Changes

Cohort / File(s) Summary
컴포넌트 이름 변경
apps/client/src/shared/components/tooltipCard/TooltipCard.tsx, apps/client/src/pages/myBookmark/MyBookmark.tsx, apps/client/src/pages/remind/Remind.tsx
Tooltip 컴포넌트를 TooltipCard로 이름 변경하고 관련 페이지의 import/사용부를 업데이트했습니다.
새로운 Balloon 컴포넌트
apps/client/src/shared/components/balloon/Balloon.tsx
variant(gray, main)와 side(위치) 설정을 지원하는 새로운 Balloon 컴포넌트를 추가했습니다. onClose 콜백과 선택적 닫기 버튼을 포함합니다.
JobPins 페이지 UI 업데이트
apps/client/src/pages/jobPins/JobPins.tsx
기존의 단순 텍스트 렌더링을 Balloon 컴포넌트 기반의 복합 UI로 재구성했습니다. 아이콘과 flex 레이아웃을 포함합니다.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

Suggested labels

🛠️ Feature, frontend

Suggested reviewers

  • jllee000
  • constantly-dev

Poem

🐰 풍선이 떴네요, 말이야 나도 몰라요,
툴팁 옷을 갈아입고 카드로 변신했대요,
조브삔스 페이지는 이제 더 예뻐졌고,
공컴 라이브러리도 쑥쑥 자라가니,
행복한 코드의 향연이 펼쳐져요! 🎈

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Balloon 컴포넌트 추가, JobPins 페이지 UI 변경 등은 issue #255의 '공통 툴팁 컴포넌트 제작' 범위를 벗어나는 변경사항으로 보여집니다. PR 설명에서 Balloon 컴포넌트 추가와 JobPins 페이지 UI 변경사항이 tooltip 구현과 어떻게 연관되는지 명확히 설명하거나, 필요시 범위를 재정의하세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 tooltip 컴포넌트 구현이라는 주요 변경사항과 관련되어 있으나, 실제 변경사항에는 Balloon 컴포넌트 추가, TooltipCard 리팩터링 등 더 많은 내용이 포함되어 있습니다.
Linked Issues check ✅ Passed 연결된 issue #255는 '공통 툴팁 컴포넌트 제작'을 요구하며, PR의 Tooltip → TooltipCard 컴포넌트와 Balloon 컴포넌트 추가가 이 목표를 충족합니다.
Description check ✅ Passed PR 설명이 필수 섹션을 완벽하게 포함하고 있으며, 작업 내용, 리뷰 포인트, 스크린샷, 그리고 설계 결정에 대한 상세한 설명이 제공되었습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#255/tooltip-component

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

github-actions bot commented Feb 21, 2026

✅ Storybook chromatic 배포 확인:
🐿️ storybook

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/client/src/shared/components/tooltipCard/TooltipCard.tsx (1)

14-25: ⚠️ Potential issue | 🟠 Major

하드코딩된 id="info-card" — 다중 인스턴스 시 중복 ID 위험

id="info-card"가 정적으로 고정되어 있어, 같은 페이지에 TooltipCard가 두 개 이상 렌더링될 경우 HTML 표준 위반(중복 ID)이 발생하고 aria-describedby가 의도치 않은 요소를 참조하게 됩니다. 또한 role="tooltip"이 없어 보조 기술이 해당 요소를 툴팁으로 인식하지 못합니다.

React의 useId 훅을 활용해 인스턴스별 고유 ID를 생성하고, role="tooltip"을 추가하는 것을 권장합니다.

🔧 수정 제안
+import { useId } from 'react';
 import { Icon } from '@pinback/design-system/icons';
 import InfoCard from './InfoCard';

 export default function TooltipCard() {
+  const tooltipId = useId();
   return (
     <div className="mt-[0.8rem] flex items-center">
       <p className="body3-r text-font-gray-3">
         일부 콘텐츠는 제목·이미지가 불러와지지 않을 수 있어요.
       </p>

       <div className="relative inline-flex items-center">
         <button
           type="button"
-          aria-describedby="info-card"
+          aria-describedby={tooltipId}
           className="peer rounded p-[0.4rem]"
         >
           <Icon name="ic_info" size={16} />
         </button>

         <div
-          id="info-card"
+          id={tooltipId}
+          role="tooltip"
           className="pointer-events-none absolute left-0 top-[calc(100%+8px)] z-[100] opacity-0 transition-opacity duration-150 peer-hover:opacity-100 peer-focus-visible:opacity-100"
         >
           <InfoCard />
         </div>
       </div>
     </div>
   );
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/client/src/shared/components/tooltipCard/TooltipCard.tsx` around lines
14 - 25, TooltipCard currently uses a hardcoded id ("info-card") for the tooltip
which causes duplicate IDs and broken aria-describedby links when multiple
TooltipCard instances render; update the TooltipCard component to call React's
useId (or generate a stable unique id) and use that id for both the button's
aria-describedby and the tooltip container's id, and also add role="tooltip" to
the tooltip container (the element rendering InfoCard) so assistive tech
recognizes it; locate the id usage in TooltipCard and the tooltip div that
renders <InfoCard /> and replace the static id with the generated unique id and
add the role attribute.
🧹 Nitpick comments (1)
apps/client/src/shared/components/balloon/Balloon.tsx (1)

19-22: variantStyle 객체를 컴포넌트 외부로 이동

variantStyle은 props에 의존하지 않는 순수 상수이므로 컴포넌트 함수 내부에 정의할 필요가 없습니다. 현재 구조에서는 매 렌더마다 객체가 재생성됩니다. 컴포넌트 외부로 이동하면 불필요한 재생성을 방지할 수 있습니다.

♻️ 수정 제안
+const variantStyle: Record<BalloonVariant, string> = {
+  gray: 'bg-gray900 text-white',
+  main: 'bg-main500 text-white',
+};
+
 export function Balloon({
   variant = 'gray',
   side = 'bottom',
   onClose,
   children,
 }: BalloonProps) {
-  const variantStyle = {
-    gray: 'bg-gray900 text-white',
-    main: 'bg-main500 text-white',
-  };
-
   return (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/client/src/shared/components/balloon/Balloon.tsx` around lines 19 - 22,
Move the variantStyle constant out of the Balloon component so it isn't
re-created on every render: locate the variantStyle object (currently defined
inside the Balloon component in Balloon.tsx) and hoist it to module scope above
the Balloon function, keeping the same key names ('gray', 'main') and values;
ensure the Balloon component continues to reference variantStyle unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/client/src/pages/jobPins/JobPins.tsx`:
- Line 13: The temporary alert handler on the Balloon component (onClose={() =>
alert('닫힘')}) must be removed or replaced: locate the Balloon usage in
JobPins.tsx and either remove the onClose prop or replace it with a non-blocking
stub that clearly marks intent (e.g., a TODO handler that calls a close function
or dispatches a close action) and add a TODO comment referencing Balloon.onClose
to implement real close logic later; ensure no alert() calls remain in
production code.
- Line 7: In the JobPins component (JobPins.tsx) remove the unnecessary JSX
whitespace token {' '} that appears immediately after the <div> element (an
artifact of editing) so the <div> children render without the extraneous blank
node; locate the stray {' '} near the top of the component and delete it leaving
the surrounding JSX intact.
- Around line 5-35: The JobPins page currently contains only hardcoded Balloon
demo markup; replace this with real data fetching and state management: add
state (e.g., const [pins, setPins] = useState<JobPin[]>() ) and a data loader
(useEffect or React Query/SWR) that calls your job pins API (e.g., fetchJobPins
or getSavedJobPins) to populate pins, render the list by mapping pins to
<Balloon ...> elements instead of the static examples, show loading/error UI
while fetching, implement onClose handlers that call an unpin API (e.g.,
removeJobPin or unpinJob) and update state optimistically (update setPins to
remove the pin on success or rollback on failure), and ensure prop typings for
the JobPins component and children so Balloon receives the correct title,
subtitle, icon, and id values from each pin.

In `@apps/client/src/shared/components/balloon/Balloon.tsx`:
- Around line 34-38: 조건부로 렌더되는 닫기 버튼 (onClose 핸들러와 함께 렌더되는 <button> 바로 그 요소)에
accessibility와 form 동작 문제를 고치기 위해 버튼에 명시적 type="button" 속성을 추가하고 스크린 리더용
aria-label을 추가하세요 (예: aria-label="닫기" 또는 컴포넌트에 전달되는 레이블 prop 사용). 변경 대상 식별자:
onClose 조건부 버튼 렌더링과 Icon name="ic_close" 사용 부분(Balloon.tsx의 해당 버튼)을 찾아 해당 요소에
type="button"과 적절한 aria-label을 설정하면 됩니다.
- Around line 1-11: The file uses the React namespace type in BalloonProps
(children: React.ReactNode) but does not import React; add an explicit import
for React (e.g., import React from 'react') at the top of Balloon.tsx so the
React symbol is available for the BalloonProps type and other references such as
BalloonVariant and the Balloon component that use React types.

---

Outside diff comments:
In `@apps/client/src/shared/components/tooltipCard/TooltipCard.tsx`:
- Around line 14-25: TooltipCard currently uses a hardcoded id ("info-card") for
the tooltip which causes duplicate IDs and broken aria-describedby links when
multiple TooltipCard instances render; update the TooltipCard component to call
React's useId (or generate a stable unique id) and use that id for both the
button's aria-describedby and the tooltip container's id, and also add
role="tooltip" to the tooltip container (the element rendering InfoCard) so
assistive tech recognizes it; locate the id usage in TooltipCard and the tooltip
div that renders <InfoCard /> and replace the static id with the generated
unique id and add the role attribute.

---

Nitpick comments:
In `@apps/client/src/shared/components/balloon/Balloon.tsx`:
- Around line 19-22: Move the variantStyle constant out of the Balloon component
so it isn't re-created on every render: locate the variantStyle object
(currently defined inside the Balloon component in Balloon.tsx) and hoist it to
module scope above the Balloon function, keeping the same key names ('gray',
'main') and values; ensure the Balloon component continues to reference
variantStyle unchanged.

Comment on lines +5 to +35
return (
<div>
{' '}
<Balloon variant="main" side="bottom">
<div className="text-lg font-semibold">치삐가 방금</div>

<div className="text-sm opacity-90">도토리 1개를 모았어요!</div>
</Balloon>
<Balloon variant="gray" side="left" onClose={() => alert('닫힘')}>
<div className="text-lg font-semibold">치삐가 방금</div>

<div className="text-sm opacity-90">도토리 1개를 모았어요!</div>
</Balloon>
<Balloon variant="gray" side="left">
<Icon name="ic_info" size={16} />
<div className="text-sm opacity-90">도토리 1개를 모았어요!</div>
</Balloon>
<Balloon variant="main" side="bottom">
<div className="flex items-center gap-3">
{/* 캐릭터 이미지 */}
<Icon name="chippi_profile" size={40} />

{/* 텍스트 영역 */}
<div className="flex flex-col">
<div className="text-[18px] font-semibold">치삐가 방금</div>
<div className="text-[16px]">도토리 1개를 모았어요!</div>
</div>
</div>
</Balloon>
</div>
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check JobPins router registration and actual usage
echo "=== JobPins 라우터 등록 확인 ==="
rg -n "JobPins" --type=tsx --type=ts -g '!node_modules' -C 3

echo "=== 관심 직무 핀 관련 API 연결 여부 ==="
rg -n "jobPin" --type=tsx --type=ts -g '!node_modules' -C 2

Repository: Pinback-Team/pinback-client

Length of output: 198


🏁 Script executed:

#!/bin/bash
# Corrected search - JobPins references in TypeScript/TSX files
echo "=== JobPins 라우터 등록 확인 ==="
rg "JobPins" -g "*.ts" -g "*.tsx" -C 3

echo "=== jobPin 또는 관심 직무 핀 관련 API 연결 여부 ==="
rg "jobPin|job-pin|pin.*job" -g "*.ts" -g "*.tsx" -C 2

echo "=== JobPins 파일 자체 확인 ==="
cat -n apps/client/src/pages/jobPins/JobPins.tsx | head -50

Repository: Pinback-Team/pinback-client

Length of output: 7832


JobPins 페이지가 Balloon 컴포넌트 데모 코드로만 구성되어 있고 실제 기능이 없음

현재 파일은 라우터에 등록되어 있고 사이드바에서 "관심 직무 핀" 메뉴로 접근 가능하지만, 콘텐츠는 Balloon 컴포넌트 쇼케이스의 하드코딩된 예시로만 구성되어 있습니다. "치삐가 방금", "도토리 1개를 모았어요!" 등의 텍스트는 실제 데이터가 아니며, API 연결, 데이터 페칭, 상태 관리가 모두 없는 상태입니다. 실제 관심 직무 핀 기능을 구현하거나, 이 페이지의 목적을 명확히 해야 합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/client/src/pages/jobPins/JobPins.tsx` around lines 5 - 35, The JobPins
page currently contains only hardcoded Balloon demo markup; replace this with
real data fetching and state management: add state (e.g., const [pins, setPins]
= useState<JobPin[]>() ) and a data loader (useEffect or React Query/SWR) that
calls your job pins API (e.g., fetchJobPins or getSavedJobPins) to populate
pins, render the list by mapping pins to <Balloon ...> elements instead of the
static examples, show loading/error UI while fetching, implement onClose
handlers that call an unpin API (e.g., removeJobPin or unpinJob) and update
state optimistically (update setPins to remove the pin on success or rollback on
failure), and ensure prop typings for the JobPins component and children so
Balloon receives the correct title, subtitle, icon, and id values from each pin.

return <div>관심 직무 핀 페이지</div>;
return (
<div>
{' '}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

불필요한 {' '} 제거

<div> 직후의 {' '}는 렌더링에 아무런 의미 없는 공백 문자이며, 코드 편집 중 발생한 아티팩트로 보입니다. 제거해 주세요.

🧹 수정 제안
   return (
     <div>
-      {' '}
       <Balloon variant="main" side="bottom">
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{' '}
return (
<div>
<Balloon variant="main" side="bottom">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/client/src/pages/jobPins/JobPins.tsx` at line 7, In the JobPins
component (JobPins.tsx) remove the unnecessary JSX whitespace token {' '} that
appears immediately after the <div> element (an artifact of editing) so the
<div> children render without the extraneous blank node; locate the stray {' '}
near the top of the component and delete it leaving the surrounding JSX intact.


<div className="text-sm opacity-90">도토리 1개를 모았어요!</div>
</Balloon>
<Balloon variant="gray" side="left" onClose={() => alert('닫힘')}>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

alert() — 프로덕션 코드에 포함하면 안 되는 임시 핸들러

onClose={() => alert('닫힘')}은 명백히 개발 중 사용하는 임시 코드입니다. alert()은 브라우저 UI를 블로킹하며 프로덕션 환경에 적합하지 않습니다. 실제 닫기 로직이 구현될 때까지 핸들러를 제거하거나 TODO 주석으로 명시해 주세요.

🔧 수정 제안
-      <Balloon variant="gray" side="left" onClose={() => alert('닫힘')}>
+      {/* TODO: 닫기 상태 관리 로직 구현 후 onClose 연결 필요 */}
+      <Balloon variant="gray" side="left">
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Balloon variant="gray" side="left" onClose={() => alert('닫힘')}>
{/* TODO: 닫기 상태 관리 로직 구현 후 onClose 연결 필요 */}
<Balloon variant="gray" side="left">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/client/src/pages/jobPins/JobPins.tsx` at line 13, The temporary alert
handler on the Balloon component (onClose={() => alert('닫힘')}) must be removed
or replaced: locate the Balloon usage in JobPins.tsx and either remove the
onClose prop or replace it with a non-blocking stub that clearly marks intent
(e.g., a TODO handler that calls a close function or dispatches a close action)
and add a TODO comment referencing Balloon.onClose to implement real close logic
later; ensure no alert() calls remain in production code.

Comment on lines 34 to 38
{onClose && (
<button onClick={onClose}>
<Icon name="ic_close" size={16} />
</button>
)}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

닫기 버튼에 aria-labeltype="button" 속성 누락

onClose 조건부 버튼에 두 가지 문제가 있습니다.

  1. aria-label이 없어 스크린 리더 사용자가 버튼의 목적을 인식할 수 없습니다.
  2. type="button"이 없어, 이 컴포넌트가 <form> 내부에서 사용될 경우 기본값인 type="submit"으로 동작하여 의도치 않은 폼 제출이 발생할 수 있습니다.
♿ 수정 제안
-        <button onClick={onClose}>
+        <button type="button" aria-label="닫기" onClick={onClose}>
           <Icon name="ic_close" size={16} />
         </button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{onClose && (
<button onClick={onClose}>
<Icon name="ic_close" size={16} />
</button>
)}
{onClose && (
<button type="button" aria-label="닫기" onClick={onClose}>
<Icon name="ic_close" size={16} />
</button>
)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/client/src/shared/components/balloon/Balloon.tsx` around lines 34 - 38,
조건부로 렌더되는 닫기 버튼 (onClose 핸들러와 함께 렌더되는 <button> 바로 그 요소)에 accessibility와 form 동작
문제를 고치기 위해 버튼에 명시적 type="button" 속성을 추가하고 스크린 리더용 aria-label을 추가하세요 (예:
aria-label="닫기" 또는 컴포넌트에 전달되는 레이블 prop 사용). 변경 대상 식별자: onClose 조건부 버튼 렌더링과 Icon
name="ic_close" 사용 부분(Balloon.tsx의 해당 버튼)을 찾아 해당 요소에 type="button"과 적절한
aria-label을 설정하면 됩니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat 기능 개발하라 개발 달려라 달려

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] 툴팁 컴포넌트 구현

1 participant