refactor(ui): SearchField 공통 스타일 분리 및 종속성 제거#644
refactor(ui): SearchField 공통 스타일 분리 및 종속성 제거#644seongwon030 wants to merge 1 commit intodevelop-fefrom
Conversation
- SearchField.styles.ts 도입으로 페이지 전용 스타일 의존 제거 - SearchField가 공통 스타일을 직접 참조하도록 변경 - 검색 입력 UI의 재사용성/일관성 향상
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
|
Warning
|
| Cohort / File(s) | Change Summary |
|---|---|
Header import 업데이트frontend/src/components/common/Header/Header.tsx |
SearchBox import 경로를 @/pages/MainPage/components/SearchBox/SearchBox로 변경. 사용 방식 동일. |
구(舊) 공통 SearchBox 제거frontend/src/components/common/SearchBox/SearchBox.tsx |
기존 공통 SearchBox 컴포넌트 파일 삭제(기능: 컨텍스트 기반 검색, 라우팅, 믹스패널 트래킹 포함). |
신규 공통 SearchField 추가frontend/src/components/common/SearchField/SearchField.tsx |
재사용 가능한 검색 입력/버튼 컴포넌트 추가. 제어형 입력, onSubmit, autoBlur, 접근성 ARIA 라벨 지원. 기본 props 제공. |
메인 페이지 SearchBox 신규 구현frontend/src/pages/MainPage/components/SearchBox/SearchBox.tsx |
SearchField를 사용해 검색 흐름 구성. 키워드 설정, 카테고리 초기화, 검색 상태 플래그, 홈 경로로의 네비게이션, 믹스패널 이벤트 트래킹 수행. |
Sequence Diagram(s)
sequenceDiagram
actor User
participant SearchField as UI: SearchField
participant SearchBox as MainPage SearchBox
participant Router as React Router
participant SearchCtx as SearchContext
participant CategoryCtx as CategoryContext
participant Mixpanel as Mixpanel
User->>SearchField: 입력 및 제출
SearchField->>SearchBox: onSubmit()
SearchBox->>Router: 필요 시 "/"로 이동
SearchBox->>SearchCtx: setKeyword(inputValue), setIsSearching(true)
SearchBox->>CategoryCtx: setSelectedCategory('all')
SearchBox->>Mixpanel: track("Search Executed", { value, path })
Estimated code review effort
🎯 2 (Simple) | ⏱️ ~8 minutes
Assessment against linked issues
| Objective | Addressed | Explanation |
|---|---|---|
| 공통 검색 필드 컴포넌트 도입 (MOA-152) | ✅ | |
| 기존 공통 SearchBox 대체/이관 (MOA-152) | ✅ | |
| 메인 페이지 검색 흐름 유지 및 연동 (MOA-152) | ✅ | |
| 헤더에서 신규 구조 반영 (MOA-152) | ✅ |
Possibly related PRs
- [feature] 검색 시 전체동아리 기준으로 검색된다 #616: SearchBox의 컨텍스트 연동 확장과 본 PR의 이동/재구성이 동일 영역을 다룹니다.
- [feature] 믹스패널 track 2차 추가 및 기타 작업 #283: 기존 SearchBox의 트래킹 로직 변경과 본 PR의 트래킹 유지 구현이 연관됩니다.
- [fix] styled-components unknown prop warning 제거 #377: SearchBox 포커스 스타일 처리($isFocused)와 본 PR의 SearchField 포커스 상태가 유사 범위를 다룹니다.
Suggested labels
🔨 Refactor, 💻 FE
Suggested reviewers
- Zepelown
- oesnuj
- lepitaaar
✨ Finishing Touches
- 📝 Generate Docstrings
🧪 Generate unit tests
- Create PR with unit tests
- Post copyable unit tests in a comment
- Commit unit tests in branch
refactor/#643-common-searchField-MOA-152
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.
🪧 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
@coderabbitaiin a new review comment at the desired location with your query. - PR comments: Tag
@coderabbitaiin 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 ignoreanywhere in the PR description to prevent this PR from being reviewed. - Add
@coderabbitai summaryto generate the high-level summary at a specific location in the PR description. - Add
@coderabbitaianywhere 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.
There was a problem hiding this comment.
Actionable comments posted: 1
🔭 Outside diff range comments (1)
frontend/src/components/common/Header/Header.tsx (1)
59-76: 모바일 헤더에서 Admin 경로에서도 SearchBox가 항상 렌더링 — Provider 미존재 시 런타임 크래시 가능useSearch/useCategory는 Provider 외부 사용 시 예외를 던집니다(참고: frontend/src/context/SearchContext.tsx, CategoryContext.tsx). DesktopHeader는 isAdminPage에서 SearchBox를 숨기지만, MobileHeader는 조건 없이 SearchBox를 렌더링합니다. Admin 경로(예: /admin)에서 모바일 화면일 때 Provider로 래핑되어 있지 않으면 크래시가 발생할 수 있습니다.
아래와 같이 MobileHeader에도 isAdminPage를 전달하고 조건부 렌더링으로 정렬해 주세요.
다음 diff를 적용하세요:
interface MobileHeaderProps { + isAdminPage: boolean; handleHomeClick: (device: 'mobile' | 'desktop') => void; handleMenuClick: () => void; } -const MobileHeader = ({ - handleHomeClick, - handleMenuClick, -}: MobileHeaderProps) => ( +const MobileHeader = ({ + isAdminPage, + handleHomeClick, + handleMenuClick, +}: MobileHeaderProps) => ( <Styled.MobileHeaderContainer> <Styled.MobileHeaderWrapper> <Styled.MobileMainIcon> <img src={MobileMainIcon} alt='홈 버튼' onClick={() => handleHomeClick('mobile')} /> </Styled.MobileMainIcon> - <SearchBox /> + {!isAdminPage && <SearchBox />} <Styled.MobileMenu aria-label='메뉴 버튼'> <img src={MenuBar} alt='메뉴 버튼' onClick={handleMenuClick} /> </Styled.MobileMenu> </Styled.MobileHeaderWrapper> </Styled.MobileHeaderContainer> );그리고 Header에서 prop을 전달합니다:
return isMobile ? ( <> <MobileHeader + isAdminPage={isAdminPage} handleHomeClick={handleHomeClick} handleMenuClick={openMenu} />
♻️ Duplicate comments (1)
frontend/src/pages/MainPage/components/SearchBox/SearchBox.tsx (1)
8-13: Provider 의존성 런타임 예외 가능성 재확인useSearch/useCategory는 Provider 외부 사용 시 예외를 던집니다. Header.tsx에서 모바일 admin 경로 케이스를 방지하도록 수정 제안 드렸습니다(중복 코멘트).
아래 스크립트로 Provider/Header 배치를 대략적으로 확인할 수 있습니다. 결과에서 App/레이아웃 루트에 SearchProvider/CategoryProvider가 존재하고 Header가 그 하위에 있는지 확인해 주세요.
#!/bin/bash # Search Provider 선언 및 사용 위치 확인 rg -n "SearchProvider|CategoryProvider" -A 3 -B 3 # Header 사용 위치 살펴보기 rg -n "<Header\b" -A 5 -B 5 # SearchBox가 Admin 라우트에서 렌더링될 수 있는지 탐색(모바일 레이아웃 등) rg -n "path=['\"]?/admin" -A 10 -B 10
🧹 Nitpick comments (4)
frontend/src/pages/MainPage/components/SearchBox/SearchBox.tsx (2)
20-30: 네비게이션 직후 window.location.pathname 사용은 타이밍 이슈 가능 — 이벤트 필드 명확화(from/to) 및 상수화 권장navigate('/') 호출 직후 window.location.pathname을 읽으면 비동기 타이밍에 따라 이전 경로가 기록될 수 있습니다. 검색 발생 위치와 이동 목적지를 명확히 기록하도록 이벤트 속성을 from/to로 구분하고, 매직 스트링들을 상수로 추출하세요.
아래 diff를 참고하세요:
+// Constants +const DEFAULT_CATEGORY = 'all'; +const SEARCH_EXECUTED_EVENT = 'Search Executed'; + const SearchBox = () => { const { setKeyword, inputValue, setInputValue, setIsSearching } = useSearch(); const { setSelectedCategory } = useCategory(); const trackEvent = useMixpanelTrack(); const navigate = useNavigate(); const location = useLocation(); const redirectToHome = () => { if (location.pathname !== '/') { navigate('/'); } }; - const handleSearch = () => { + const handleSearch = () => { + const trimmed = inputValue.trim(); + if (!trimmed) { + // 비어있는 검색어는 무시하거나 별도 이벤트로 로깅하는 정책을 적용할 수 있습니다. + return; + } redirectToHome(); - setKeyword(inputValue); - setSelectedCategory('all'); + setKeyword(trimmed); + setSelectedCategory(DEFAULT_CATEGORY); setIsSearching(true); - trackEvent('Search Executed', { - inputValue: inputValue, - page: window.location.pathname, - }); + trackEvent(SEARCH_EXECUTED_EVENT, { + inputValue: trimmed, + from: location.pathname, + to: '/', + }); };정책적으로 빈 검색어 허용이 필요하다면 trimmed 체크 부분은 제거하셔도 됩니다.
23-24: 카테고리 기본값 'all' 매직 스트링 사용 — 도메인 상수로 추출하세요'ALL'/'전체'와 같은 도메인 상수는 전역적으로 일관되어야 합니다. CategoryContext에서 사용하는 상수(예: DEFAULT_CATEGORY 또는 Category.All)가 있다면 그것을 사용하고, 없다면 본 파일 상단에 상수로 선언하세요. 상기 diff에 DEFAULT_CATEGORY 예시 포함했습니다.
CategoryContext에서 허용하는 값이 정확히 'all'인지 확인해 주세요. 필요 시 enum 또는 유니언 타입으로 한정하는 것을 권장합니다.
frontend/src/components/common/SearchField/SearchField.tsx (2)
35-41: 검색 입력 필드의 시맨틱과 모바일 UX 개선type을 'search'로 변경하고 enterKeyHint/autoComplete를 추가하면 모바일 키보드와 브라우저 기본 동작이 더 적합해집니다.
- type='text' + type='search' + enterKeyHint='search' + autoComplete='on'필요 시 name='q' 등 검색 파라미터 명도 지정하면 폼 통합 시 재사용성이 좋아집니다.
44-49: 아이콘의 스크린리더 중복 낭독 방지버튼 자체에 aria-label='검색'이 있으므로, 내부 아이콘 이미지는 장식용으로 처리하세요. alt를 빈 문자열로 하고 aria-hidden을 지정하면 접근성이 향상됩니다.
- <Styled.SearchButton + <Styled.SearchButton type='submit' $isFocused={isFocused} aria-label='검색' > - <img src={searchButtonIcon} alt='Search Button' /> + <img src={searchButtonIcon} alt='' aria-hidden='true' /> </Styled.SearchButton>
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
frontend/src/components/common/Header/Header.tsx(1 hunks)frontend/src/components/common/SearchBox/SearchBox.tsx(0 hunks)frontend/src/components/common/SearchField/SearchField.tsx(1 hunks)frontend/src/pages/MainPage/components/SearchBox/SearchBox.tsx(1 hunks)
💤 Files with no reviewable changes (1)
- frontend/src/components/common/SearchBox/SearchBox.tsx
🧰 Additional context used
📓 Path-based instructions (2)
frontend/**/*.{ts,tsx}
📄 CodeRabbit Inference Engine (frontend/.cursorrules)
frontend/**/*.{ts,tsx}: Replace magic numbers with named constants for clarity.
Replace complex or nested ternary operators with if/else statements or IIFEs for readability.
Assign complex boolean conditions to named variables.
Use consistent return types for similar functions and hooks.
Avoid hidden side effects; functions should only perform actions implied by their signature (Single Responsibility Principle).
Use unique, descriptive names for custom wrappers and functions to avoid ambiguity.
Define constants near related logic or ensure names link them clearly.
Files:
frontend/src/pages/MainPage/components/SearchBox/SearchBox.tsxfrontend/src/components/common/Header/Header.tsxfrontend/src/components/common/SearchField/SearchField.tsx
frontend/**/*.tsx
📄 CodeRabbit Inference Engine (frontend/.cursorrules)
frontend/**/*.tsx: Abstract complex logic/interactions into dedicated components or higher-order components (HOCs).
Separate significantly different conditional UI/logic into distinct components.
Colocate simple, localized logic or use inline definitions to reduce context switching.
Choose field-level or form-level cohesion based on form requirements.
Break down broad state management into smaller, focused hooks or contexts.
Use component composition instead of props drilling.
Files:
frontend/src/pages/MainPage/components/SearchBox/SearchBox.tsxfrontend/src/components/common/Header/Header.tsxfrontend/src/components/common/SearchField/SearchField.tsx
🧬 Code Graph Analysis (1)
frontend/src/pages/MainPage/components/SearchBox/SearchBox.tsx (2)
frontend/src/context/SearchContext.tsx (1)
useSearch(39-45)frontend/src/context/CategoryContext.tsx (1)
useCategory(11-18)
🔇 Additional comments (2)
frontend/src/components/common/Header/Header.tsx (2)
3-3: 경로 변경 적합 — 페이지 전용 SearchBox로의 의존 전환 LGTMHeader가 공통 SearchBox에서 MainPage 전용 SearchBox로 경로를 전환했습니다. PR 목표(공통 스타일 분리 및 페이지 전용 의존 제거)와 부합합니다. 런타임 영향 없음.
106-113: isAdminPage 계산 로직과 MobileHeader/ DesktopHeader 간 표시 기준 일관성 확인 요청현재 isAdminPage는 location.pathname.startsWith('/admin')으로 판단합니다. 경로가 '/admin' 외 하위 경로(예: '/admin/users')까지 정확히 커버하는 점은 좋습니다. 위 코멘트대로 MobileHeader에도 동일 조건을 적용하여 UX 및 오류 방지 일관성을 유지하세요.
원하시면 Provider 트리(App 루트 등)에서 Header가 항상 SearchProvider/CategoryProvider 하위에 있는지 추가로 검증하는 스크립트를 제공할 수 있습니다.
Also applies to: 118-137
| return ( | ||
| <Styled.SearchBoxContainer $isFocused={isFocused} onSubmit={handleSubmit}> | ||
| <Styled.SearchInputStyles |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
컨테이너 요소가 form인지 확인 필요 — onSubmit 동작/엔터 제출 보장 위해 as='form'과 role='search' 지정 권장
Styled.SearchBoxContainer가 styled.form이 아니라면 onSubmit이 동작하지 않습니다. 안전하게 as='form'과 role='search'를 명시하세요.
- return (
- <Styled.SearchBoxContainer $isFocused={isFocused} onSubmit={handleSubmit}>
+ return (
+ <Styled.SearchBoxContainer
+ as='form'
+ role='search'
+ $isFocused={isFocused}
+ onSubmit={handleSubmit}
+ >또는 SearchField.styles.ts에서 SearchBoxContainer를 styled.form으로 선언되어 있는지 확인해 주세요.
🏁 Script executed:
#!/bin/bash
# Find the style file and inspect the definition of SearchBoxContainer
STYLE_FILE=$(fd SearchField.styles.ts)
echo "Found style file: $STYLE_FILE"
rg -n "SearchBoxContainer" "$STYLE_FILE"Length of output: 446
Search 역할 명시를 위해 role="search" 추가 필요
현재 SearchBoxContainer는 styled.form으로 정의되어 있어 onSubmit은 정상적으로 동작합니다. 그러나 접근성(ARIA) 향상을 위해 role="search" 속성을 추가하세요.
대상 파일: frontend/src/components/common/SearchField/SearchField.tsx
라인: 31–33
- return (
- <Styled.SearchBoxContainer $isFocused={isFocused} onSubmit={handleSubmit}>
+ return (
+ <Styled.SearchBoxContainer
+ role="search"
+ $isFocused={isFocused}
+ onSubmit={handleSubmit}
+ >📝 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.
| return ( | |
| <Styled.SearchBoxContainer $isFocused={isFocused} onSubmit={handleSubmit}> | |
| <Styled.SearchInputStyles | |
| return ( | |
| <Styled.SearchBoxContainer | |
| role="search" | |
| $isFocused={isFocused} | |
| onSubmit={handleSubmit} | |
| > | |
| <Styled.SearchInputStyles | |
| /* ...rest of props... */ |
🤖 Prompt for AI Agents
In frontend/src/components/common/SearchField/SearchField.tsx around lines 31 to
33, the SearchBoxContainer (a styled.form) needs the ARIA role added; update the
JSX to include role="search" on the Styled.SearchBoxContainer element (ensure
the prop passes through to the underlying form element and adjust any TS props
types if necessary so role is accepted).
#️⃣연관된 이슈
📝작업 내용
중점적으로 리뷰받고 싶은 부분(선택)
논의하고 싶은 부분(선택)
🫡 참고사항
Summary by CodeRabbit