Skip to content

feat : 공통 컴포넌트 BottomSheet 추가 - #50

Merged
kim3360 merged 14 commits into
developfrom
feat/8-Component-BottomSheet
Jul 13, 2026
Merged

kim3360 merged 14 commits into
developfrom
feat/8-Component-BottomSheet

Conversation

@kim3360

@kim3360 kim3360 commented Jul 9, 2026

Copy link
Copy Markdown
Member

변경 내용

  • Vaul 기반 BottomSheet 공통 컴포넌트를 추가했습니다.
  • 스냅 포인트(0.5 → 1) 드래그, 풀페이지 전환, 상단 네비(뒤로/닫기)를 지원합니다.
  • Figma 기준 PIN 목록 화면(PinListSheet, PinCard, SortTabs)을 구현했습니다.
  • 북마크/하트/다음 아이콘 SVG를 추가하고 ?react 방식으로 적용했습니다.
  • PIN 관련 타입(PinSong, PlaceInfo, PinSort)을 정의했습니다.

공통 컴포넌트

  • BottomSheet: 열림/닫힘, 스냅 포인트, 드래그 핸들
  • BottomSheet.Header / Content / Footer / FullPageNav 서브 컴포넌트

PIN 도메인 컴포넌트

  • PinListSheet: 장소 정보, 북마크, 정렬 탭, PIN 카드 목록
  • PinCard: 곡 정보, 좋아요 수, PIN 수 표시
  • SortTabs: 인기순/최신순 탭

SONG 도메인 컴포넌트

  • SongBottom: 제목, 검색창, 노래 카드 리스트
  • SongCard: 썸네일, 곡명, 아티스트 row 컴포넌트

관련 이슈

Closes #8
Closes #13

변경 사항

  • 새로운 기능 추가
  • 버그 수정
  • UI/UX 개선
  • 리팩토링
  • 문서 업데이트

테스트

  • 브라우저에서 정상 동작 확인
  • 기존 기능에 영향 없음 확인

스크린샷 (UI 변경시)
image
image

체크리스트

  • 코드가 정상적으로 동작합니다
  • 새로운 에러나 경고가 없습니다
  • 필요시 문서를 업데이트했습니다
  • 코드 포맷팅을 실행했습니다 (pnpm format)
  • ESLint 검사를 통과했습니다 (pnpm lint:fix)

코드 품질 확인
PR 제출 전에 다음 명령어를 실행하여 코드 품질을 확인해주세요:

pnpm lint:fix
pnpm format

Summary by CodeRabbit

Summary

  • New Features
    • Vaul 기반 바텀시트 UI를 추가했습니다(열기/닫기, 스냅, 풀페이지 전환 포함).
    • 장소별 핀 목록을 바텀시트로 열어 핀 선택과 정렬(인기/최신)을 지원합니다.
    • 노래 선택 바텀시트를 추가하고, 검색 후 곡을 선택할 수 있습니다.
    • 핀/장소/노래 및 정렬 관련 타입을 추가했습니다.
  • UI Updates
    • 상단 바 레이아웃을 grid → flex로 전환했습니다.
  • Chores
    • 의존성 구성을 업데이트했습니다.

@kim3360
kim3360 requested review from JeongGyul, L0521 and onebone July 9, 2026 13:09
@kim3360 kim3360 self-assigned this Jul 9, 2026
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Vaul 기반 공통 BottomSheet와 Pin 목록 시트, Song 선택 시트를 추가했습니다. 관련 도메인 타입, 카드, 정렬 탭, 검색 및 선택 상태 처리를 정의하고 TopBar 레이아웃을 flex 기반으로 변경했습니다.

Changes

BottomSheet 및 Pin/Song 기능

Layer / File(s) Summary
의존성 및 도메인 타입
package.json, src/types/pin.ts
vaul 의존성을 추가하고 PinSort, PlaceInfo, Pin, Song 타입을 정의했습니다.
공통 BottomSheet 구현
src/components/ui/BottomSheet.tsx
Vaul 래퍼, 컨텍스트와 훅, 스냅 포인트 상태 처리, Header/Content/FullPageNav 하위 컴포넌트를 추가했습니다.
Pin 목록 시트
src/features/pin/components/PinCard.tsx, src/features/pin/components/SortTabs.tsx, src/features/pin/components/PinListSheet.tsx
PIN 카드, 정렬 탭, 장소 정보와 PIN 목록을 표시하는 BottomSheet 및 선택 콜백을 추가했습니다.
Song 선택 시트
src/features/pin/components/SongCard.tsx, src/features/pin/components/SongBottom.tsx
노래 카드, 제목·아티스트 검색 필터링, 노래 선택 후 시트 닫기와 검색어 초기화를 추가했습니다.

TopBar 레이아웃

Layer / File(s) Summary
TopBar flex 배치
src/components/ui/TopBar.tsx
최상위 컨테이너의 배치를 grid에서 flex와 justify-between 기반으로 변경했습니다.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant SongBottom
  participant SearchInput
  participant BottomSheet
  participant SongCard

  User->>SongBottom: 노래 선택 시트 열기 클릭
  SongBottom->>BottomSheet: open=true 설정
  User->>SearchInput: 검색어 입력
  SearchInput->>SongBottom: query 변경 전달
  SongBottom->>SongBottom: filteredSongs 계산
  SongBottom->>SongCard: 필터링된 곡 목록 렌더링
  User->>SongCard: 곡 선택
  SongCard->>SongBottom: onClick 호출
  SongBottom->>SongBottom: onSelect(song) 호출
  SongBottom->>BottomSheet: 시트 닫기 및 query 초기화
Loading

Suggested labels: feature

Suggested reviewers: onebone, jeonggyul, l0521

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning TopBar의 grid→flex 레이아웃 변경은 이번 BottomSheet/핀 카드 작업과 직접 관련이 없습니다. TopBar 변경은 별도 PR로 분리하거나, 이번 작업과의 연관성을 명확히 설명해 주세요.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목이 공통 BottomSheet 추가라는 핵심 변경을 간결하게 잘 요약합니다.
Linked Issues check ✅ Passed BottomSheet와 핀/곡 카드들이 #8, #13의 핵심 요구를 구현합니다.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/8-Component-BottomSheet

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.

@kim3360
kim3360 marked this pull request as ready for review July 9, 2026 14:19

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (5)
src/features/pin/components/SongBottom.tsx (2)

8-11: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

타입명이 오해를 줍니다.

SongCardProps는 실제로 SongPage(SongBottom) 컴포넌트의 props인데, SongCard.tsx에도 동일한 이름의 타입이 존재해 혼동을 줍니다. SongPageProps 또는 SongBottomProps로 리네임하는 것을 권장합니다.

♻️ 제안
-type SongCardProps = {
+type SongPageProps = {
   songs?: Song[];
   onSelect?: (song: Song) => void;
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/pin/components/SongBottom.tsx` around lines 8 - 11, Rename the
props type in SongBottom to avoid confusion with the similarly named type in
SongCard; the current SongCardProps is misleading because it belongs to the
SongBottom/SongPage component. Update the type name in SongBottom and any local
references to a clearer identifier such as SongBottomProps or SongPageProps,
keeping the props shape unchanged.

20-20: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

export 방식이 다른 컴포넌트들과 불일치합니다.

BottomSheet, SongCard는 named export인데 이 컴포넌트만 export default를 사용합니다. 또한 파일명(SongBottom.tsx)과 컴포넌트명(SongPage)도 일치하지 않아 import 시 혼란을 줄 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/pin/components/SongBottom.tsx` at line 20, SongBottom.tsx has an
export/name mismatch: the SongPage component is using export default while
related components like BottomSheet and SongCard use named exports. Update
SongPage to use the same named export style as the other components, and align
the component name with the file’s purpose to avoid import confusion; keep the
fix localized to SongPage and its export declaration.
src/features/pin/components/PinCard.tsx (1)

7-7: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

타입명 오타: PingCardPropsPinCardProps.

컴포넌트명은 PinCard인데 타입명이 PingCardProps로 오타가 있습니다. 로컬 타입이라 당장 문제는 없지만 가독성을 위해 수정하는 것을 권장합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/pin/components/PinCard.tsx` at line 7, The local props type name
in PinCard is misspelled as PingCardProps; rename it to PinCardProps in the
PinCard component so the type matches the component name and improves
readability. Update the type alias declaration and any nearby references in
PinCard.tsx that use this props type to keep the naming consistent.
src/features/pin/components/SortTabs.tsx (1)

16-35: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

aria-selectedrole="tab" 컨텍스트 없이 사용하면 의미가 명확하지 않습니다.

일반 <button>aria-selected를 붙이는 것은 ARIA 스펙상 tab/tablist/tabpanel 구조에서 사용하는 속성입니다. 탭 UI가 아니라면 aria-pressed가 더 적절하고, 탭 UI로 의도한 것이라면 부모에 role="tablist", 각 버튼에 role="tab"을 추가해야 스크린리더가 올바르게 인식합니다.

♿ 제안 수정 (tablist 구조로)
-    <div className="flex w-full h-10 rounded-xl bg-pli-black-75 px-[4.5px] py-1 gap-[3px]">
+    <div role="tablist" className="flex w-full h-10 rounded-xl bg-pli-black-75 px-[4.5px] py-1 gap-[3px]">
       {OPTIONS.map((option) => {
         const selected = value === option.value;

         return (
           <button
             key={option.value}
             type="button"
+            role="tab"
             aria-selected={selected}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/pin/components/SortTabs.tsx` around lines 16 - 35, The SortTabs
buttons are using aria-selected without a tab context, so update the
accessibility semantics in SortTabs to match the intended UI. If this is a tab
interface, add role="tablist" to the container and role="tab" to each button in
the OPTIONS map so aria-selected is valid; otherwise replace aria-selected with
aria-pressed on the button elements. Keep the change localized to SortTabs and
preserve the existing selected state logic.
src/features/pin/components/PinListSheet.tsx (1)

15-53: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

미리보기용 mock 데이터와 트리거 버튼이 재사용 컴포넌트에 하드코딩되어 있습니다.

MOCK_PLACE, MOCK_PINS를 기본값으로 사용하고, 컴포넌트 내부에 "PIN 목록 바텀시트 미리보기" 안내 문구와 열기 버튼까지 포함되어 있습니다. PinListSheet는 지도/핀 조회 화면에서 재사용될 공통 시트 컴포넌트인데, 데모/미리보기 로직이 섞여 있어 실제 사용처(지도 클릭 시 열기 등)에 적용할 때 이 프리뷰 UI를 제거하는 별도 작업이 필요합니다. 미리보기는 Storybook이나 별도 데모 페이지로 분리하는 것을 권장합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/pin/components/PinListSheet.tsx` around lines 15 - 53,
`PinListSheet` is mixing reusable sheet behavior with demo-only mock data and
trigger UI. Remove the `MOCK_PLACE` and `MOCK_PINS` default fallbacks from
`PinListSheetProps` usage, and delete the hardcoded preview text/button inside
`PinListSheet` so the component only renders the sheet itself. Keep the
open/close trigger and any preview UX in a separate demo surface such as
Storybook or a dedicated page, and update the `PinListSheet` component API to
rely on real `place`, `pins`, and `onPinClick` inputs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/ui/BottomSheet.tsx`:
- Around line 17-41: `SheetContent` currently renders `DrawerPrimitive.Content`
without an accessible title, so add a `DrawerPrimitive.Title` inside the content
(or wrap the existing `Header` heading with it) to satisfy accessibility
requirements; if this sheet should be modal with a dimmed backdrop, also render
`DrawerPrimitive.Overlay` inside `SheetPortal` alongside
`DrawerPrimitive.Content` in `BottomSheet.tsx`.

In `@src/features/pin/components/PinCard.tsx`:
- Around line 7-20: `PinCard`에서 선언된 className prop이 전달받지 않고 button에 적용되지 않아 호출부
스타일이 무시됩니다. `PinCard`의 props 구조 분해에 className을 포함하고, 루트 `button`의 className에 기존
고정 클래스와 함께 병합되도록 수정하세요. `PingCardProps`와 `PinCard` 컴포넌트를 기준으로 찾아서, 전달된
className이 실제 렌더링에 반영되게 하면 됩니다.

In `@src/features/pin/components/SongBottom.tsx`:
- Around line 63-69: The SongBottom sheet click handler currently closes the
sheet without clearing the search state, so reopening preserves the previous
query. Update the SongCard onClick flow in SongBottom to also reset the local
query state when a song is selected, alongside onSelect and setOpen(false), so
the sheet reopens with a fresh search. Use the existing query state setter in
this component and keep the reset behavior tied to the same selection path.

---

Nitpick comments:
In `@src/features/pin/components/PinCard.tsx`:
- Line 7: The local props type name in PinCard is misspelled as PingCardProps;
rename it to PinCardProps in the PinCard component so the type matches the
component name and improves readability. Update the type alias declaration and
any nearby references in PinCard.tsx that use this props type to keep the naming
consistent.

In `@src/features/pin/components/PinListSheet.tsx`:
- Around line 15-53: `PinListSheet` is mixing reusable sheet behavior with
demo-only mock data and trigger UI. Remove the `MOCK_PLACE` and `MOCK_PINS`
default fallbacks from `PinListSheetProps` usage, and delete the hardcoded
preview text/button inside `PinListSheet` so the component only renders the
sheet itself. Keep the open/close trigger and any preview UX in a separate demo
surface such as Storybook or a dedicated page, and update the `PinListSheet`
component API to rely on real `place`, `pins`, and `onPinClick` inputs.

In `@src/features/pin/components/SongBottom.tsx`:
- Around line 8-11: Rename the props type in SongBottom to avoid confusion with
the similarly named type in SongCard; the current SongCardProps is misleading
because it belongs to the SongBottom/SongPage component. Update the type name in
SongBottom and any local references to a clearer identifier such as
SongBottomProps or SongPageProps, keeping the props shape unchanged.
- Line 20: SongBottom.tsx has an export/name mismatch: the SongPage component is
using export default while related components like BottomSheet and SongCard use
named exports. Update SongPage to use the same named export style as the other
components, and align the component name with the file’s purpose to avoid import
confusion; keep the fix localized to SongPage and its export declaration.

In `@src/features/pin/components/SortTabs.tsx`:
- Around line 16-35: The SortTabs buttons are using aria-selected without a tab
context, so update the accessibility semantics in SortTabs to match the intended
UI. If this is a tab interface, add role="tablist" to the container and
role="tab" to each button in the OPTIONS map so aria-selected is valid;
otherwise replace aria-selected with aria-pressed on the button elements. Keep
the change localized to SortTabs and preserve the existing selected state logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ced5673a-1e74-4d9e-92ff-1b08c85955fb

📥 Commits

Reviewing files that changed from the base of the PR and between f146cf1 and e8086c9.

⛔ Files ignored due to path filters (4)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • src/assets/icons/bookmark.svg is excluded by !**/*.svg
  • src/assets/icons/heart.svg is excluded by !**/*.svg
  • src/assets/icons/next.svg is excluded by !**/*.svg
📒 Files selected for processing (8)
  • package.json
  • src/components/ui/BottomSheet.tsx
  • src/features/pin/components/PinCard.tsx
  • src/features/pin/components/PinListSheet.tsx
  • src/features/pin/components/SongBottom.tsx
  • src/features/pin/components/SongCard.tsx
  • src/features/pin/components/SortTabs.tsx
  • src/types/pin.ts

Comment thread src/components/ui/BottomSheet.tsx
Comment thread src/features/pin/components/PinCard.tsx
Comment thread src/features/pin/components/SongBottom.tsx
@kim3360

kim3360 commented Jul 10, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/ui/TopBar.tsx`:
- Line 28: Update the TopBar layout around the div using justify-between so the
title remains geometrically centered regardless of back/close button widths or
missing callbacks. Use equal-width grid columns or independently center-position
the title, while preserving the existing button behavior and styling for screens
such as NicknameSetupPage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 826747c0-7d2b-4f87-89af-249f089bff8b

📥 Commits

Reviewing files that changed from the base of the PR and between e64de07 and e0f89cf.

📒 Files selected for processing (1)
  • src/components/ui/TopBar.tsx

Comment thread src/components/ui/TopBar.tsx Outdated
@kim3360
kim3360 merged commit 2c06d12 into develop Jul 13, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

공통 컴포넌트: PinSongCard 공통 컴포넌트: BottomSheet

2 participants