-
Notifications
You must be signed in to change notification settings - Fork 1
Feat/23 공통 Dropdown 컴포넌트 구현 #60
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Walkthrough드롭다운 공통 컴포넌트와 관련 타입, 카테고리 상수, 아이콘 컴포넌트(Chevron, Check) 등이 새롭게 추가 및 리팩토링되었습니다. 드롭다운 예제 페이지와 아이콘 갤러리 페이지도 구현되어, 새로운 컴포넌트들의 사용 예시가 포함되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Dropdown
participant ParentComponent
User->>Dropdown: 버튼 클릭
Dropdown->>Dropdown: open 상태 토글
User->>Dropdown: 옵션 클릭
Dropdown->>ParentComponent: onChange(newValue) 콜백 호출
ParentComponent->>Dropdown: value prop 업데이트
Dropdown->>Dropdown: 선택값, 포커스 등 상태 갱신
Estimated code review effort
총평: 3 (30~60분) — 드롭다운 컴포넌트가 가장 복잡하며, 나머지 파일들은 중간 이하 난이도입니다. Possibly related PRs
Suggested reviewers
Poem
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
src/components/Dropdown.tsxOops! Something went wrong! :( ESLint: 9.30.1 ESLint couldn't find the plugin "eslint-plugin-react-hooks". (The package "eslint-plugin-react-hooks" was not found when loaded as a Node module from the directory "".) It's likely that the plugin isn't installed correctly. Try reinstalling by running the following: The plugin "eslint-plugin-react-hooks" was referenced from the config file in " » eslint-config-next/core-web-vitals » /node_modules/.pnpm/[email protected][email protected][email protected][email protected]/node_modules/eslint-config-next/index.js". If you still can't figure out the problem, please see https://eslint.org/docs/latest/use/troubleshooting. 📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
✨ Finishing Touches
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this 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
🧹 Nitpick comments (6)
public/assets/svg/chevron.tsx (1)
4-22: 접근성과 재사용성을 개선하세요.SVG 아이콘에 접근성 속성이 누락되어 있고, 색상이 하드코딩되어 재사용성이 제한됩니다.
다음 개선사항을 적용하세요:
<svg xmlns='http://www.w3.org/2000/svg' width={size} height={size} fill='none' viewBox='0 0 24 24' + role='img' + aria-hidden='true' style={{ transform: direction === 'up' ? 'rotate(180deg)' : 'none', ...props.style, }} > <path - stroke='#1B1B1B' + stroke='currentColor' strokeLinecap='round' strokeLinejoin='round' strokeWidth='1.5' d='M5.25 9 12 15.75 18.75 9' /> </svg>src/constants/categories.ts (1)
9-14: 향후 국제화를 위한 구조 개선을 고려하세요.현재 한국어 문자열을 직접 사용하고 있는데, 향후 다국어 지원 시 키-값 구조로 변경이 필요할 수 있습니다.
향후 확장성을 위해 다음과 같은 구조도 고려해보세요:
export const ACTIVITY_CATEGORIES = { CULTURE_ARTS: '문화 예술', FOOD_BEVERAGE: '식음료', SPORTS: '스포츠', TOUR: '투어', TOURISM: '관광', WELLBEING: '웰빙', } as const; export type ActivityCategoryKey = keyof typeof ACTIVITY_CATEGORIES; export type ActivityCategory = (typeof ACTIVITY_CATEGORIES)[ActivityCategoryKey];public/assets/svg/check.tsx (1)
15-55: 코드 중복을 제거하고 접근성을 개선하세요.두 개의 SVG가 많은 공통 속성을 가지고 있어 중복이 발생하고 있습니다. 또한 접근성 속성이 누락되었습니다.
다음과 같이 리팩토링하여 중복을 제거하세요:
const CheckIcon = ({ size = 24, showBackground = true, ...props }: CheckIconProps) => { + const commonSvgProps = { + xmlns: 'http://www.w3.org/2000/svg', + width: size, + height: size, + fill: 'none', + viewBox: '0 0 24 24', + role: 'img', + 'aria-hidden': 'true', + ...props, + }; + + const pathProps = { + strokeLinecap: 'round' as const, + strokeLinejoin: 'round' as const, + strokeWidth: '1.5', + d: 'm7.607 12.35 3.08 3.15 5.563-7.143', + }; - // 배경 없이 체크만 표시하는 경우 - if (!showBackground) { - return ( - <svg - xmlns='http://www.w3.org/2000/svg' - width={size} - height={size} - fill='none' - viewBox='0 0 24 24' - {...props} - > - <path - stroke='currentColor' - strokeLinecap='round' - strokeLinejoin='round' - strokeWidth='1.5' - d='m7.607 12.35 3.08 3.15 5.563-7.143' - /> - </svg> - ); - } - return ( - <svg - xmlns='http://www.w3.org/2000/svg' - width={size} - height={size} - fill='none' - viewBox='0 0 24 24' - {...props} - > - <circle cx='12' cy='12' r='12' fill='#121'></circle> - <path - stroke='#fff' - strokeLinecap='round' - strokeLinejoin='round' - strokeWidth='1.5' - d='m7.607 12.35 3.08 3.15 5.563-7.143' - /> - </svg> - ); + return ( + <svg {...commonSvgProps}> + {showBackground && <circle cx='12' cy='12' r='12' fill='#121' />} + <path + {...pathProps} + stroke={showBackground ? '#fff' : 'currentColor'} + /> + </svg> + ); };src/app/page.tsx (1)
20-62: 아이콘 배열 구조를 개선하여 타입 안전성을 높이세요.현재 아이콘 배열이 타입 정의 없이 구현되어 있습니다.
타입 안전성을 위해 다음과 같이 개선하세요:
+interface IconItem { + name: string; + component: React.ReactElement; +} + export default function Home() { - const icons = [ + const icons: IconItem[] = [ { name: 'BellIcon', component: <BellIcon size={32} /> }, // ... 나머지 아이콘들 ];src/app/examples/page.tsx (1)
15-21: 더 포괄적인 드롭다운 테스트를 위한 확장을 고려하세요.현재는 단일 드롭다운만 테스트하고 있습니다. 다양한 use case를 테스트하면 더 유용할 것입니다.
다음과 같은 추가 테스트 케이스를 고려해보세요:
// 비제어 컴포넌트 테스트 <Dropdown options={ACTIVITY_CATEGORIES} placeholder='비제어 드롭다운' onChange={(value) => console.log('Selected:', value)} /> // 다른 크기의 드롭다운 <Dropdown className='h-40 w-400' options={ACTIVITY_CATEGORIES} placeholder='작은 드롭다운' /> // 기본값이 있는 드롭다운 <Dropdown options={ACTIVITY_CATEGORIES} defaultValue='스포츠' placeholder='기본값 있는 드롭다운' />src/components/Dropdown.tsx (1)
109-111: ID 생성 방식 개선을 제안합니다.현재 하드코딩된 ID 접두사 대신 컴포넌트 인스턴스별로 고유한 ID를 생성하는 것이 좋겠습니다.
+ import { useId } from 'react'; export default function Dropdown<T extends string>({ // ... props }: DropdownProps<T>) { + const id = useId(); // focusedIndex 변경 시 스크롤 useEffect(() => { if (isOpen && focusedIndex >= 0) { const focusedElement = document.getElementById( - `dropdown-option-${focusedIndex}`, + `${id}-dropdown-option-${focusedIndex}`, ); focusedElement?.scrollIntoView({ block: 'nearest' }); } }, [focusedIndex, isOpen]);그리고 옵션 렌더링 부분도 수정:
<li key={option} - id={`dropdown-option-${index}`} + id={`${id}-dropdown-option-${index}`} role='option' aria-selected={isSelected} // ... >
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
public/assets/svg/check.tsx(1 hunks)public/assets/svg/chevron.tsx(1 hunks)src/app/examples/page.tsx(1 hunks)src/app/page.tsx(1 hunks)src/components/Dropdown.tsx(1 hunks)src/constants/categories.ts(1 hunks)src/types/dropdownTypes.ts(1 hunks)
🔇 Additional comments (18)
src/constants/categories.ts (1)
8-18: 잘 구현된 타입 안전한 상수 정의입니다.
as const를 사용한 타입 추론과 유니온 타입 생성이 적절하게 구현되었습니다.public/assets/svg/check.tsx (1)
3-7: 타입 정의가 적절하게 구현되었습니다.props에 대한 인터페이스 정의와 기본값 설정이 잘 되어 있습니다.
src/app/page.tsx (1)
19-89: 아이콘 갤러리가 잘 구현되었습니다.개발 및 테스트 목적의 아이콘 갤러리가 명확하고 직관적으로 구현되었습니다. 반응형 그리드와 다양한 props 데모가 유용합니다.
src/app/examples/page.tsx (1)
7-24: 드롭다운 테스트 컴포넌트가 잘 구현되었습니다.상태 관리와 타입 안전성이 적절하게 구현되어 있고, 드롭다운 컴포넌트의 기본 사용법을 명확하게 보여줍니다.
src/components/Dropdown.tsx (11)
1-9: 임포트 구조가 잘 정리되어 있습니다.필요한 모든 의존성이 적절히 임포트되었고, 타입 정의와 컴포넌트들이 올바르게 참조되고 있습니다.
11-28: JSDoc 문서화가 잘 작성되었습니다.제네릭 타입과 컴포넌트 사용법에 대한 명확한 설명과 실제 사용 예시가 포함되어 있어 개발자가 이해하기 쉽습니다.
29-40: 제네릭 타입 선언과 초기 상태 관리가 적절합니다.
T extends string제약조건을 통해 타입 안전성을 보장하고, 내부 상태 변수들이 올바르게 초기화되었습니다.
45-47: controlled/uncontrolled 상태 판별 로직이 우수합니다.
valueprop의 존재 여부로 제어 모드를 판별하는 방식이 React의 일반적인 패턴을 잘 따르고 있습니다.
49-53: 외부 클릭 처리가 적절합니다.드롭다운이 열려있을 때 외부 클릭 시 닫히도록 하는 기능이 잘 구현되었고, 포커스 인덱스도 함께 초기화됩니다.
56-64: 선택 처리 로직이 잘 구현되었습니다.controlled/uncontrolled 모드를 모두 지원하며, 선택 후 드롭다운을 닫고 버튼에 포커스를 돌려주는 UX 처리가 훌륭합니다.
67-104: 키보드 내비게이션 구현이 매우 우수합니다.
- Enter/Space를 통한 선택 및 토글
- ArrowUp/Down을 통한 순환 내비게이션
- Escape를 통한 닫기
모든 기본적인 키보드 상호작용이 웹 접근성 가이드라인에 맞게 구현되었습니다.
107-114: 자동 스크롤 기능이 우수합니다.포커스된 옵션이 화면에 보이도록 자동으로 스크롤하는 기능이 UX를 크게 향상시킵니다.
117-149: 드롭다운 버튼의 접근성 구현이 훌륭합니다.
aria-expanded,aria-haspopup,aria-label속성이 적절히 설정됨- 비활성화 상태의 시각적 피드백과 커서 처리
- 포커스 및 열림 상태에 따른 스타일링
웹 접근성 가이드라인을 잘 준수하고 있습니다.
151-163: 애니메이션 구현이 부드럽고 적절합니다.Framer Motion을 사용한 나타남/사라짐 애니메이션이 자연스럽고, z-index 설정으로 올바른 레이어링을 보장합니다.
164-201: 옵션 목록의 접근성과 상호작용이 잘 구현되었습니다.
role="listbox"와role="option"사용aria-selected속성으로 선택 상태 표시- 마우스와 키보드 상호작용 모두 지원
- 선택된 항목에 CheckIcon 표시
접근성과 사용성을 모두 고려한 우수한 구현입니다.
src/types/dropdownTypes.ts (3)
1-1: ClassValue 임포트가 적절합니다.clsx의 ClassValue 타입을 사용하여 className prop에 다양한 형태의 클래스 값을 허용할 수 있게 되었습니다.
3-15: JSDoc 문서화가 매우 상세하고 명확합니다.제네릭 타입과 각 프로퍼티에 대한 설명이 잘 작성되어 있어 개발자가 이해하기 쉽습니다.
16-23: 인터페이스 설계가 우수합니다.
- 제네릭 타입
T extends string로 타입 안전성 보장readonly T[]로 옵션 배열의 불변성 보장- 선택적 프로퍼티들이 적절히 정의됨
value?: T | ''로 빈 값 상태도 허용API 설계가 유연하면서도 타입 안전합니다.
| @@ -0,0 +1,25 @@ | |||
| import React from 'react'; | |||
|
|
|||
| const ChevronIcon = ({ size = 24, direction = 'down', ...props }) => ( | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
TypeScript 타입 정의가 누락되었습니다.
TypeScript 프로젝트에서 컴포넌트 props에 대한 타입 정의가 없어 타입 안전성이 보장되지 않습니다.
다음과 같이 타입 정의를 추가하세요:
+interface ChevronIconProps {
+ size?: number;
+ direction?: 'up' | 'down';
+ [key: string]: any;
+}
+
-const ChevronIcon = ({ size = 24, direction = 'down', ...props }) => (
+const ChevronIcon = ({ size = 24, direction = 'down', ...props }: ChevronIconProps) => (🤖 Prompt for AI Agents
In public/assets/svg/chevron.tsx at line 3, the ChevronIcon component lacks
TypeScript type definitions for its props, which reduces type safety. Define an
interface or type for the props specifying 'size' as a number, 'direction' as a
string literal union (e.g., 'up' | 'down' | 'left' | 'right'), and include any
additional props as appropriate. Then, annotate the component's props parameter
with this type to ensure proper type checking.
minimo-9
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
만드시느라 고생 많으셨습니다. 아이콘 볼 수 있는 건 굉장히 좋은 것 같네요!
evaain706
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
내/외부 상태
외부클릭시 닫힘
키보드 이벤트등 많은것을 고려하신다음 구현해주신것같습니다!
구현 수고하셨습니다!
src/components/Dropdown.tsx
Outdated
|
|
||
| return ( | ||
| <div ref={dropdownRef} className={cn('relative', className)}> | ||
| {' '} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
공백 주석은 제거하셔도 될 것 같습니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오 놓친 부분이 있었네요 감사합니다 !
BokyungCodes
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
제네릭 타입으로 확장 가능하게 설계돼 있어서 활용도도 높고, 제어/비제어 방식까지 모두 지원되는 구조라 완성도가 높은 컴포넌트인 것 같습니다! 수고하셨습니다👍
📌 변경 사항 개요
공통 Dropdown 컴포넌트를 구현하고, 프로젝트에서 사용하는 아이콘들을 컴포넌트화해서 사용중이라 에디터에서 아이콘 파일의 이름만 보고 어떤 모양인지 유추해야 되는 상황이라 임시로 프로젝트의 모든 아이콘을 확인할 수 있게 임시로 추가했습니다.
📝 상세 내용
1. 아이콘 추가
2. Dropdown 공통 컴포넌트 구현
<T extends string>다양한 옵션 타입 지원value !== undefined로 내/외 상태 관리focusedIndex상태로 방향키classNameprop으로3. 타입 및 상수
ClassValue타입으로 className 형태 지원하도록 했습니다.🔗 관련 이슈
🖼️ 스크린샷(선택사항)
💡 참고 사항
/: 랜딩 페이지 작업 전 임시로 프로젝트에서 사용중인 아이콘 확인 가능하도록 했습니다./examples: 드롭다운 기능 테스트Summary by CodeRabbit
New Features
개선 및 변경
문서화