Skip to content

[feat] 검색 페이지 UI 구현#44

Merged
maylh merged 13 commits intodevelopfrom
feat/#37/search-ui
Aug 17, 2025
Merged

[feat] 검색 페이지 UI 구현#44
maylh merged 13 commits intodevelopfrom
feat/#37/search-ui

Conversation

@maylh
Copy link
Collaborator

@maylh maylh commented Aug 16, 2025

🛰️ 관련 이슈


✨ 주요 변경 사항

  1. 검색 페이지 UI 구현
  2. 검색 결과 페이지 UI 구현
  3. 홈페이지, 검색 페이지, 검색 결과 페이지 라우트 추가
  4. SearchResultItem에 검색어 타입(playlist or user)을 의미하는 type props 추가
  5. 네비게이션 바 다중 경로 처리 가능하도록 수정
  6. Routes 전체를 하나의 <Suspense>로 감싸도록 변경

🔍 테스트 방법 / 체크리스트

  • 없음

🗯️ PR 포인트

  • 엔터 키를 통해서도 검색이 실행될 수 있도록 onKeyDown props를 추가했습니다.

  • 검색 결과는 백엔드에서 플레이리스트인지 유저인지 구분하는 타입이 함께 반환될 예정이라, type props를 미리 추가했습니다. 데이터 형식에 맞춰 추후 컴포넌트를 한 번 더 수정할 계획입니다.

  • 네비게이션 바 다중 경로 처리를 지원하도록 수정했습니다. 아래와 같이 버튼 클릭 시 이동할 기본 경로는 paths 배열의 첫 번째 요소로 설정해두었기 때문에,

    <NavLink to={paths[0]} key={title}>
    

    활성화 경로가 여러 개인 경우, 배열 뒤쪽에 추가해주셔야 합니다 !

     { icon: Home, title: '홈', paths: ['/', '/search', '/searchResult'] }
    
  • 기존에는 각 페이지를 개별 로 감싸 로딩 상태를 처리했지만, 현재는 Routes 전체를 하나의 로 감싸도록 변경했습니다. 이 방식은 개별 페이지마다 를 추가할 필요가 없어 편리하지만, 모든 페이지가 로딩되기 전까지 로딩 화면이 표시되므로 진입 페이지에서도 불필요하게 로딩이 보일 수 있습니다 (아주 짧게 !!) 따라서 사용자가 처음 진입할 가능성이 높은 //discover 페이지는 적용에서 제외하는 방식도 고려해보면 좋을 것 같습니다 !

  • vercel auth 해제해두어서 전달해드린 프리뷰 링크로 접속 가능합니다 ! (혹시 안되면 말씀해주세요 . .)


🚀 알게된 점


📖 참고 자료 (선택)


🖼️ Demo

2025-08-17.04.20.36.mov

++
너무나도 당연히 검색어 누르면 다시 그 검색어로 재검색 된다고 생각했어요 ... (왜지)
우선 discover 페이지로 이동하도록 수정해두었습니다 ! 데모 영상에서 해당 부분은 눈감아주세요 ..

  • 플레이리스트라면, 해당 플레이리스트 페이지로 이동
  • 유저라면, 해당 유저의 대표 플레이리스트 페이지로 이동

이게 맞는 것 같은데,

  • 플레이리스트

    • id가 플레이리스트의 고유 id 이므로 그대로 /discover/:id로 이동하면 됨 (response data의 id === playlist의 고유 id도 가정이긴 한데, 보통 해당 플레이리스트 정보를 내려주면 그 id는 그 플레이리스트의 고유 id를 의미하니까 ..?)
  • 유저

    • 클릭 시 유저의 대표 플레이리스트로 이동하려면 대표 플레이리스트의 id를 알아야 함
    • 즉, item.id가 유저의 id라면 해당 유저의 대표 플레이리스트 id를 매핑해야만 이동 가능 (바로 이동하려면 유저의 id 값 = 해당 유저 대표 플레이리스트의 id값이어야 하는데 이건 불가능하지 않은가 ? 🧐🧐🧐)

Summary by CodeRabbit

  • New Features

    • 검색 페이지(Search)와 검색 결과 페이지(SearchResult) 추가 및 관련 라우트(/, /search, /searchResult, /mypage/*) 추가.
    • CategoryButton, TrendKeyword 등 UI 컴포넌트 추가; SearchResultItem이 type·imageUrl을 지원.
    • Input 컴포넌트에 Enter 등 키 이벤트(onKeyDown) 지원.
  • Refactor

    • 홈·공용 UI와 플레이리스트를 배럴(named export)로 통합; 라우트 로딩을 위한 최상위 Suspense 적용.
  • Style

    • Header의 가로 오프셋 제거로 레이아웃 정리.
  • Documentation

    • Storybook: CategoryButton, TrendKeyword, SearchResultItem 스토리 추가/갱신.

@maylh maylh self-assigned this Aug 16, 2025
@maylh maylh requested a review from hansololiviakim as a code owner August 16, 2025 19:20
@maylh maylh added the HIGH 빠르게 처리해야 하는 높은 우선순위 label Aug 16, 2025
@coderabbitai
Copy link

coderabbitai bot commented Aug 16, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

검색 및 검색결과 페이지와 관련 UI 컴포넌트를 추가하고 라우팅을 확장했습니다. NavItem 인터페이스를 다중 경로로 변경하고 NavBar 활성 로직을 업데이트했으며, Input에 onKeyDown을 추가하고 일부 UI를 배럴화했습니다.

Changes

Cohort / File(s) Summary
Routing
src/app/routes/routes.tsx
HomePage, SearchPage, SearchResultPage를 lazy 로드로 추가하고 라우트를 확장. /mypage/*는 PrivateRoute로 유지. Routes 전체를 단일 Suspense로 감쌌음.
Search Pages
src/pages/searchPage/index.tsx, src/pages/searchPage/SearchResultPage.tsx
검색 페이지 및 검색결과 페이지 신규 추가: 입력/엔터 네비게이션, URL 쿼리 동기화, 정렬 상태 및 mock 데이터 기반 결과 렌더링. searchResultMockData도 추가.
Search UI Components
src/pages/searchPage/ui/SearchResultItem.tsx, src/pages/searchPage/ui/TrendKeyword.tsx, src/pages/searchPage/ui/index.ts
SearchResultItem: props 변경(image→imageUrl, type 추가, userName nullable). TrendKeyword 컴포넌트 추가 및 ui index에 재노출.
Shared UI & Config
src/shared/ui/Input.tsx, src/shared/ui/Header.tsx, src/shared/ui/NavBar.tsx, src/shared/config/navItems.ts, src/shared/ui/CategoryButton.tsx, src/shared/ui/index.ts
Input에 onKeyDown prop 추가. Header의 폭/마진 관련 스타일 제거. NavItem.path→paths:string[] 변경 및 NavBar에서 paths.includes로 활성화 로직 적용. CategoryButton 추가 및 UI 배럴에 노출, ScrollCarousel 배럴 노출 추가.
Home & Playlist Exports
src/pages/homePage/index.tsx, src/pages/homePage/ui/index.ts, src/widgets/playlist/index.ts
홈/플레이리스트 모듈을 배럴(named)로 정리하여 named export로 변경/재노출(LoopCarousel, Playlist 등).
Storybook
src/stories/CategoryButton.stories.tsx, src/stories/SearchResultItem.stories.tsx, src/stories/TrendKeyword.stories.ts
CategoryButton/TrendKeyword 스토리 추가 및 SearchResultItem 스토리에서 image→imageUrl, type arg 반영.

Sequence Diagram(s)

sequenceDiagram
  participant U as 사용자
  participant SP as SearchPage
  participant R as Router
  participant SRP as SearchResultPage

  U->>SP: 키워드 입력 + Enter
  SP->>R: navigate("/searchResult?keyword=...")
  R->>SRP: 렌더(SearchResultPage)
  SRP->>SRP: query에서 keyword 동기화 및 결과 렌더
  U->>SRP: 결과 아이템 클릭
  SRP->>R: navigate("/discover/<id>")
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Assessment against linked issues

Objective Addressed Explanation
검색 페이지 UI 구현 (#37)
검색결과 페이지 UI 구현 (#37)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
홈 페이지 배럴 리팩터링 (src/pages/homePage/index.tsx) 검색 UI 구현 목표와 직접적 연관 없음 — 모듈 내보내기 구조 변경.
Routes Suspense 재배치 (src/app/routes/routes.tsx) 라우트 추가는 관련되나 전역 Suspense 재배치(로딩 구조 변경)는 이슈의 UI 구현 요구에 명시되지 않음.
Header 스타일 폭/마진 제거 (src/shared/ui/Header.tsx) 검색 UI 구현 요구사항과 직접 관련되지 않는 스타일 조정임.

Possibly related PRs

Suggested reviewers

  • hansololiviakim

Poem

"나는 토끼, 키워드 톡톡 쳐,
엔터 누르면 길이 뻥! 뻥! 열려요.
트렌드 말랑, 결과 뿅—플레이리스트 춤추네.
카테 버튼 빛나고, Paths에 길이 많네.
냠냠 검색숲, 토끼가 깡총 🐰🌿"

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#37/search-ui

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Summary of Changes

Hello @maylh, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

이 PR은 검색 페이지 및 검색 결과 페이지의 UI를 구현하고, 관련 라우팅을 추가합니다. 또한, 기존 내비게이션 바의 다중 경로 처리 기능을 개선하고, 전역 Suspense 설정을 변경하여 애플리케이션의 사용자 경험을 향상시키는 것을 목표로 합니다.

Highlights

  • 검색 페이지 및 검색 결과 페이지 UI 구현: 사용자가 검색어를 입력하고 결과를 확인할 수 있는 새로운 페이지들이 추가되었습니다.
  • 홈페이지, 검색 페이지, 검색 결과 페이지 라우트 추가: React Router를 사용하여 이들 페이지로의 이동 경로가 설정되었습니다.
  • SearchResultItem 컴포넌트 개선: 검색 결과 항목을 표시하는 SearchResultItem 컴포넌트에 type (플레이리스트 또는 사용자) prop이 추가되어, 백엔드에서 반환될 데이터 형식에 맞춰 유연하게 표시할 수 있도록 준비되었습니다.
  • 네비게이션 바 다중 경로 처리 지원: NAV_ITEMS 설정이 path 대신 paths 배열을 사용하여 하나의 내비게이션 항목이 여러 관련 경로에서 활성화될 수 있도록 수정되었습니다.
  • Routes 전체를 Suspense로 감싸도록 변경: 개별 페이지에 적용되던 Suspense가 Routes 컴포넌트 전체를 감싸도록 변경되어 로딩 처리 방식이 중앙화되었습니다.
  • Input 컴포넌트에 onKeyDown prop 추가: 검색 입력 필드에서 엔터 키를 눌러 검색을 실행할 수 있도록 Input 컴포넌트에 onKeyDown 이벤트 핸들러가 추가되었습니다.
  • 새로운 UI 컴포넌트 추가: CategoryButton과 TrendKeyword 컴포넌트가 새로 추가되어 검색 페이지의 UI를 구성하는 데 사용됩니다.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

이번 PR은 검색 페이지와 검색 결과 페이지의 UI를 구현하고 관련 라우팅을 설정하는 내용을 담고 있습니다. 전반적으로 FSD 아키텍처에 따라 구조가 잘 잡혀있고, 재사용 가능한 컴포넌트들이 잘 활용되었습니다.

몇 가지 개선점을 제안합니다:

  • 검색 결과 페이지에서 항목 클릭 시의 사용자 경험을 개선할 수 있는 방안을 제안했습니다.
  • TypeScript 타입 안정성을 높이기 위해 타입 단언 대신 명시적 타입 정의를 사용하는 방법을 제안했습니다.
  • 코드 가독성과 유지보수성 향상을 위해 컴포넌트 파일에서 목업 데이터를 분리하는 것을 권장했습니다.
  • 웹 접근성 향상을 위해 시맨틱 HTML 태그 사용을 제안했습니다.

PR에 언급해주신 내용들도 잘 확인했습니다. 특히 네비게이션 바의 다중 경로 처리나 Suspense 적용 범위에 대한 고민은 좋은 접근이라고 생각합니다. 코드 리뷰가 개발에 도움이 되기를 바랍니다.

@github-actions
Copy link

github-actions bot commented Aug 16, 2025

🎵 Storybook Link 🎵
🔗 https://689dbb45f8d09aea7832eeb1-ryubdgsbdl.chromatic.com/

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: 4

🧹 Nitpick comments (24)
src/pages/searchPage/ui/TrendKeyword.tsx (1)

7-9: div 대신 button으로 변경하고 onClick/접근성 보완을 권장

해당 컴포넌트는 시각적으로 버튼(칩)이며 :active 상태를 정의하고 있어 상호작용 요소임이 암시됩니다. 하지만 현재 div로 구현되어 있어 키보드 포커스/엔터 동작/스크린리더 접근성이 부족합니다. 추후 클릭으로 검색 실행/전환이 붙을 가능성이 높으므로 button으로 전환하고 onClick 및 포커스 스타일을 추가하는 것을 권장합니다. 또한 포인터 커서도 함께 지정하는 것이 좋습니다.

적용 예시:

 interface TrendKeywordProps {
   text: string
+  onClick?: () => void
 }

-const TrendKeyword = ({ text }: TrendKeywordProps) => {
-  return <StyledButton>{text}</StyledButton>
+const TrendKeyword = ({ text, onClick }: TrendKeywordProps) => {
+  return (
+    <StyledButton type="button" onClick={onClick}>
+      {text}
+    </StyledButton>
+  )
 }
 
 export default TrendKeyword
 
-const StyledButton = styled.div`
+const StyledButton = styled.button`
   width: fit-content;
   background-color: ${({ theme }) => theme.COLOR['gray-700']};
   color: ${({ theme }) => theme.COLOR['gray-50']};
   padding: 6px 12px;
   border-radius: 99px;
   ${({ theme }) => theme.FONT['body2-normal']};
+  cursor: pointer;
+  transition: background-color 0.2s ease, color 0.2s ease;
 
+  &:focus-visible {
+    outline: 2px solid ${({ theme }) => theme.COLOR['primary-normal']};
+    outline-offset: 2px;
+  }
   &:active {
     background-color: ${({ theme }) => theme.COLOR['primary-normal']};
     color: ${({ theme }) => theme.COLOR['gray-900']};
   }
 `

참고: 향후 “선택됨(토글)” 상태가 필요하다면 selected?: boolean props를 추가하고 :active 대신 props 기반 동적 스타일을 적용하는 편이 일관됩니다.

Also applies to: 13-25

src/shared/ui/NavBar.tsx (2)

16-18: 활성 상태 판별: 하위 경로 및 트레일링 슬래시 대응

paths.includes(location.pathname)는 정확히 일치하는 경우만 활성 처리됩니다. /discover/123 같은 하위 경로가 생기면 비활성으로 보일 수 있습니다. 단순하고 안전한 쪽으로 startsWith 기반으로 개선을 제안합니다. 루트(/)는 특수 처리해야 합니다.

-      {NAV_ITEMS.map(({ icon: Icon, title, paths }) => {
-        const isActive = paths.includes(location.pathname)
+      {NAV_ITEMS.map(({ icon: Icon, title, paths }) => {
+        const isActive = paths.some((p) =>
+          p === '/'
+            ? location.pathname === '/'
+            : location.pathname === p || location.pathname.startsWith(`${p}/`)
+        )
         const color = isActive ? theme.COLOR['primary-normal'] : theme.COLOR['gray-100']

대안: react-router의 matchPath를 사용하면 패턴 매칭을 더 유연하게 할 수 있습니다. 필요 시 요청 주세요, 해당 방식으로도 패치 드리겠습니다.


21-21: key 안정성 및 a11y 향상

  • key로 title을 쓰면 다국어 전환 등으로 title이 바뀔 때 불필요한 재마운트가 발생할 수 있습니다. 기본 경로를 key로 쓰는 편이 안정적입니다.
  • 현재 페이지 표시를 위해 aria-current="page"를 부여하면 보조기기 접근성이 개선됩니다.
-          <NavLink to={paths[0]} key={title}>
+          <NavLink to={paths[0]} key={paths[0]} aria-current={isActive ? 'page' : undefined}>
src/app/routes/routes.tsx (2)

19-19: 라우트 경로 네이밍 일관성: 소문자/케밥 케이스 권장

URL은 일반적으로 소문자 케밥 케이스를 권장합니다. 현재 /searchResult는 대문자를 포함하고 있어 SEO/가독성 측면에서 일관성이 떨어집니다. /search-results로 조정하는 것을 제안합니다.

아래 변경 시, navItems 등 경로를 참조하는 설정도 함께 업데이트해 주세요.

-        <Route path="/searchResult" element={<SearchResultPage />} />
+        <Route path="/search-results" element={<SearchResultPage />} />

15-29: Routes 전체를 하나의 Suspense로 감싸는 전략: UX 트레이드오프 점검 제안

전역 Suspense는 간결하지만, 라우트 전환 시 전체 화면이 로딩 상태로 전환될 수 있습니다. 페이지별 스켈레톤/로딩 UI를 유지하고, 전역 Suspense는 최소화하는 혼합 전략도 고려해볼 수 있습니다. 특히 홈/검색 페이지는 진입 빈도가 높아 프리패치(import().then) 또는 idle preloading도 검토해볼 가치가 있습니다.

전환 시 전역 로딩이 UX에 미치는 영향(플리커/레이아웃 점프)이 수용 가능한지 QA에서 확인 부탁드립니다.

src/pages/searchPage/ui/SearchResultItem.tsx (3)

36-41: button 기본 type 명시로 의도치 않은 폼 제출 방지

styled.button은 폼 내부에서 기본 type이 'submit'로 동작할 수 있습니다. 명시적으로 type="button"을 지정해 사이드이펙트를 방지하는 것을 권장합니다.

-const ItemContainer = styled.button`
+const ItemContainer = styled.button.attrs({ type: 'button' })`
   display: flex;
   gap: 12px;
   width: 335px;
 `

6-12: props를 판별 유니온으로 강화해 타입 안정성 향상

플레이리스트의 경우 userName이 반드시 필요하고, 유저 타입에선 불필요합니다. 판별 유니온을 사용하면 컴파일 타임에 이를 강제할 수 있습니다.

-interface SearchResultItemProps {
-  imageUrl?: string
-  type: 'playlist' | 'user'
-  searchResult: string
-  userName?: string | null
-  onClick: () => void
-}
+type SearchResultBaseProps = {
+  imageUrl?: string
+  searchResult: string
+  onClick: () => void
+}
+
+type PlaylistResultProps = SearchResultBaseProps & {
+  type: 'playlist'
+  userName: string
+}
+
+type UserResultProps = SearchResultBaseProps & {
+  type: 'user'
+  userName?: null | undefined
+}
+
+type SearchResultItemProps = PlaylistResultProps | UserResultProps

48-65: 텍스트 말줄임 처리 유연화: 하드코딩된 max-width 제거 제안

해상도/레이아웃 변화에 대응하기 위해 픽셀 고정값 대신 flex 기반으로 말줄임 처리하는 것을 권장합니다. Right에 min-width: 0을 주고, SearchResult는 width: 100%로 처리하면 컨테이너 크기에 맞춰 자연스럽게 동작합니다.

 const Right = styled.div`
   display: flex;
   flex-direction: column;
   justify-content: center;
   align-items: flex-start;
   gap: 4px;
+  min-width: 0;
 `
 
 const SearchResult = styled.span`
-  display: inline-block;
-  max-width: 267px;
+  display: block;
+  width: 100%;
   overflow: hidden;
   text-overflow: ellipsis;
   white-space: nowrap;
 
   color: ${({ theme }) => theme.COLOR['gray-50']};
   ${({ theme }) => theme.FONT.headline2};
 `
src/pages/searchPage/index.tsx (4)

17-19: 뒤로가기 안전 처리(히스토리 없을 때 홈으로 폴백)

히스토리 스택이 없는 진입(직접 URL 접근)에서 navigate(-1)는 무효합니다. 폴백을 추가해 주세요.

-        left={<SvgButton icon={LeftArrow} onClick={() => navigate(-1)} />}
+        left={
+          <SvgButton
+            icon={LeftArrow}
+            onClick={() => (window.history.length > 1 ? navigate(-1) : navigate('/'))}
+          />
+        }

34-35: 페이지 내 다중 h1 사용 → 섹션 헤딩을 h2로 조정 권장

페이지당 h1은 한 번만 사용하는 것이 시맨틱/접근성 측면에서 안전합니다. 섹션 타이틀은 h2가 적합합니다.

-        <h1>인기 검색어</h1>
+        <h2>인기 검색어</h2>
-        <h1>장르와 테마</h1>
+        <h2>장르와 테마</h2>
-  & h1 {
+  & h2 {
     ${({ theme }) => theme.FONT.headline1};
     font-weight: 600;
   }

Also applies to: 42-43, 60-63


43-47: 카테고리 선택 시 검색과의 연결 고려

CategoryButton 클릭 시 해당 키워드로 즉시 검색하거나 검색창에 채워 넣는 인터랙션을 제공하면 사용성이 좋아집니다. 추후 API 연동 시 onClick 핸들러 연결을 제안합니다.


87-113: 목데이터 분리

페이지 파일 내 목데이터는 분리하여 관리하는 것이 유지보수에 유리합니다. 예: src/pages/searchPage/mocks.ts 또는 fixtures로 이동.

src/stories/TrendKeyword.stories.ts (1)

6-6: 스토리 카테고리 정합성

TrendKeyword는 searchPage UI 산출물로 보입니다. Storybook 트리에서 ‘Shared’보다는 ‘Search’와 같은 도메인 카테고리가 더 적합해 보입니다.

-  title: 'Shared/TrendKeyword',
+  title: 'Search/TrendKeyword',
src/shared/ui/CategoryButton.tsx (1)

14-17: 사이즈 토큰의 테마화 고려

sizes를 추후 theme 또는 상수 모듈로 승격하면 일관성 유지와 스케일링에 유리합니다. 현재는 이 수준으로 충분하나, 재사용도가 높아지면 분리 권장합니다.

src/pages/searchPage/SearchResultPage.tsx (6)

88-114: Fast Refresh 경고 해결: mock 데이터를 별도 파일로 분리하고 이 파일에서는 컴포넌트만 export 하세요

현재 파일에서 컴포넌트 외에 searchResultMockData를 함께 export 하고 있어 Fast Refresh 최적화가 깨질 수 있습니다. mock/fixture는 별도 파일로 분리하고 이 파일은 페이지 컴포넌트만 export 하도록 권장합니다.

적용 예시 (이 파일 수정):

- export const searchResultMockData = [
+ const searchResultMockData = [
   {
     id: 1,
     type: 'playlist',
     searchResult: '도파민이 필요할 땐 이 노동요를 들어주세요 😎',
     userName: 'deulak',
   },
   ...
 ]

mock 분리 예시(새 파일 추가):

  • 새 파일: src/pages/searchPage/searchResult.mock.ts
export type SearchResultItemData = {
  id: number
  type: 'playlist' | 'user'
  searchResult: string
  userName?: string
  imageUrl?: string
}

export const searchResultMockData: SearchResultItemData[] = [
  { id: 1, type: 'playlist', searchResult: '도파민이 필요할 땐 이 노동요를 들어주세요 😎', userName: 'deulak' },
  { id: 2, type: 'playlist', searchResult: '카페 재즈 모음', userName: 'jazzlover' },
  { id: 3, type: 'user', imageUrl: 'image/url/example.png', searchResult: '김들락' },
  { id: 5, type: 'playlist', searchResult: '새벽감성 인디 플레이리스트', userName: 'deulak' },
]

이 파일 상단 import 예시:

+ import { searchResultMockData } from './searchResult.mock'

26-29: 검색어 공백 방지 및 중복 로직 제거: handleSearch에서 trim/guard 처리

엔터 처리(onKeyDown)와 동일한 네비게이션 로직이 중복되어 있습니다. 한 곳(handleSearch)에서만 처리하고, 공백 검색은 방지하는 편이 안전합니다.

- const handleSearch = (keyword: string) => {
-   setSearchValue(keyword)
-   navigate(`/searchResult?keyword=${encodeURIComponent(keyword)}`)
- }
+ const handleSearch = (keyword: string) => {
+   const trimmed = keyword.trim()
+   if (!trimmed) return
+   setSearchValue(trimmed)
+   navigate(`/searchResult?keyword=${encodeURIComponent(trimmed)}`)
+ }

44-48: Enter 키 처리 DRY: handleSearch 재사용

엔터 시에도 동일한 로직을 handleSearch로 위임하세요.

- onKeyDown={(e) => {
-   if (e.key === 'Enter') {
-     navigate(`/searchResult?keyword=${encodeURIComponent(searchValue)}`)
-   }
- }}
+ onKeyDown={(e) => {
+   if (e.key === 'Enter') {
+     handleSearch(searchValue)
+   }
+ }}

1-1: 미세 최적화: handleSearch를 useCallback으로 메모이즈(선택)

리스트 아이템의 onClick에 핸들러를 전달하므로, useCallback으로 불필요한 재생성을 줄일 수 있습니다.

-import { useEffect, useState } from 'react'
+import { useCallback, useEffect, useState } from 'react'

그리고:

const handleSearch = useCallback((keyword: string) => {
  const trimmed = keyword.trim()
  if (!trimmed) return
  setSearchValue(trimmed)
  navigate(`/searchResult?keyword=${encodeURIComponent(trimmed)}`)
}, [navigate])

82-86: 접근성(A11y): 리스트는 시맨틱 태그 사용 고려

검색 결과 목록은 의미상 리스트입니다. <div> 대신 <ul>/<li> 또는 role="list"/role="listitem" 적용을 고려해 주세요.

예시:

-const ResultList = styled.div`
+const ResultList = styled.ul`
   display: flex;
   flex-direction: column;
   gap: 20px;
 `

렌더링 시:

- <ResultList>
-   {searchResultMockData.map((item) => (
-     <SearchResultItem key={item.id} ... />
-   ))}
- </ResultList>
+ <ResultList role="list">
+   {searchResultMockData.map((item) => (
+     <li key={item.id} role="listitem" style={{ listStyle: 'none' }}>
+       <SearchResultItem {...props} />
+     </li>
+   ))}
+ </ResultList>

104-104: 오타: 'expample' → 'example'

mock 데이터의 URL 철자 오타입니다.

- imageUrl: 'image/url/expample.png',
+ imageUrl: 'image/url/example.png',
src/stories/SearchResultItem.stories.tsx (4)

10-16: argTypes의 type 컨트롤이 스토리 렌더에서 덮어써져 비활성화됨

각 스토리에서 type을 JSX로 고정하여 전달하고 있어, 컨트롤러에서 변경해도 반영되지 않습니다. 컨트롤을 활용하려면 렌더에서는 {...args}만 전달하고, args에서 기본값으로 타입을 지정하세요.


28-32: 컨트롤 친화적 렌더: JSX에서 type 하드코딩 제거

렌더에서 type="playlist"를 제거하고 args에 기본값을 설정하세요. 이렇게 하면 Controls 패널에서 상호작용이 가능합니다.

 export const PlaylistSearchResult: Story = {
-  render: (args) => (
-    <Container>
-      <SearchResultItem {...args} type="playlist" />
-    </Container>
-  ),
+  render: (args) => (
+    <Container>
+      <SearchResultItem {...args} />
+    </Container>
+  ),
   args: {
+    type: 'playlist',
     searchResult: '플레이리스트 #1',
     userName: 'deulak',
   },
 }

40-44: 동일 이슈: User 스토리도 JSX 하드코딩 제거

User 스토리도 동일하게 수정해 주세요.

 export const UserSearchResult: Story = {
-  render: (args) => (
-    <Container>
-      <SearchResultItem {...args} type="user" />
-    </Container>
-  ),
+  render: (args) => (
+    <Container>
+      <SearchResultItem {...args} />
+    </Container>
+  ),
   args: {
+    type: 'user',
     searchResult: '닉네임예시입니다',
   },
 }

45-47: User 스토리 기본 이미지 제공 권장

imageUrl 컨트롤을 비활성화한 상태라면, User 스토리에서 기본 imageUrl을 제공하여 UI가 의도대로 보이도록 하는 것이 좋습니다.

 export const UserSearchResult: Story = {
   ...
   args: {
     type: 'user',
     searchResult: '닉네임예시입니다',
+    imageUrl: 'https://picsum.photos/seed/search-result-user/80/80',
   },
 }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c074414 and 23e6c87.

📒 Files selected for processing (18)
  • src/app/routes/routes.tsx (1 hunks)
  • src/pages/homePage/index.tsx (1 hunks)
  • src/pages/homePage/ui/index.ts (1 hunks)
  • src/pages/searchPage/SearchResultPage.tsx (1 hunks)
  • src/pages/searchPage/index.tsx (1 hunks)
  • src/pages/searchPage/ui/SearchResultItem.tsx (1 hunks)
  • src/pages/searchPage/ui/TrendKeyword.tsx (1 hunks)
  • src/pages/searchPage/ui/index.ts (1 hunks)
  • src/shared/config/navItems.ts (1 hunks)
  • src/shared/ui/CategoryButton.tsx (1 hunks)
  • src/shared/ui/Header.tsx (0 hunks)
  • src/shared/ui/Input.tsx (3 hunks)
  • src/shared/ui/NavBar.tsx (1 hunks)
  • src/shared/ui/index.ts (1 hunks)
  • src/stories/CategoryButton.stories.tsx (1 hunks)
  • src/stories/SearchResultItem.stories.tsx (3 hunks)
  • src/stories/TrendKeyword.stories.ts (1 hunks)
  • src/widgets/playlist/index.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • src/shared/ui/Header.tsx
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit Configuration File

**/*.{ts,tsx,js,jsx}: ## 1. 일반적인 코딩 컨벤션

포맷팅

  • .prettierrc 설정에 따라 포맷팅 확인
  • 들여쓰기: 2칸 스페이스
  • 최대 줄 길이: 100자
  • 세미콜론 사용 안함
  • 따옴표: 작은따옴표 사용
  • 괄호 안 공백: 있음
  • 화살표 함수 괄호: 항상 사용
  • 줄바꿈: LF 사용

네이밍 컨벤션

  • 컴포넌트: PascalCase (예: UserProfile)
  • 유틸리티/훅/변수: camelCase (예: getUserData, useUserInfo)
  • 상수: UPPER_SNAKE_CASE (예: API_BASE_URL)
  • 이미지 파일: kebab-case (예: user-profile-icon.png)

주석 사용

  • 복잡한 로직에만 주석 추가
  • 불필요한 주석 지양 (코드로 설명 가능한 것)
  • TODO/FIXME 형식: // TODO: 설명 - 작성자

가독성

  • 매직 넘버 지양, 의미있는 상수 사용
  • 함수는 하나의 책임만 가지도록 작성 (최대 20줄 권장)
  • 중첩 깊이 최소화 (3단계 이하 권장)

2. React 모범 사례

컴포넌트 작성

  • 최신 React hooks 사용 권장
  • 컴포넌트는 단일 책임 원칙 준수
  • Presentational/Container 컴포넌트 분리
  • 성능 최적화: memo, useCallback, useMemo 적절히 사용
  • 대용량 리스트는 가상화 라이브러리 사용 고려

상태 관리

  • Zustand와 Tanstack Query를 일관되게 사용
  • 상태 구조는 정규화된 형태로 관리
  • 에러 처리: Error Boundary와 try-catch 또는 onError 콜백 활용

3. 스타일링

Styled Components

  • Styled Components 일관되게 사용
  • 스타일드 컴포넌트명은 의미있게 작성
  • 동적 스타일링은 props나 CSS 변수 활용
  • 테마 시스템 활용하여 글로벌 스타일 관리
  • 재사용 가능한 스타일은 mixin이나 확장으로 관리
  • CSS 포맷팅 가독성 유지
  • 사용하지 않는 스타일이나 중복 스타일 제거

4. Vite 및 빌드 최적화

  • 모듈 import 최적화 (tree-shaking 고려)
  • 환경변수는 .env 파일로 관리
  • vite.config.ts에서 빌드 성능 튜닝 (sourcemap 설정, 플러그인 최적화 등)

5. 아키텍처 및 개발 환경

폴더 구조

  • Feature-Sliced Design (FSD) 구조 준수
  • 레이어별 참조 규칙 엄격히 적용

타입스크립트

  • strict 모드 사용
  • 타입 명시적으로 작성 (any 사용 지양)
  • path alias (@/_) 절대 경로 import 사용

Git 훅

  • Husky 설정으로 pre-commit, commit-msg 린팅 확인

6. 기타 가이드라인

  • 충분한 근거와 함께 리뷰 제공
  • 정보 검증 후 답변
  • 간결하고 명확한 응답
  • 필요시 추가 컨텍스트 요청
  • 검증되지 않은 주장 지양
  • 가능한 경우 출처 명시
  • 별도 언급 없으면 JavaScript 기준
  • 한국어로 응답
  • 대부분 브라우저에서 지원하는 ES6+ 기능 활용
  • 코드 리뷰를 통한 유지보수성 향상에 적극 활용

Files:

  • src/pages/searchPage/ui/index.ts
  • src/pages/homePage/ui/index.ts
  • src/shared/ui/index.ts
  • src/pages/searchPage/index.tsx
  • src/stories/TrendKeyword.stories.ts
  • src/widgets/playlist/index.ts
  • src/shared/ui/Input.tsx
  • src/shared/ui/CategoryButton.tsx
  • src/pages/searchPage/ui/TrendKeyword.tsx
  • src/shared/ui/NavBar.tsx
  • src/stories/CategoryButton.stories.tsx
  • src/pages/searchPage/SearchResultPage.tsx
  • src/pages/homePage/index.tsx
  • src/shared/config/navItems.ts
  • src/pages/searchPage/ui/SearchResultItem.tsx
  • src/app/routes/routes.tsx
  • src/stories/SearchResultItem.stories.tsx
🧠 Learnings (1)
📚 Learning: 2025-08-12T18:31:01.433Z
Learnt from: maylh
PR: dnd-side-project/dnd-13th-8-frontend#14
File: src/stories/NavBar.stories.ts:1-1
Timestamp: 2025-08-12T18:31:01.433Z
Learning: Storybook에서 Meta, StoryObj 타입은 'storybook/react'가 아닌 프레임워크별 패키지에서 import해야 함. React-Vite 프로젝트의 경우 'storybook/react-vite'에서 import하는 것이 공식 가이드에 따른 올바른 방식임.

Applied to files:

  • src/stories/TrendKeyword.stories.ts
  • src/stories/CategoryButton.stories.tsx
🧬 Code Graph Analysis (5)
src/pages/searchPage/index.tsx (1)
src/shared/styles/theme.ts (1)
  • theme (3-116)
src/shared/ui/CategoryButton.tsx (1)
src/shared/styles/theme.ts (1)
  • theme (3-116)
src/pages/searchPage/ui/TrendKeyword.tsx (1)
src/shared/styles/theme.ts (1)
  • theme (3-116)
src/shared/ui/NavBar.tsx (2)
src/shared/config/navItems.ts (1)
  • NAV_ITEMS (11-16)
src/shared/styles/theme.ts (1)
  • theme (3-116)
src/pages/searchPage/SearchResultPage.tsx (2)
src/shared/hooks/useSingleSelect.ts (1)
  • useSingleSelect (3-11)
src/shared/ui/ContentHeader.tsx (1)
  • SortType (8-8)
🪛 GitHub Check: Build and Lint
src/pages/searchPage/SearchResultPage.tsx

[warning] 88-88:
Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components

⏰ 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: Run Chromatic
🔇 Additional comments (13)
src/shared/ui/Input.tsx (1)

30-31: onKeyDown 전달 추가 좋습니다

입력값에서 Enter 처리 등을 위해 필요한 이벤트 핸들러가 정상적으로 타입 정의되고 실제 input 엘리먼트에 전달됩니다. 하위 호환성에도 영향이 없습니다. LGTM.

Also applies to: 48-49, 67-67

src/shared/config/navItems.ts (1)

8-9: 다중 경로 지원 설계 타당

NavItem의 paths: string[] 전환과 홈 항목에 검색/검색결과 경로를 포함시키는 접근이 요구사항과 잘 부합합니다. 라우팅/활성화 로직과의 결합(첫 요소를 기본 이동 경로로 사용)도 명확합니다. LGTM.

Also applies to: 12-16

src/pages/searchPage/ui/index.ts (1)

1-2: 배럴 익스포트 추가 적절

UI 컴포넌트의 진입점을 일관되게 제공하여 import 경로를 단순화합니다. LGTM.

src/pages/homePage/ui/index.ts (1)

1-2: 배럴(export) 구성 LGTM

UI 하위 컴포넌트를 배럴로 노출하는 방향이 일관성 있고, import 경로 단축과 트리 셰이킹에도 유리합니다. 변경사항 문제없습니다.

src/shared/ui/index.ts (1)

14-15: UI 배럴에 컴포넌트 추가 LGTM

ScrollCarousel, CategoryButton의 배럴 노출이 일관된 공용 UI API 표면을 유지하도록 도와줍니다. 추가적인 우려 없습니다.

src/widgets/playlist/index.ts (1)

1-2: 플레이리스트 위젯 배럴화 LGTM

위젯 모듈도 배럴로 정리되어 소비측에서 명확한 import가 가능해졌습니다. 문제 없습니다.

src/app/routes/routes.tsx (1)

8-11: 지연 로딩 구성 LGTM

HomePage, SearchPage, SearchResultPage를 코드 스플리팅한 구성이 명확합니다. PrivateRoute와의 조합도 문제 없어 보입니다.

src/stories/TrendKeyword.stories.ts (2)

1-1: Storybook 타입 import가 가이드와 일치합니다

우리 프로젝트 컨벤션(react-vite 프레임워크별 패키지에서 타입 import)에 부합합니다.


1-21: Storybook preview에 ThemeProvider가 이미 적용되어 있습니다
.storybook/preview.tsx에서 전역 데코레이터로 ThemeProvider(theme)GlobalStyle이 설정되어 있어, 해당 스토리에서도 테마가 정상 적용됩니다.

  • .storybook/preview.tsx: 26–30라인
src/stories/CategoryButton.stories.tsx (3)

1-1: Storybook 타입 import 컨벤션 준수

React-Vite 환경에 맞춰 '@storybook/react-vite'에서 타입을 import한 점 좋습니다.


21-31: Small 스토리 컨테이너 폭 지정 적절

small 변형이 width: 100%를 사용하므로 컨테이너 폭(160px)으로 실제 렌더 폭을 제어한 접근이 적절합니다.


1-38: Storybook 전역 테마 적용 확인 완료

.storybook/preview.tsx에서 ThemeProvidertheme 설정이 정상적으로 적용된 것이 확인되었습니다. 추가 조치 불필요합니다.

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

7-9: 배럴(named) 익스포트 전환 LGTM

UI/위젯 배럴을 통한 import로 응집도/가독성이 개선되었습니다. 런타임 영향도 없고 일관성도 좋습니다.

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: 0

♻️ Duplicate comments (1)
src/pages/searchPage/SearchResultPage.tsx (1)

60-68: 타입 단언(as) 제거 및 null 전달 지양

  • as 단언 없이도 동작하도록 mock 데이터에 명시적 타입을 부여하면 안전합니다.
  • React/TS 관례상 선택 props는 undefined로 생략하고 null 전달을 피하는 편이 좋습니다.
-          {searchResultMockData.map((item) => (
+          {searchResultMockData.map((item) => (
             <SearchResultItem
               key={item.id}
-              type={item.type as 'playlist' | 'user'}
+              type={item.type}
               searchResult={item.searchResult}
               imageUrl={item.imageUrl}
-              userName={item.type === 'playlist' ? item.userName : null}
-              onClick={() => handleItemClick(item.type)}
+              {...(item.type === 'playlist' && item.userName ? { userName: item.userName } : {})}
+              onClick={() => handleItemClick(item)}
             />
           ))}
🧹 Nitpick comments (4)
src/pages/searchPage/SearchResultPage.tsx (4)

47-51: 엔터 입력 처리 보완: IME 조합 중 입력·공백 검색·중복 네비게이션 방지

한글/일본어 입력기의 조합 중 Enter 입력을 방지하고, 공백만 입력 시 네비게이션을 막으며, 동일 키워드일 때는 replace로 히스토리 오염을 줄이는 편이 안전합니다.

-        onKeyDown={(e) => {
-          if (e.key === 'Enter') {
-            navigate(`/searchResult?keyword=${encodeURIComponent(searchValue)}`)
-          }
-        }}
+        onKeyDown={(e) => {
+          if (e.key === 'Enter' && !(('isComposing' in e.nativeEvent) && e.nativeEvent.isComposing)) {
+            const next = searchValue.trim()
+            if (!next) return
+            const current = (keyword ?? '').trim()
+            const url = `/searchResult?keyword=${encodeURIComponent(next)}`
+            if (next === current) {
+              navigate(url, { replace: true })
+            } else {
+              navigate(url)
+            }
+          }
+        }}

26-33: 아이템 클릭 시 상세 라우팅 및 타입 안전화

현재 두 타입 모두 '/discover'로 이동합니다. 일반적으로는 각 타입의 상세 페이지로 이동하는 것이 자연스럽습니다. 아이템 전체를 인자로 받아 타입에 따라 분기하세요.

실제 라우팅 스펙(예: /playlist/:id, /user/:userName)을 확인해 주세요.

-  const handleItemClick = (type: string) => {
-    if (type === 'playlist') {
-      navigate('/discover')
-    } else if (type === 'user') {
-      navigate('/discover')
-    }
-  }
+  const handleItemClick = (item: typeof searchResultMockData[number]) => {
+    if (item.type === 'playlist') {
+      // TODO: 실제 라우팅 스펙에 맞게 수정
+      navigate(`/playlist/${item.id}`)
+    } else if (item.type === 'user') {
+      // TODO: 실제 라우팅 스펙에 맞게 수정
+      navigate(`/user/${encodeURIComponent(item.searchResult)}`)
+    }
+  }

91-117: Fast Refresh 경고 해결: 컴포넌트 파일의 named export 제거 + mock 타입 명시

컴포넌트 파일에서 컴포넌트 외 named export를 제공하면 Fast Refresh가 깨질 수 있습니다. 경고에 따라 export를 제거하고, 동시에 mock 데이터에 명시적 타입을 부여해 위의 단언 제거까지 함께 해결하세요.

-export const searchResultMockData = [
+type SearchResult = {
+  id: number
+  type: 'playlist' | 'user'
+  searchResult: string
+  userName?: string
+  imageUrl?: string
+}
+
+const searchResultMockData: SearchResult[] = [
   {
     id: 1,
     type: 'playlist',
     searchResult: '도파민이 필요할 땐 이 노동요를 들어주세요 😎',
     userName: 'deulak',
   },
   {
     id: 2,
     type: 'playlist',
     searchResult: '카페 재즈 모음',
     userName: 'jazzlover',
   },
   {
     id: 3,
     type: 'user',
-    imageUrl: 'image/url/expample.png',
+    imageUrl: 'image/url/example.png',
     searchResult: '김들락',
   },
 
   {
     id: 5,
     type: 'playlist',
     searchResult: '새벽감성 인디 플레이리스트',
     userName: 'deulak',
   },
 ]

참고: mock을 다른 곳에서 재사용할 계획이라면, 별도 파일(예: src/pages/searchPage/_mock/searchResultMockData.ts)로 분리하여 import하는 방식을 권장합니다. 현재 파일에서는 위와 같이 export만 제거해도 Fast Refresh 경고는 사라집니다.


107-107: 문자열 오탈자(nit): 'expample' → 'example'

이미 위 diff에 반영했습니다. 실제 이미지 경로라면 정확한 URL을 확인해 주세요.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 23e6c87 and 3ac7e2c.

📒 Files selected for processing (2)
  • src/pages/searchPage/SearchResultPage.tsx (1 hunks)
  • src/pages/searchPage/ui/TrendKeyword.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/pages/searchPage/ui/TrendKeyword.tsx
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit Configuration File

**/*.{ts,tsx,js,jsx}: ## 1. 일반적인 코딩 컨벤션

포맷팅

  • .prettierrc 설정에 따라 포맷팅 확인
  • 들여쓰기: 2칸 스페이스
  • 최대 줄 길이: 100자
  • 세미콜론 사용 안함
  • 따옴표: 작은따옴표 사용
  • 괄호 안 공백: 있음
  • 화살표 함수 괄호: 항상 사용
  • 줄바꿈: LF 사용

네이밍 컨벤션

  • 컴포넌트: PascalCase (예: UserProfile)
  • 유틸리티/훅/변수: camelCase (예: getUserData, useUserInfo)
  • 상수: UPPER_SNAKE_CASE (예: API_BASE_URL)
  • 이미지 파일: kebab-case (예: user-profile-icon.png)

주석 사용

  • 복잡한 로직에만 주석 추가
  • 불필요한 주석 지양 (코드로 설명 가능한 것)
  • TODO/FIXME 형식: // TODO: 설명 - 작성자

가독성

  • 매직 넘버 지양, 의미있는 상수 사용
  • 함수는 하나의 책임만 가지도록 작성 (최대 20줄 권장)
  • 중첩 깊이 최소화 (3단계 이하 권장)

2. React 모범 사례

컴포넌트 작성

  • 최신 React hooks 사용 권장
  • 컴포넌트는 단일 책임 원칙 준수
  • Presentational/Container 컴포넌트 분리
  • 성능 최적화: memo, useCallback, useMemo 적절히 사용
  • 대용량 리스트는 가상화 라이브러리 사용 고려

상태 관리

  • Zustand와 Tanstack Query를 일관되게 사용
  • 상태 구조는 정규화된 형태로 관리
  • 에러 처리: Error Boundary와 try-catch 또는 onError 콜백 활용

3. 스타일링

Styled Components

  • Styled Components 일관되게 사용
  • 스타일드 컴포넌트명은 의미있게 작성
  • 동적 스타일링은 props나 CSS 변수 활용
  • 테마 시스템 활용하여 글로벌 스타일 관리
  • 재사용 가능한 스타일은 mixin이나 확장으로 관리
  • CSS 포맷팅 가독성 유지
  • 사용하지 않는 스타일이나 중복 스타일 제거

4. Vite 및 빌드 최적화

  • 모듈 import 최적화 (tree-shaking 고려)
  • 환경변수는 .env 파일로 관리
  • vite.config.ts에서 빌드 성능 튜닝 (sourcemap 설정, 플러그인 최적화 등)

5. 아키텍처 및 개발 환경

폴더 구조

  • Feature-Sliced Design (FSD) 구조 준수
  • 레이어별 참조 규칙 엄격히 적용

타입스크립트

  • strict 모드 사용
  • 타입 명시적으로 작성 (any 사용 지양)
  • path alias (@/_) 절대 경로 import 사용

Git 훅

  • Husky 설정으로 pre-commit, commit-msg 린팅 확인

6. 기타 가이드라인

  • 충분한 근거와 함께 리뷰 제공
  • 정보 검증 후 답변
  • 간결하고 명확한 응답
  • 필요시 추가 컨텍스트 요청
  • 검증되지 않은 주장 지양
  • 가능한 경우 출처 명시
  • 별도 언급 없으면 JavaScript 기준
  • 한국어로 응답
  • 대부분 브라우저에서 지원하는 ES6+ 기능 활용
  • 코드 리뷰를 통한 유지보수성 향상에 적극 활용

Files:

  • src/pages/searchPage/SearchResultPage.tsx
🧬 Code Graph Analysis (1)
src/pages/searchPage/SearchResultPage.tsx (2)
src/shared/hooks/useSingleSelect.ts (1)
  • useSingleSelect (3-11)
src/shared/ui/ContentHeader.tsx (1)
  • SortType (8-8)
🪛 GitHub Check: Build and Lint
src/pages/searchPage/SearchResultPage.tsx

[warning] 91-91:
Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components

🔇 Additional comments (1)
src/pages/searchPage/SearchResultPage.tsx (1)

54-58: 정렬 상태 연동 👍

useSingleSelect과 ContentHeader의 currentSort/onSortChange 연동이 깔끔합니다. SortType 범위도 일관적입니다.

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: 0

♻️ Duplicate comments (2)
src/pages/searchPage/SearchResultPage.tsx (2)

26-28: 검색 결과 항목 클릭 시 타입별 라우팅 고려

현재는 항상 /discover/:id로 이동합니다. 보통은 타입(playlist/user)에 따라 각 상세 페이지로 이동하는 동작이 자연스럽습니다. 이전 리뷰에서도 동일 제안이 있었습니다.

예시(프로젝트 라우트 구조에 맞게 경로만 조정해 주세요):

-  const handleItemClick = (id: number) => {
-    navigate(`/discover/${id}`)
-  }
+  const handleItemClick = (id: number, type: 'playlist' | 'user') => {
+    if (type === 'playlist') {
+      navigate(`/playlist/${id}`) // TODO: 실제 라우트에 맞게 변경
+    } else {
+      navigate(`/user/${id}`) // TODO: 실제 라우트에 맞게 변경 (userName 등으로 이동 시 수정)
+    }
+  }
-              onClick={() => handleItemClick(item.id)}
+              onClick={() => handleItemClick(item.id, item.type)}

Also applies to: 55-63


58-61: 타입 단언(as) 제거 및 null 전달 지양

type={item.type as 'playlist' | 'user'} 단언은 타입 안정성을 낮춥니다. 또한 userNamenull을 넘기기보다 prop을 생략(또는 undefined)하는 패턴이 일반적입니다. 이전 리뷰와 동일한 맥락입니다.

권장 수정:

-              type={item.type as 'playlist' | 'user'}
+              type={item.type}
...
-              userName={item.type === 'playlist' ? item.userName : null}
+              {...(item.type === 'playlist' && item.userName ? { userName: item.userName } : {})}

추가로, SearchResultItemuserName prop 타입이 string | undefined(optional)인지 확인 부탁드립니다. 만약 string | null로 선언되어 있다면 아래 둘 중 하나로 정리하는 것을 권장합니다.

  • props에서 userName?: string으로 변경
  • 혹은 호출부에서 항상 string | null 규약을 지키도록 처리

검증 스크립트:

#!/usr/bin/env bash
# SearchResultItem의 props에서 userName 타입 확인
rg -n -C2 --type=tsx '(interface|type)\s+SearchResultItemProps\b|\buserName\??:' src
🧹 Nitpick comments (4)
src/pages/searchPage/SearchResultPage.tsx (4)

42-46: IME(한글 등) 조합 입력 시 엔터 처리 및 빈 검색어 가드 추가 제안

조합 중(한글 입력 등) Enter가 눌리면 의도치 않은 검색이 발생할 수 있습니다. 또한 공백만 있는 검색어는 네비게이션을 생략하는 편이 UX에 좋습니다.

아래처럼 isComposing 체크와 trim() 가드를 추가해 주세요.

-        onKeyDown={(e) => {
-          if (e.key === 'Enter') {
-            navigate(`/searchResult?keyword=${encodeURIComponent(searchValue)}`)
-          }
-        }}
+        onKeyDown={(e) => {
+          if (e.key === 'Enter' && !e.nativeEvent.isComposing) {
+            const value = searchValue.trim()
+            if (value) {
+              navigate(`/searchResult?keyword=${encodeURIComponent(value)}`)
+            }
+          }
+        }}

86-112: 컴포넌트 파일 내 mock 데이터 export 분리 권장 (Fast Refresh 경고 해소, FSD 정렬)

이 파일에서 컴포넌트 외 상수를 export하면 React Fast Refresh가 비활성화될 수 있다는 경고가 있습니다. 또한 FSD 원칙상 mock은 별도 파일로 분리하는 것이 바람직합니다.

적용 방향:

  1. mock 데이터를 별도 파일로 이동(예: src/pages/searchPage/_mock/searchResult.ts)
  2. 이 파일에서는 컴포넌트만 export

이 파일 변경:

+import { searchResultMockData } from './_mock/searchResult'
-export const searchResultMockData = [
-  {
-    id: 1,
-    type: 'playlist',
-    searchResult: '도파민이 필요할 땐 이 노동요를 들어주세요 😎',
-    userName: 'deulak',
-  },
-  {
-    id: 2,
-    type: 'playlist',
-    searchResult: '카페 재즈 모음',
-    userName: 'jazzlover',
-  },
-  {
-    id: 3,
-    type: 'user',
-    imageUrl: 'image/url/expample.png',
-    searchResult: '김들락',
-  },
-  {
-    id: 5,
-    type: 'playlist',
-    searchResult: '새벽감성 인디 플레이리스트',
-    userName: 'deulak',
-  },
-]

새 파일 예시: src/pages/searchPage/_mock/searchResult.ts

export type SearchResultData =
  | {
      id: number
      type: 'playlist'
      searchResult: string
      userName: string
      imageUrl?: string
    }
  | {
      id: number
      type: 'user'
      searchResult: string
      imageUrl?: string
    }

export const searchResultMockData: SearchResultData[] = [
  {
    id: 1,
    type: 'playlist',
    searchResult: '도파민이 필요할 땐 이 노동요를 들어주세요 😎',
    userName: 'deulak',
  },
  {
    id: 2,
    type: 'playlist',
    searchResult: '카페 재즈 모음',
    userName: 'jazzlover',
  },
  {
    id: 3,
    type: 'user',
    imageUrl: 'image/url/example.png', // typo 수정
    searchResult: '김들락',
  },
  {
    id: 5,
    type: 'playlist',
    searchResult: '새벽감성 인디 플레이리스트',
    userName: 'deulak',
  },
]

검증:

  • 빌드/린트 경고(“Fast refresh only works when a file only exports components”)가 사라지는지 확인해 주세요.

49-66: 정렬 UI와 리스트 데이터 정렬 동기화

ContentHeader에서 currentSortonSortChange를 제공하지만 실제 렌더링 리스트는 정렬되지 않습니다. 선택된 정렬값에 따라 렌더링 데이터를 정렬하면 UX 일관성이 좋아집니다.

적용 예시:

  • 아래 코드(컴포넌트 상단) 추가:
// 추가: useMemo 임포트 필요
// import { useMemo } from 'react'

const sortedData = useMemo(() => {
  // 실제 정렬 기준에 맞게 구현 (예시는 latest일 때 id 내림차순)
  if (selected === 'latest') {
    return [...searchResultMockData].sort((a, b) => b.id - a.id)
  }
  return searchResultMockData
}, [selected])
  • 리스트 렌더링 교체:
-          {searchResultMockData.map((item) => (
+          {sortedData.map((item) => (

102-102: 오탈자: 'expample' → 'example'

목 이미지 URL의 철자가 잘못되었습니다. mock이더라도 혼동 방지를 위해 수정 권장합니다.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3ac7e2c and dfb85da.

📒 Files selected for processing (1)
  • src/pages/searchPage/SearchResultPage.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit Configuration File

**/*.{ts,tsx,js,jsx}: ## 1. 일반적인 코딩 컨벤션

포맷팅

  • .prettierrc 설정에 따라 포맷팅 확인
  • 들여쓰기: 2칸 스페이스
  • 최대 줄 길이: 100자
  • 세미콜론 사용 안함
  • 따옴표: 작은따옴표 사용
  • 괄호 안 공백: 있음
  • 화살표 함수 괄호: 항상 사용
  • 줄바꿈: LF 사용

네이밍 컨벤션

  • 컴포넌트: PascalCase (예: UserProfile)
  • 유틸리티/훅/변수: camelCase (예: getUserData, useUserInfo)
  • 상수: UPPER_SNAKE_CASE (예: API_BASE_URL)
  • 이미지 파일: kebab-case (예: user-profile-icon.png)

주석 사용

  • 복잡한 로직에만 주석 추가
  • 불필요한 주석 지양 (코드로 설명 가능한 것)
  • TODO/FIXME 형식: // TODO: 설명 - 작성자

가독성

  • 매직 넘버 지양, 의미있는 상수 사용
  • 함수는 하나의 책임만 가지도록 작성 (최대 20줄 권장)
  • 중첩 깊이 최소화 (3단계 이하 권장)

2. React 모범 사례

컴포넌트 작성

  • 최신 React hooks 사용 권장
  • 컴포넌트는 단일 책임 원칙 준수
  • Presentational/Container 컴포넌트 분리
  • 성능 최적화: memo, useCallback, useMemo 적절히 사용
  • 대용량 리스트는 가상화 라이브러리 사용 고려

상태 관리

  • Zustand와 Tanstack Query를 일관되게 사용
  • 상태 구조는 정규화된 형태로 관리
  • 에러 처리: Error Boundary와 try-catch 또는 onError 콜백 활용

3. 스타일링

Styled Components

  • Styled Components 일관되게 사용
  • 스타일드 컴포넌트명은 의미있게 작성
  • 동적 스타일링은 props나 CSS 변수 활용
  • 테마 시스템 활용하여 글로벌 스타일 관리
  • 재사용 가능한 스타일은 mixin이나 확장으로 관리
  • CSS 포맷팅 가독성 유지
  • 사용하지 않는 스타일이나 중복 스타일 제거

4. Vite 및 빌드 최적화

  • 모듈 import 최적화 (tree-shaking 고려)
  • 환경변수는 .env 파일로 관리
  • vite.config.ts에서 빌드 성능 튜닝 (sourcemap 설정, 플러그인 최적화 등)

5. 아키텍처 및 개발 환경

폴더 구조

  • Feature-Sliced Design (FSD) 구조 준수
  • 레이어별 참조 규칙 엄격히 적용

타입스크립트

  • strict 모드 사용
  • 타입 명시적으로 작성 (any 사용 지양)
  • path alias (@/_) 절대 경로 import 사용

Git 훅

  • Husky 설정으로 pre-commit, commit-msg 린팅 확인

6. 기타 가이드라인

  • 충분한 근거와 함께 리뷰 제공
  • 정보 검증 후 답변
  • 간결하고 명확한 응답
  • 필요시 추가 컨텍스트 요청
  • 검증되지 않은 주장 지양
  • 가능한 경우 출처 명시
  • 별도 언급 없으면 JavaScript 기준
  • 한국어로 응답
  • 대부분 브라우저에서 지원하는 ES6+ 기능 활용
  • 코드 리뷰를 통한 유지보수성 향상에 적극 활용

Files:

  • src/pages/searchPage/SearchResultPage.tsx
🧬 Code Graph Analysis (1)
src/pages/searchPage/SearchResultPage.tsx (2)
src/shared/hooks/useSingleSelect.ts (1)
  • useSingleSelect (3-11)
src/shared/ui/ContentHeader.tsx (1)
  • SortType (8-8)
🪛 GitHub Check: Build and Lint
src/pages/searchPage/SearchResultPage.tsx

[warning] 86-86:
Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components

🔇 Additional comments (1)
src/pages/searchPage/SearchResultPage.tsx (1)

19-25: URL 쿼리 파라미터 ↔ 입력값 동기화 처리 좋아요

keyword 변경 시 searchValue를 동기화하는 패턴은 뒤로 가기/URL 공유 등 다양한 진입 케이스에서 일관된 UX를 보장합니다. 유지해주세요.

Copy link
Member

@hansololiviakim hansololiviakim left a comment

Choose a reason for hiding this comment

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

멋 져 요 👍
vercel 제 계정이랑 시크릿모드 둘 다 들어가봤는데 잘 들어가져요 ! 감사합니다 ^_______^
원래 오늘 컨퍼런스 스태프 킥오프 모임 가야 하는데 흑흑.... 오늘 한 5~6시까지 달려보고 진행상황 다시 공유드릴게요! 감사합니다ㅎㅎ

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: 0

♻️ Duplicate comments (3)
src/pages/searchPage/index.tsx (1)

35-39: IME(한글 등) 조합 중 Enter 방지 로직 추가 제안

Enter 처리 전에 IME 조합 여부를 확인하지 않아 한글 입력 중 조기 네비게이션 가능성이 있습니다. handleSearch 내부에서 trim 빈값은 걸러지지만, 조합 완료 전 상태에서도 이동 트리거가 될 수 있어 UX 이슈입니다. 아래처럼 보완을 권장합니다.

-        onKeyDown={(e) => {
-          if (e.key === 'Enter') {
-            handleSearch()
-          }
-        }}
+        onKeyDown={(e) => {
+          if (e.key !== 'Enter') return
+          // IME 조합 중 Enter 방지 (한글 등)
+          // @ts-expect-error: vendor typing for isComposing
+          if (e.nativeEvent?.isComposing) return
+          handleSearch()
+        }}
src/shared/ui/CategoryButton.tsx (2)

3-10: 기본 버튼 type 지정 및 표준 버튼 props 전달로 재사용성 향상

폼 내 우발적 submit 방지와 재사용성 강화를 위해 기본 type='button' 지정 + ButtonHTMLAttributes 확장을 권장합니다.

 import styled, { css } from 'styled-components'
+import type { ButtonHTMLAttributes } from 'react'
 
-interface CategoryButtonProps {
+interface CategoryButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
   text: string
   size: 'small' | 'large'
 }
 
-const CategoryButton = ({ text, size }: CategoryButtonProps) => {
-  return <StyledButton $size={size}>{text}</StyledButton>
+const CategoryButton = ({ text, size, type = 'button', ...rest }: CategoryButtonProps) => {
+  return (
+    <StyledButton $size={size} type={type} {...rest}>
+      {text}
+    </StyledButton>
+  )
 }

19-27: 스타일 안전성/접근성 개선: 객체 기반 css 제거, 커서/포커스 스타일 추가

css(sizes[$size])는 styled-components 버전에 따라 CSSObject 처리 호환성 이슈가 있을 수 있습니다. 명시적 속성 매핑으로 교체하고, cursor/outline 등 인터랙션 스타일을 보완하면 좋습니다.

 const StyledButton = styled.button<{ $size: 'small' | 'large' }>`
   display: flex;
   align-items: flex-start;
   justify-content: flex-start;
   background-color: ${({ theme }) => theme.COLOR['gray-700']};
   color: ${({ theme }) => theme.COLOR['gray-10']};
 
-  ${({ $size }) => css(sizes[$size])}
+  cursor: pointer;
+  border: none;
+  width: ${({ $size }) => sizes[$size].width};
+  height: ${({ $size }) => sizes[$size].height};
+  border-radius: ${({ $size }) => sizes[$size].borderRadius};
+  padding: ${({ $size }) => sizes[$size].padding};
+
+  &:focus-visible {
+    outline: 2px solid ${({ theme }) => theme.COLOR['primary-normal']};
+    outline-offset: 2px;
+  }
 `
🧹 Nitpick comments (1)
src/pages/searchPage/index.tsx (1)

25-26: 히스토리 미존재 시 뒤로가기 대안 고려

navigate(-1)는 브라우저 히스토리가 없을 때 무시됩니다. 홈 등 안전한 경로로의 폴백을 고려해 주세요. 예: onClick={() => (window.history.length > 1 ? navigate(-1) : navigate('/'))}

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between dfb85da and 92e9921.

📒 Files selected for processing (2)
  • src/pages/searchPage/index.tsx (1 hunks)
  • src/shared/ui/CategoryButton.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit Configuration File

**/*.{ts,tsx,js,jsx}: ## 1. 일반적인 코딩 컨벤션

포맷팅

  • .prettierrc 설정에 따라 포맷팅 확인
  • 들여쓰기: 2칸 스페이스
  • 최대 줄 길이: 100자
  • 세미콜론 사용 안함
  • 따옴표: 작은따옴표 사용
  • 괄호 안 공백: 있음
  • 화살표 함수 괄호: 항상 사용
  • 줄바꿈: LF 사용

네이밍 컨벤션

  • 컴포넌트: PascalCase (예: UserProfile)
  • 유틸리티/훅/변수: camelCase (예: getUserData, useUserInfo)
  • 상수: UPPER_SNAKE_CASE (예: API_BASE_URL)
  • 이미지 파일: kebab-case (예: user-profile-icon.png)

주석 사용

  • 복잡한 로직에만 주석 추가
  • 불필요한 주석 지양 (코드로 설명 가능한 것)
  • TODO/FIXME 형식: // TODO: 설명 - 작성자

가독성

  • 매직 넘버 지양, 의미있는 상수 사용
  • 함수는 하나의 책임만 가지도록 작성 (최대 20줄 권장)
  • 중첩 깊이 최소화 (3단계 이하 권장)

2. React 모범 사례

컴포넌트 작성

  • 최신 React hooks 사용 권장
  • 컴포넌트는 단일 책임 원칙 준수
  • Presentational/Container 컴포넌트 분리
  • 성능 최적화: memo, useCallback, useMemo 적절히 사용
  • 대용량 리스트는 가상화 라이브러리 사용 고려

상태 관리

  • Zustand와 Tanstack Query를 일관되게 사용
  • 상태 구조는 정규화된 형태로 관리
  • 에러 처리: Error Boundary와 try-catch 또는 onError 콜백 활용

3. 스타일링

Styled Components

  • Styled Components 일관되게 사용
  • 스타일드 컴포넌트명은 의미있게 작성
  • 동적 스타일링은 props나 CSS 변수 활용
  • 테마 시스템 활용하여 글로벌 스타일 관리
  • 재사용 가능한 스타일은 mixin이나 확장으로 관리
  • CSS 포맷팅 가독성 유지
  • 사용하지 않는 스타일이나 중복 스타일 제거

4. Vite 및 빌드 최적화

  • 모듈 import 최적화 (tree-shaking 고려)
  • 환경변수는 .env 파일로 관리
  • vite.config.ts에서 빌드 성능 튜닝 (sourcemap 설정, 플러그인 최적화 등)

5. 아키텍처 및 개발 환경

폴더 구조

  • Feature-Sliced Design (FSD) 구조 준수
  • 레이어별 참조 규칙 엄격히 적용

타입스크립트

  • strict 모드 사용
  • 타입 명시적으로 작성 (any 사용 지양)
  • path alias (@/_) 절대 경로 import 사용

Git 훅

  • Husky 설정으로 pre-commit, commit-msg 린팅 확인

6. 기타 가이드라인

  • 충분한 근거와 함께 리뷰 제공
  • 정보 검증 후 답변
  • 간결하고 명확한 응답
  • 필요시 추가 컨텍스트 요청
  • 검증되지 않은 주장 지양
  • 가능한 경우 출처 명시
  • 별도 언급 없으면 JavaScript 기준
  • 한국어로 응답
  • 대부분 브라우저에서 지원하는 ES6+ 기능 활용
  • 코드 리뷰를 통한 유지보수성 향상에 적극 활용

Files:

  • src/shared/ui/CategoryButton.tsx
  • src/pages/searchPage/index.tsx
🧬 Code Graph Analysis (2)
src/shared/ui/CategoryButton.tsx (1)
src/shared/styles/theme.ts (1)
  • theme (3-116)
src/pages/searchPage/index.tsx (1)
src/shared/styles/theme.ts (1)
  • theme (3-116)
🔇 Additional comments (2)
src/pages/searchPage/index.tsx (1)

14-20: 검색 유틸 함수 분리 및 URLSearchParams 사용 적절

trim 체크 + createSearchParams로 쿼리 생성한 점 좋습니다. 재사용/테스트 용이성도 좋아졌습니다.

src/shared/ui/CategoryButton.tsx (1)

19-19: transient prop($size) 사용 적절

DOM 속성 누수를 막기 위해 $size 사용한 점 좋습니다.

@maylh maylh merged commit 9dcfe66 into develop Aug 17, 2025
5 checks passed
@maylh maylh deleted the feat/#37/search-ui branch August 17, 2025 09:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

HIGH 빠르게 처리해야 하는 높은 우선순위

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] 검색 페이지 UI 구현

2 participants