Skip to content

✨Feat: bottom tab bar 구현 및 페이지별 라우팅 간단 적용#39

Merged
skyblue1232 merged 12 commits intodevelopfrom
feat/#29/bottom-tab-bar
Oct 25, 2025
Merged

✨Feat: bottom tab bar 구현 및 페이지별 라우팅 간단 적용#39
skyblue1232 merged 12 commits intodevelopfrom
feat/#29/bottom-tab-bar

Conversation

@skyblue1232
Copy link
Copy Markdown
Contributor

@skyblue1232 skyblue1232 commented Oct 17, 2025

🔥 작업 내용

  • ex) 로그인 페이지 UI 및 반응형 구현
  • bottom tab bar 공통 컴포넌트 생성
  • 페이지별 라우팅 간단 적용 -> 영상 참고
  • cn, cva 활용

🤔 추후 작업 사항

  • ex) 소셜 로그인 연동 (카카오, 구글)
  • 탭 별로 페이지 이동을 보여주는 성격이 강해서 path는 변경 가능성 있음.
  • 버튼 공통 컴포넌트 생성

🔗 이슈

PR Point (To Reviewer)

  • ex) 로그인 입력 검증 로직 적절한지 확인 부탁드립니다.
  • 바텀 탭 바에서 페이지 별로 이동하는 것을 설정해서 유동적으로 변화 감지되도록 구현

참고 사항

  • 아래 보이는 것과 같이 href로 폴더 이름에 맞춰서 설정해놨음.
  • 페이지별로 유동적으로 변화하는 걸 보이기 위해 간단히 페이지 제목만 넣어서 구현함.
  • 이렇게 하면 영상처럼 path에 맞춰서 이동.
image image

📸 피그마 스크린샷 or 기능 GIF

(작업 내역 스크린샷)

2025-10-18.043343.mp4

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능
    • 앱 하단에 고정된 네비게이션 바 추가
    • 아이콘과 레이블이 포함된 네비게이션 항목 제공
    • 현재 활성 항목 시각적 강조 표시 기능

@vercel
Copy link
Copy Markdown

vercel bot commented Oct 17, 2025

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

Project Deployment Preview Comments Updated (UTC)
af-fe Ready Ready Preview Comment Oct 25, 2025 5:50pm

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Oct 17, 2025

Walkthrough

BottomNavTabItem 두 개의 새로운 React 컴포넌트가 추가됩니다. TabItem은 아이콘과 라벨을 가진 스타일된 탭 항목을 렌더링하고, BottomNav는 이를 사용하여 고정된 하단 네비게이션 바를 구성합니다. Next.js의 Link와 useRouter를 활용하여 라우팅과 활성 항목 상태 관리를 수행합니다.

Changes

코호트 / 파일 변경 요약
새로운 탭 컴포넌트 모음
src/shared/components/tab/TabItem.tsx
src/shared/components/tab/BottomNav.tsx
TabItem 컴포넌트 추가 (cva 기반 스타일링, Icon 및 라벨 렌더링, isActive 상태 관리) 및 BottomNav 컴포넌트 추가 (NAV_ITEMS 배열 정의, 고정 위치 하단 네비게이션 바 구현, 라우터 기반 활성 항목 표시)

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

  • TabItem 컴포넌트의 스타일 변형(variant) 및 아이콘 색상 로직 확인
  • BottomNav의 라우트 매칭 로직(pathname과 href 비교) 검증
  • NAV_ITEMS 배열의 아이콘 이름 및 경로 정확성 확인

Suggested reviewers

  • jjangminii
  • KongMezu

Poem

🐰 하단 탭바, 우리 함께 여행해요
아이콘과 라벨, 예쁜 스타일로
한 번의 클릭이 새로운 길을 열어요
민트색 반짝임, 활성화의 춤
네비게이션 완성, 즐거운 마음! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title Check ✅ Passed PR 제목 "✨Feat: bottom tab bar 구현 및 페이지별 라우팅 간단 적용"은 변경사항의 핵심을 명확하게 요약하고 있습니다. raw_summary에 따르면 BottomNav.tsx와 TabItem.tsx 두 개의 새로운 React 컴포넌트가 추가되었으며, 이는 정확히 제목에서 설명하는 내용과 일치합니다. 제목은 간결하고 구체적이며, 프로젝트 팀원이 히스토리를 스캔할 때 주요 변경사항을 즉시 이해할 수 있습니다.
Linked Issues Check ✅ Passed 이슈 #29의 주요 요구사항인 공통 컴포넌트 bottom tab-bar 생성이 BottomNav.tsx와 TabItem.tsx 컴포넌트로 구현되었습니다. raw_summary에 따르면 TabItem.tsx에서 cva를 사용한 active variant가 정의되어 있고 텍스트 색상이 isActive 상태에 따라 제어되도록 구현되었으며, BottomNav.tsx에서 Next.js Link와 useRouter를 활용한 동적 라우팅이 구현되어 페이지별로 active 상태가 변경됩니다. PR 설명에서 페이지별 유동적 변화를 보여주도록 구현했음을 명시했습니다.
Out of Scope Changes Check ✅ Passed PR에서 추가된 모든 변경사항이 명시된 목표 범위 내에 있습니다. BottomNav.tsx와 TabItem.tsx는 bottom tab bar 공통 컴포넌트 생성이라는 핵심 목표를 직접 구현하고 있으며, Next.js Link/useRouter를 활용한 라우팅 구현과 cva 스타일링 활용도 PR 설명에서 명시한 범위 내의 변경사항입니다. raw_summary에서 제공된 정보로는 범위를 벗어난 불필요한 변경이나 추가적인 기능이 없습니다.
Description Check ✅ Passed PR 설명이 템플릿의 모든 필수 섹션을 포함하고 있습니다. 작업 내용에서 bottom tab bar 공통 컴포넌트 생성, 페이지별 라우팅 적용, cn/cva 활용을 명시했고, 추후 작업 사항에서 소셜 로그인 연동 및 버튼 공통 컴포넌트 생성 계획을 기술했으며, 이슈 #29 연결, PR 검토 포인트 명확화, 참고 사항과 GIF/스크린샷을 포함했습니다. 추가로 구현 방식에 대한 상세한 설명과 시각 자료가 제공되어 검토자의 이해를 돕습니다.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#29/bottom-tab-bar

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c0e8123 and cb5ade5.

📒 Files selected for processing (1)
  • src/shared/components/tab/BottomNav.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/shared/components/tab/BottomNav.tsx (2)
src/shared/icons/iconNames.ts (1)
  • IconName (22-22)
src/shared/components/tab/TabItem.tsx (1)
  • TabItem (28-58)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-and-deploy
🔇 Additional comments (3)
src/shared/components/tab/BottomNav.tsx (3)

1-4: 임포트 구조가 적절합니다.

절대 경로를 사용하고 필요한 의존성이 모두 포함되어 있습니다.


34-49: 접근성 개선사항이 잘 적용되었습니다.

이전 리뷰 피드백에 따라 aria-labelaria-current 속성이 추가되었고, 하위 경로 매칭을 위한 startsWith 로직도 구현되었습니다. 전반적인 컴포넌트 구조와 Next.js Link 통합이 적절합니다.


37-39: 경로 매칭 로직의 잠재적 문제 검증 완료 - 실제 충돌 없음

스크립트 실행 결과, 프로젝트의 실제 라우트 구조를 확인했습니다. 현재 존재하는 라우트는 /, /auth, /main뿐이며, NAV_ITEMS의 /main, /map, /events, /chatbot, /mypage 중에서도 오직 /main만 실제 라우트로 구현되어 있습니다.

startsWith 기반 매칭이 잠재적으로 문제가 될 수 있다는 우려는 이론적으로 타당하지만, 이 프로젝트에는 /maintenance/main-dashboard 같은 충돌 가능한 라우트가 존재하지 않으므로 현재 코드는 올바르게 작동합니다. /main 라우트는 /main/ 하위 경로와만 매칭되며, 다른 경로와의 충돌은 발생하지 않습니다.


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 github-actions bot added the comment 필요한 주석 추가 및 변경 label Oct 17, 2025
@github-actions
Copy link
Copy Markdown

🏷️ Labeler has automatically applied labels based on your PR title, branch name, or commit message.
Please verify that they are correct before merging.

Copy link
Copy Markdown
Contributor

@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: 6

🧹 Nitpick comments (3)
src/pages/chatbot/index.tsx (1)

1-7: 페이지 컴포넌트 간 코드 중복이 있습니다.

ChatBotPage, EventsPage, HomePage, MyPage, RoutePage가 모두 동일한 구조를 가지고 있습니다. 공통 레이아웃 컴포넌트로 추출하면 유지보수성이 향상됩니다.

예시: 공통 레이아웃 컴포넌트

// src/shared/components/layout/PlaceholderPage.tsx
export default function PlaceholderPage({ title }: { title: string }) {
  return (
    <div className="flex items-center justify-center min-h-screen text-mint-700 text-2xl font-semibold">
      {title}
    </div>
  );
}

각 페이지에서 사용:

import PlaceholderPage from '@/shared/components/layout/PlaceholderPage';

export default function ChatBotPage() {
  return <PlaceholderPage title="ChatBot Page" />;
}
src/shared/components/tab/BottomNav.tsx (2)

36-48: 접근성 개선을 고려해보세요.

탭 네비게이션의 접근성을 높이기 위해 aria-labelaria-current 속성을 추가하는 것을 권장합니다.

     <nav
       className="..."
+      aria-label="메인 네비게이션"
     >
       {NAV_ITEMS.map((item) => {
         const isActive = router.pathname === item.href;

         return (
           <Link
             key={item.id}
             href={item.href}
             className="flex-1 flex justify-center"
+            aria-current={isActive ? 'page' : undefined}
+            aria-label={item.label}
           >
             <TabItem label={item.label} icon={item.icon} isActive={isActive} />
           </Link>
         );
       })}
     </nav>

36-37: 중첩 라우트 지원을 고려해보세요.

현재 활성 상태 감지는 정확한 경로 일치(router.pathname === item.href)만 처리합니다. 향후 중첩 라우트가 추가되면 상위 탭이 활성화되지 않을 수 있습니다.

예: /mypage/settings 페이지에서 MyPage 탭이 활성화되지 않음

필요시 다음과 같이 수정할 수 있습니다:

-        const isActive = router.pathname === item.href;
+        const isActive = item.href === '/' 
+          ? router.pathname === item.href
+          : router.pathname.startsWith(item.href);

현재 PR에서는 중첩 라우트가 없으므로 즉시 변경할 필요는 없습니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fd6cef7 and ae23a98.

📒 Files selected for processing (9)
  • src/pages/_app.tsx (1 hunks)
  • src/pages/chatbot/index.tsx (1 hunks)
  • src/pages/events/index.tsx (1 hunks)
  • src/pages/home/index.tsx (1 hunks)
  • src/pages/mypage/index.tsx (1 hunks)
  • src/pages/route/index.tsx (1 hunks)
  • src/shared/components/tab/BottomNav.tsx (1 hunks)
  • src/shared/components/tab/TabItem.tsx (1 hunks)
  • src/shared/lib/utils.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/shared/components/tab/TabItem.tsx (2)
src/shared/icons/iconNames.ts (1)
  • IconName (19-19)
src/shared/lib/utils.ts (1)
  • cn (71-73)
src/shared/components/tab/BottomNav.tsx (2)
src/shared/icons/iconNames.ts (1)
  • IconName (19-19)
src/shared/components/tab/TabItem.tsx (1)
  • TabItem (28-58)
src/pages/_app.tsx (1)
src/shared/components/tab/BottomNav.tsx (1)
  • BottomNav (21-51)
🔇 Additional comments (5)
src/pages/events/index.tsx (1)

1-7: 코드 구조 확인 완료.

이 페이지는 ChatBotPage와 동일한 패턴을 따르고 있습니다. 코드 중복에 대한 리팩토링 제안은 ChatBotPage 리뷰 코멘트를 참고하세요.

src/pages/mypage/index.tsx (1)

1-7: 코드 구조 확인 완료.

ChatBotPage와 동일한 패턴입니다. 중복 코드 리팩토링 제안은 ChatBotPage 리뷰를 참고하세요.

src/pages/home/index.tsx (1)

1-7: 코드 구조 확인 완료.

동일한 패턴입니다. 중복 코드 관련 제안은 ChatBotPage 리뷰를 참고하세요.

src/pages/route/index.tsx (1)

1-7: 코드 구조 확인 완료.

동일한 패턴입니다. 중복 코드 관련 제안은 ChatBotPage 리뷰를 참고하세요.

src/pages/_app.tsx (1)

7-12: 현재 코드베이스에서는 BottomNav 조건부 렌더링이 필요하지 않습니다.

검증 결과, 다음을 확인했습니다:

  • 현재 페이지 구조: 모든 페이지는 콘텐츠 페이지(home, chatbot, events, mypage, route)이며, 이들은 BottomNav를 표시해야 합니다.
  • 로그인/인증 페이지: 존재하지 않습니다.
  • 에러 페이지: 존재하지 않습니다.
  • auth 관련 파일: 찾을 수 없습니다.

리뷰 코멘트는 현재 존재하지 않는 페이지(로그인, 에러 페이지)를 기준으로 작성되었습니다. 현재 코드베이스 상태에서 모든 기존 페이지는 BottomNav를 표시하는 것이 올바른 구조입니다. 향후 로그인/인증/에러 페이지가 추가될 때 필요에 따라 조건부 렌더링을 고려하면 됩니다.

Likely an incorrect or invalid review comment.

Comment on lines +13 to +19
const NAV_ITEMS: NavItem[] = [
{ id: 'home', label: 'Home', icon: 'HouseSimple', href: '/' },
{ id: 'route', label: 'Route', icon: 'MapPin_', href: '/route' },
{ id: 'events', label: 'Events', icon: 'CalendarBlank', href: '/events' },
{ id: 'chatbot', label: 'ChatBot', icon: 'ChatCircle', href: '/chatbot' },
{ id: 'mypage', label: 'MyPage', icon: 'User', href: '/mypage' },
];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Home 라우트 경로 불일치가 있습니다.

Line 14에서 Home의 href'/'로 설정되어 있지만, src/pages/home/index.tsx 파일은 Next.js에서 /home 경로로 매핑됩니다. /src/pages/index.tsx에 해당합니다.

현재 프로젝트에 src/pages/index.tsx가 없다면 Home 탭을 클릭했을 때 404 에러가 발생하거나 의도하지 않은 페이지로 이동할 수 있습니다.

다음 중 하나를 선택하여 수정하세요:

옵션 1: Home href를 /home으로 변경

-  { id: 'home', label: 'Home', icon: 'HouseSimple', href: '/' },
+  { id: 'home', label: 'Home', icon: 'HouseSimple', href: '/home' },

옵션 2: src/pages/home/index.tsx를 src/pages/index.tsx로 이동

다음 스크립트로 index.tsx 파일 존재 여부를 확인하세요:

#!/bin/bash
# Description: Check if root index.tsx exists and verify home page routing

# Check for root index page
fd -t f "^index\.tsx$" src/pages -d 1

# Check home directory
fd -t f "index\.tsx" src/pages/home
🤖 Prompt for AI Agents
In src/shared/components/tab/BottomNav.tsx around lines 13 to 19, the Home nav
item's href is set to '/' but your Next.js page for Home lives at
src/pages/home/index.tsx (so '/' will 404 or go to a different page); fix by
either changing the Home item's href to '/home' OR moving/renaming
src/pages/home/index.tsx to src/pages/index.tsx so Home maps to '/', then update
imports/routes accordingly and verify by running the provided fd check or your
dev server.

Comment on lines +25 to +34
<nav
className="
fixed bottom-[2rem] left-1/2 -translate-x-1/2
w-[calc(100%-48px)] max-w-[42.5rem]
flex justify-between items-center
px-[1.3rem]
py-[1rem]
rounded-[37.07px]
bg-mint-50 border border-mint-300
"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

고정 네비게이션에 z-index가 누락되었습니다.

fixed 포지셔닝을 사용하는 네비게이션 바에 z-index가 명시되지 않아 페이지 콘텐츠가 네비게이션 바를 가릴 수 있습니다.

다음과 같이 z-index를 추가하세요:

     <nav
       className="
         fixed bottom-[2rem] left-1/2 -translate-x-1/2
         w-[calc(100%-48px)] max-w-[42.5rem]
         flex justify-between items-center
         px-[1.3rem]
         py-[1rem]
         rounded-[37.07px]
         bg-mint-50 border border-mint-300
+        z-50
       "
     >
📝 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
<nav
className="
fixed bottom-[2rem] left-1/2 -translate-x-1/2
w-[calc(100%-48px)] max-w-[42.5rem]
flex justify-between items-center
px-[1.3rem]
py-[1rem]
rounded-[37.07px]
bg-mint-50 border border-mint-300
"
<nav
className="
fixed bottom-[2rem] left-1/2 -translate-x-1/2
w-[calc(100%-48px)] max-w-[42.5rem]
flex justify-between items-center
px-[1.3rem]
py-[1rem]
rounded-[37.07px]
bg-mint-50 border border-mint-300
z-50
"
>
🤖 Prompt for AI Agents
In src/shared/components/tab/BottomNav.tsx around lines 25 to 34, the fixed
bottom navigation lacks a z-index so page content can overlap it; add an
explicit stacking context (for example the Tailwind utility class z-50 or
another project-standard z value) to the nav element’s class list so the nav
always sits above page content while keeping existing layout and styles intact.

Comment on lines +21 to +26
interface TabItemProps extends VariantProps<typeof tabItemStyle> {
label: string;
icon: IconName;
isActive?: boolean;
onClick?: () => void;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

props 이름 불일치 문제를 수정하세요.

VariantProps<typeof tabItemStyle>을 확장하면 이미 active prop이 포함되지만, 컴포넌트에서는 isActive를 별도 prop으로 선언했습니다. 이로 인해 두 prop이 모두 존재하게 되어 혼란을 초래할 수 있습니다.

다음 중 하나를 선택하여 수정하세요:

방법 1 (권장): cva 변형 prop을 직접 사용

-interface TabItemProps extends VariantProps<typeof tabItemStyle> {
+interface TabItemProps 
+  extends Omit<VariantProps<typeof tabItemStyle>, 'active'> {
   label: string;
   icon: IconName;
   isActive?: boolean;
   onClick?: () => void;
 }

방법 2: cva 변형 이름을 일치시키기

tabItemStyle의 변형을 active에서 isActive로 변경:

 const tabItemStyle = cva(
   '...',
   {
     variants: {
-      active: {
+      isActive: {
         true: 'text-mint-700',
         false: 'text-mint-500',
       },
     },
     defaultVariants: {
-      active: false,
+      isActive: false,
     },
   },
 );

그런 다음 컴포넌트에서:

-    className={cn(tabItemStyle({ active: isActive }))}
+    className={cn(tabItemStyle({ isActive }))}
📝 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
interface TabItemProps extends VariantProps<typeof tabItemStyle> {
label: string;
icon: IconName;
isActive?: boolean;
onClick?: () => void;
}
interface TabItemProps
extends Omit<VariantProps<typeof tabItemStyle>, 'active'> {
label: string;
icon: IconName;
isActive?: boolean;
onClick?: () => void;
}
🤖 Prompt for AI Agents
In src/shared/components/tab/TabItem.tsx around lines 21 to 26, the interface
currently extends VariantProps<typeof tabItemStyle> (which already defines an
active prop) but also declares a separate isActive prop, causing
duplicate/conflicting props; fix by removing isActive from the interface and
using the cva variant prop name (active) throughout the component and its
callers (or alternatively rename the cva variant to isActive if you prefer that
naming), ensure the component passes the active boolean into tabItemStyle
invocation and update any parent usages to supply active instead of isActive so
only one prop controls the active state.

Comment on lines +35 to +57
<div
className={cn(tabItemStyle({ active: isActive }))}
onClick={onClick}
>
<Icon
name={icon}
size={24}
color={isActive ? 'mint-700' : 'mint-500'}
className={cn(
'transition-transform duration-200',
isActive && 'scale-110',
)}
/>
<span
className={cn(
'text-label-md font-medium',
isActive ? 'text-mint-700' : 'text-mint-500',
)}
>
{label}
</span>
</div>
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

접근성 문제: 버튼 요소를 사용하거나 적절한 키보드 지원을 추가하세요.

클릭 가능한 div 요소를 사용하면 키보드 탐색이 불가능하고 스크린 리더 사용자가 탭 버튼임을 인식할 수 없습니다. 이는 접근성 기준(WCAG)을 위반합니다.

button 요소를 사용하도록 수정하세요:

   return (
-    <div
-      className={cn(tabItemStyle({ active: isActive }))} 
+    <button
+      type="button"
+      className={cn(tabItemStyle({ active: isActive }))}
       onClick={onClick}
+      aria-label={label}
+      aria-current={isActive ? 'page' : undefined}
     >
       <Icon
         name={icon}
         size={24}
         color={isActive ? 'mint-700' : 'mint-500'}
         className={cn(
           'transition-transform duration-200',
           isActive && 'scale-110',
         )}
       />
       <span
         className={cn(
           'text-label-md font-medium',
           isActive ? 'text-mint-700' : 'text-mint-500',
         )}
       >
         {label}
       </span>
-    </div>
+    </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
<div
className={cn(tabItemStyle({ active: isActive }))}
onClick={onClick}
>
<Icon
name={icon}
size={24}
color={isActive ? 'mint-700' : 'mint-500'}
className={cn(
'transition-transform duration-200',
isActive && 'scale-110',
)}
/>
<span
className={cn(
'text-label-md font-medium',
isActive ? 'text-mint-700' : 'text-mint-500',
)}
>
{label}
</span>
</div>
);
<button
type="button"
className={cn(tabItemStyle({ active: isActive }))}
onClick={onClick}
aria-label={label}
aria-current={isActive ? 'page' : undefined}
>
<Icon
name={icon}
size={24}
color={isActive ? 'mint-700' : 'mint-500'}
className={cn(
'transition-transform duration-200',
isActive && 'scale-110',
)}
/>
<span
className={cn(
'text-label-md font-medium',
isActive ? 'text-mint-700' : 'text-mint-500',
)}
>
{label}
</span>
</button>
);

Comment on lines +39 to +55
<Icon
name={icon}
size={24}
color={isActive ? 'mint-700' : 'mint-500'}
className={cn(
'transition-transform duration-200',
isActive && 'scale-110',
)}
/>
<span
className={cn(
'text-label-md font-medium',
isActive ? 'text-mint-700' : 'text-mint-500',
)}
>
{label}
</span>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

색상 로직 중복을 제거하세요.

색상 결정 로직이 세 곳에서 반복됩니다:

  1. tabItemStyle 변형 (line 11-12)
  2. Iconcolor prop (line 42)
  3. spanclassName (line 51)

부모 divtext-mint-700/text-mint-500 클래스가 이미 자식 요소에 상속되므로 중복이 불필요합니다.

다음과 같이 리팩토링하세요:

   return (
     <div
       className={cn(tabItemStyle({ active: isActive }))} 
       onClick={onClick}
     >
       <Icon
         name={icon}
         size={24}
-        color={isActive ? 'mint-700' : 'mint-500'}
         className={cn(
           'transition-transform duration-200',
           isActive && 'scale-110',
         )}
       />
       <span
-        className={cn(
-          'text-label-md font-medium',
-          isActive ? 'text-mint-700' : 'text-mint-500',
-        )}
+        className="text-label-md font-medium"
       >
         {label}
       </span>
     </div>
   );

참고: Icon 컴포넌트가 부모로부터 색상을 상속받지 않는다면, color prop은 유지하되 span의 중복된 색상 클래스는 제거하세요.

📝 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
<Icon
name={icon}
size={24}
color={isActive ? 'mint-700' : 'mint-500'}
className={cn(
'transition-transform duration-200',
isActive && 'scale-110',
)}
/>
<span
className={cn(
'text-label-md font-medium',
isActive ? 'text-mint-700' : 'text-mint-500',
)}
>
{label}
</span>
<Icon
name={icon}
size={24}
className={cn(
'transition-transform duration-200',
isActive && 'scale-110',
)}
/>
<span
className="text-label-md font-medium"
>
{label}
</span>

Comment on lines 7 to 28
text: [
'text-display-lg',
'text-display-md',
'text-display-sm',
'text-headline-lg',
'text-headline-md',
'text-headline-sm',
'text-title-lg',
'text-title-md',
'text-title-sm',
'text-label-lg',
'text-label-md',
'text-label-sm',
'text-body-lg',
'text-body-md',
'text-body-sm',
'text-display-serif',
'text-headline-lg-serif',
'text-headline-md-serif',
'text-headline-sm-serif',
'text-label-serif',
'display-lg',
'display-md',
'display-sm',
'headline-lg',
'headline-md',
'headline-sm',
'title-lg',
'title-md',
'title-sm',
'label-lg',
'label-md',
'label-sm',
'body-lg',
'body-md',
'body-sm',
'display-serif',
'headline-lg-serif',
'headline-md-serif',
'headline-sm-serif',
'label-serif',
],
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

타이포그래피 키 변경 사항을 전체 코드베이스에서 확인하세요.

text- 접두사가 제거되었습니다 (예: text-display-lgdisplay-lg). 이는 tailwind-merge 설정 변경이므로, 실제 Tailwind CSS 클래스명이 프로젝트의 tailwind.config.ts에서도 동일하게 업데이트되었는지 확인이 필요합니다.

또한 기존 컴포넌트에서 이전 키를 사용하고 있다면 업데이트가 필요합니다.

다음 스크립트로 검증하세요:


🏁 Script executed:

#!/bin/bash
# Description: Verify typography key usage across the codebase

echo "=== Checking tailwind.config.ts for typography definitions ==="
fd "tailwind.config" --exec cat {}

echo -e "\n=== Searching for old text- prefixed typography classes ==="
rg -n "text-(display|headline|title|label|body)-(lg|md|sm)" src/ \
  -g "*.tsx" -g "*.ts" -g "!*.config.ts" \
  --context 2

echo -e "\n=== Searching for new typography classes without text- prefix ==="
rg -n "(display|headline|title|label|body)-(lg|md|sm)" src/ \
  -g "*.tsx" -g "*.ts" -g "!utils.ts" \
  --context 1

Length of output: 777


추가 검증이 필요합니다. 타이포그래피 마이그레이션의 완성도를 확인하겠습니다.

추가 검증을 진행하겠습니다. utils.ts 파일의 내용과 전체 코드베이스에서의 사용 패턴을 확인하겠습니다.

타이포그래피 마이그레이션이 미완료 상태입니다. 컴포넌트 업데이트가 필요합니다.

검증 결과, src/shared/components/tab/TabItem.tsx 라인 50에서 여전히 이전 형식(text-label-md)을 사용하고 있습니다. src/shared/lib/utils.ts에서 정의한 새로운 형식(접두사 제거)으로 업데이트되지 않았습니다.

  • TabItem.tsx 라인 50: 'text-label-md''label-md'로 변경 필요
  • tailwind.config.ts 파일을 찾을 수 없어 실제 Tailwind 클래스 정의 상태 확인 불가
  • 코드베이스의 다른 컴포넌트에서도 동일한 이전 형식을 사용하고 있을 가능성 검토 필요
🤖 Prompt for AI Agents
In src/shared/lib/utils.ts around lines 7-28 the typography tokens were migrated
to remove the "text-" prefix (e.g., 'label-md'), but a component still uses the
old format: update src/shared/components/tab/TabItem.tsx at line 50 replacing
'text-label-md' with 'label-md'; then sweep the codebase for other occurrences
of "text-" prefixed typography classes (e.g., text-*-*) and replace them with
the new token names from utils.ts, and if needed verify tailwind.config.ts to
ensure the new class names are defined (the config file wasn't found during
review).

@skyblue1232 skyblue1232 added the feat 새로운 기능 추가 / 퍼블리싱 label Oct 17, 2025
Copy link
Copy Markdown
Contributor

@jjangminii jjangminii left a comment

Choose a reason for hiding this comment

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

저희 배럴파일은 지양하기로 했어서 이부분은 나중에 페이지 만들게된다면 수정하는게 좋을 것. ㅏㅌ아요 추가적으로 코멘트 남겼으니 확인해주세요-! 수고하셨습니다

flex justify-between items-center
px-[1.3rem]
py-[1rem]
rounded-[37.07px]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

어떻게 값이...이게 진짜죠

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

rounded 피그마 보니까 그렇게 되있더라고요..

<nav
className="
fixed bottom-[2rem] left-1/2 -translate-x-1/2
w-[calc(100%-48px)] max-w-[42.5rem]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

w-full로 주고 사용하는곳에서 패딩값으로 조절하는 방식은 어떤가요? 그리고 최소 사이즈를 지정하는 방식으로요

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

그렇게 히면 이게 fixed여서 패딩 없이 꽉 채우더라고요..

Comment on lines +24 to +25
isActive?: boolean;
onClick?: () => void;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

이 두개가 사용되지 않을 때가 있나요? 옵셔널로 한 이유가 있을까요

Comment on lines +13 to +19
const NAV_ITEMS: NavItem[] = [
{ id: 'home', label: 'Home', icon: 'HouseSimple', href: '/' },
{ id: 'route', label: 'Route', icon: 'MapPin_', href: '/route' },
{ id: 'events', label: 'Events', icon: 'CalendarBlank', href: '/events' },
{ id: 'chatbot', label: 'ChatBot', icon: 'ChatCircle', href: '/chatbot' },
{ id: 'mypage', label: 'MyPage', icon: 'User', href: '/mypage' },
];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

이 부분은 따로 상수나 타입파일로 분리하는건 어떤가요?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

넵 알겠습니다!!

@@ -0,0 +1,51 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
import { TabItem } from './TabItem';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

동일 뎁스에 있는 파일이라 상대경로가 간단해보이긴하지만 절대경로로 수정하는 건 어떤가요? 이 부분에 대해서 어떻게 생각하시는지 궁금합니다

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

일단 구현해놓고 절대경로 수정하려고 했습니다! 수정할게요

Copy link
Copy Markdown
Contributor

@KongMezu KongMezu left a comment

Choose a reason for hiding this comment

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

시험기간에도 고생많으셨습니다.. 시험 파이팅하시고 나중에 리뷰 한번 보심 좋을거 같습니다 :)

"
>
{NAV_ITEMS.map((item) => {
const isActive = router.pathname === item.href;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

이거 하위 페이지로 진입하면 상위 탭 꺼지지 않을까요?? startwith 써서 코드 수정 해보면 좋을 거 같습니다 :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

수정했습니다!!

@skyblue1232
Copy link
Copy Markdown
Contributor Author

image image

@skyblue1232
Copy link
Copy Markdown
Contributor Author

image

화면 하단에 여백이 없어서 윗 컴포넌트(내브바)랑 아래 컴포넌트(본 화면)가 겹치더라고요.. 그래서 이런 식으로 했는데 괜찮은가요??

Copy link
Copy Markdown
Contributor

@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: 1

♻️ Duplicate comments (3)
src/shared/components/tab/TabItem.tsx (3)

39-55: 색상 로직 중복 제거 권장.

색상 결정 로직이 세 곳에서 반복됩니다:

  • tabItemStyle 변형 (lines 11-12)
  • Iconcolor prop (line 42)
  • spanclassName (line 51)

부모 요소의 text-mint-700/text-mint-500 클래스가 자식에게 상속되므로 중복이 불필요합니다.

다음과 같이 리팩토링할 수 있습니다:

       <Icon
         name={icon}
         size={24}
-        color={isActive ? 'mint-700' : 'mint-500'}
         className={cn(
           'transition-transform duration-200',
           isActive && 'scale-110',
         )}
       />
       <span
-        className={cn(
-          'text-body-sm',
-          isActive ? 'text-mint-700' : 'text-mint-500',
-        )}
+        className="text-body-sm"
       >
         {label}
       </span>

참고: Icon 컴포넌트가 부모의 색상을 상속받지 않는다면 color prop은 유지하되, span의 중복된 색상 클래스는 제거하세요.


21-26: prop 이름 충돌과 불필요한 옵셔널 수정 필요.

이전 리뷰에서 지적된 두 가지 문제가 여전히 남아있습니다:

  1. VariantProps<typeof tabItemStyle>이 이미 active prop을 포함하는데 별도로 isActive를 선언하여 충돌이 발생합니다.
  2. @jjangminii의 질문에 답변: 하단 네비게이션 컨텍스트에서 isActiveonClick은 항상 제공되므로 옵셔널로 만들 필요가 없습니다. 옵셔널로 두면 런타임에 문제가 발생할 수 있습니다.

다음과 같이 수정하세요:

-interface TabItemProps extends VariantProps<typeof tabItemStyle> {
+interface TabItemProps {
   label: string;
   icon: IconName;
-  isActive?: boolean;
-  onClick?: () => void;
+  isActive: boolean;
+  onClick: () => void;
 }

35-57: 접근성 문제: button 요소를 사용하세요.

이전 리뷰에서 지적된 접근성 문제가 해결되지 않았습니다. 클릭 가능한 div 대신 button 요소를 사용해야 합니다:

  • 키보드 탐색 불가능 (Tab, Enter, Space 키 미지원)
  • 스크린 리더가 탭 버튼임을 인식하지 못함
  • WCAG 접근성 기준 위반

다음과 같이 수정하세요:

   return (
-    <div
+    <button
+      type="button"
       className={cn(tabItemStyle({ active: isActive }))} 
       onClick={onClick}
+      aria-label={label}
+      aria-current={isActive ? 'page' : undefined}
     >
       <Icon
         name={icon}
         size={24}
         color={isActive ? 'mint-700' : 'mint-500'}
         className={cn(
           'transition-transform duration-200',
           isActive && 'scale-110',
         )}
       />
       <span
         className={cn(
           'text-body-sm',
           isActive ? 'text-mint-700' : 'text-mint-500',
         )}
       >
         {label}
       </span>
-    </div>
+    </button>
   );
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cd9e1f8 and 7cb2602.

📒 Files selected for processing (2)
  • src/shared/components/tab/BottomNav.tsx (1 hunks)
  • src/shared/components/tab/TabItem.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/shared/components/tab/BottomNav.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
src/shared/components/tab/TabItem.tsx (2)
src/shared/icons/iconNames.ts (1)
  • IconName (22-22)
src/shared/lib/utils.ts (1)
  • cn (71-73)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-and-deploy
🔇 Additional comments (2)
src/shared/components/tab/TabItem.tsx (2)

1-4: LGTM!

필요한 모든 의존성이 올바르게 임포트되었습니다.


6-19: 스타일 정의는 요구사항을 충족하지만 prop 이름과의 불일치를 확인하세요.

cva 변형이 active로 정의되어 있지만, 컴포넌트에서는 isActive prop을 사용합니다. 이로 인해 lines 21-26에서 prop 타입 충돌이 발생합니다.

/>
<span
className={cn(
'text-body-sm',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

타이포그래피 클래스 확인 필요.

이전 리뷰 코멘트에서 text-label-md를 제안했는데 현재 text-body-sm을 사용하고 있습니다. 디자인 시스템 명세에 맞는지 확인하세요.


I'll verify the typography class usage by examining the file and design system specifications.




🏁 Script executed:

#!/bin/bash
# Examine the TabItem.tsx file around line 50 with context
cat src/shared/components/tab/TabItem.tsx | head -80 | tail -40

Length of output: 790


🏁 Script executed:

#!/bin/bash
# Search for design system or typography class definitions
find . -type f \( -name "*.tsx" -o -name "*.ts" -o -name "*.css" -o -name "*.scss" \) | xargs rg -l "text-body-sm|text-label-md" -i | head -20

Length of output: 127


🏁 Script executed:

#!/bin/bash
# Look for design system documentation or tailwind config
find . -type f \( -name "*tailwind*" -o -name "*theme*" -o -name "*design*" -o -name "*token*" \) | grep -E "\.(config|json|ts|js)$" | head -20

Length of output: 930


🏁 Script executed:

#!/bin/bash
# Search for the typography classes in the entire codebase to understand usage patterns
rg "text-label-md|text-body-sm" -n --type=tsx --type=ts -C 2 | head -50

Length of output: 27



---

🏁 Script executed:

```shell
#!/bin/bash
# Examine the globals.css file for typography class definitions
cat src/styles/globals.css | grep -A 5 -B 2 "text-body-sm\|text-label-md"

Length of output: 1282


🏁 Script executed:

#!/bin/bash
# Search for tailwind config file
fd "tailwind" --extension "config" --extension "js" --extension "ts" --extension "json"

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Look at Tag.tsx to see how similar components use typography
cat src/shared/components/tag/Tag.tsx | head -100

Length of output: 1026


🏁 Script executed:

#!/bin/bash
# Search for typography usage with corrected syntax
rg "text-label-md|text-body-sm" -n -C 3 2>/dev/null

Length of output: 3265


text-label-md로 변경이 필요합니다.

디자인 시스템 명세에 따르면:

  • text-label-md: font-weight 500 (UI 레이블/요소용)
  • text-body-sm: font-weight 400 (본문 텍스트용)

TabItem의 레이블은 네비게이션 요소이므로, Tag 컴포넌트와 마찬가지로 text-label-md를 사용해야 합니다. 현재 text-body-sm은 디자인 시스템 명세와 맞지 않습니다.

🤖 Prompt for AI Agents
In src/shared/components/tab/TabItem.tsx around line 50, the text size/weight
class currently uses 'text-body-sm' but must be changed to 'text-label-md' to
match the design system (Tab labels are navigation elements and should use
font-weight 500 like Tag); update the className where 'text-body-sm' appears to
'text-label-md' (keeping other classes intact) and save/verify styles compile
and visual spot-check against the Tag component.

KongMezu
KongMezu previously approved these changes Oct 25, 2025
Copy link
Copy Markdown
Contributor

@KongMezu KongMezu left a comment

Choose a reason for hiding this comment

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

수고하셨습니다! 스크린리더 향상을 위한 리뷰 하나 남겨놨으니 한 번 적용해보시는게 좋을 거 같습니다 :) approve 해두겠습니다

router.pathname === item.href || router.pathname.startsWith(item.href + '/');

return (
<Link
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

저희가 스크린리더도 신경쓰기로 했으니
aria-current 추가해 접근성을 향상하면 좋을거 같습니다!

HTML ARIA 접근성 (accessibility)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

수정했습니다!!

@skyblue1232 skyblue1232 merged commit 193cbc6 into develop Oct 25, 2025
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comment 필요한 주석 추가 및 변경 feat 새로운 기능 추가 / 퍼블리싱

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] 공통 컴포넌트 - bottom tabBar

3 participants