Skip to content

Conversation

@LeeCh0129
Copy link
Contributor

@LeeCh0129 LeeCh0129 commented Jul 20, 2025

📌 변경 사항 개요

공통 Dropdown 컴포넌트를 구현하고, 프로젝트에서 사용하는 아이콘들을 컴포넌트화해서 사용중이라 에디터에서 아이콘 파일의 이름만 보고 어떤 모양인지 유추해야 되는 상황이라 임시로 프로젝트의 모든 아이콘을 확인할 수 있게 임시로 추가했습니다.

📝 상세 내용

1. 아이콘 추가

  • CheckIcon 개선: showBackground prop 추가로 배경 유/무 선택
  • ChevronIcon 추가: direction prop으로 up/down 회전

2. Dropdown 공통 컴포넌트 구현

  • 제네릭: <T extends string> 다양한 옵션 타입 지원
  • Controlled: value !== undefined로 내/외 상태 관리
  • 키보드 이벤트:
    • focusedIndex 상태로 방향키
    • 마지막 옵션에서 ↓누르면 처음 옵션으로, 첫 옵션에서 ↑누르면 마지막으로 이동
    • 자동 스크롤
  • 크기/스타일: className prop으로

3. 타입 및 상수

  • ClassValue 타입으로 className 형태 지원하도록 했습니다.

🔗 관련 이슈

🖼️ 스크린샷(선택사항)

image image image

💡 참고 사항

  • / : 랜딩 페이지 작업 전 임시로 프로젝트에서 사용중인 아이콘 확인 가능하도록 했습니다.
  • /examples : 드롭다운 기능 테스트

Summary by CodeRabbit

  • New Features

    • 새로운 범용 드롭다운 컴포넌트가 추가되어 옵션 선택, 키보드 내비게이션, 접근성 및 애니메이션 전환을 지원합니다.
    • 아이콘 갤러리 페이지가 구현되어 다양한 SVG 아이콘을 미리보기할 수 있습니다.
    • 카테고리 선택 드롭다운 예시 페이지가 추가되었습니다.
    • 새로운 Chevron(체브론) 아이콘 컴포넌트가 도입되었습니다.
  • 개선 및 변경

    • Check(체크) 아이콘이 배경 유무를 선택할 수 있도록 개선되었습니다.
    • 활동 카테고리 상수 및 타입이 추가되어 일관된 카테고리 선택이 가능합니다.
  • 문서화

    • 드롭다운 컴포넌트의 타입 정의와 각 속성에 대한 설명이 추가되었습니다.

@LeeCh0129 LeeCh0129 self-assigned this Jul 20, 2025
@LeeCh0129 LeeCh0129 added the ✨ Feature 기능구현 label Jul 20, 2025
@coderabbitai
Copy link

coderabbitai bot commented Jul 20, 2025

Walkthrough

드롭다운 공통 컴포넌트와 관련 타입, 카테고리 상수, 아이콘 컴포넌트(Chevron, Check) 등이 새롭게 추가 및 리팩토링되었습니다. 드롭다운 예제 페이지와 아이콘 갤러리 페이지도 구현되어, 새로운 컴포넌트들의 사용 예시가 포함되었습니다.

Changes

파일/경로 변경 요약
public/assets/svg/check.tsx CheckIcon 컴포넌트에 showBackground prop 추가 및 인터페이스 도입, 배경 원 렌더링 옵션화
public/assets/svg/chevron.tsx ChevronIcon 컴포넌트 신규 추가, 방향 및 크기 props 지원
src/app/examples/page.tsx DropdownExample 컴포넌트 신규 추가, 드롭다운 사용 예시 구현
src/app/page.tsx Home 컴포넌트에 아이콘 갤러리 UI 구현, 다양한 아이콘 컴포넌트 렌더링
src/components/Dropdown.tsx 제네릭 타입 지원 드롭다운 컴포넌트 신규 추가, 키보드 내비게이션 및 접근성 지원
src/constants/categories.ts 활동 카테고리 상수 및 ActivityCategory 타입 신규 추가
src/types/dropdownTypes.ts DropdownProps 제네릭 인터페이스 신규 정의

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: 선택값, 포커스 등 상태 갱신
Loading

Estimated code review effort

  • public/assets/svg/check.tsx: 2 (10~30분)
  • public/assets/svg/chevron.tsx: 1 (<10분)
  • src/app/examples/page.tsx: 2 (10~30분)
  • src/app/page.tsx: 3 (30~60분)
  • src/components/Dropdown.tsx: 4 (60~120분)
  • src/constants/categories.ts: 1 (<10분)
  • src/types/dropdownTypes.ts: 1 (<10분)

총평: 3 (30~60분) — 드롭다운 컴포넌트가 가장 복잡하며, 나머지 파일들은 중간 이하 난이도입니다.

Possibly related PRs

Suggested reviewers

  • evaain706
  • BokyungCodes

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

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

src/components/Dropdown.tsx

Oops! 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:

npm install eslint-plugin-react-hooks@latest --save-dev

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 details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 92f812c and 0a388d7.

📒 Files selected for processing (1)
  • src/components/Dropdown.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/Dropdown.tsx
✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 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.
    • Explain this complex logic.
    • 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. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • 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 src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need 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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

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.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • 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

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 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

📥 Commits

Reviewing files that changed from the base of the PR and between fb0e1c1 and 92f812c.

📒 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 상태 판별 로직이 우수합니다.

value prop의 존재 여부로 제어 모드를 판별하는 방식이 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 }) => (
Copy link

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.

Copy link
Contributor

@minimo-9 minimo-9 left a comment

Choose a reason for hiding this comment

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

만드시느라 고생 많으셨습니다. 아이콘 볼 수 있는 건 굉장히 좋은 것 같네요!

Copy link
Contributor

@evaain706 evaain706 left a comment

Choose a reason for hiding this comment

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

내/외부 상태
외부클릭시 닫힘
키보드 이벤트등 많은것을 고려하신다음 구현해주신것같습니다!
구현 수고하셨습니다!


return (
<div ref={dropdownRef} className={cn('relative', className)}>
{' '}
Copy link
Contributor

Choose a reason for hiding this comment

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

공백 주석은 제거하셔도 될 것 같습니다!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

오 놓친 부분이 있었네요 감사합니다 !

Copy link
Contributor

@BokyungCodes BokyungCodes left a comment

Choose a reason for hiding this comment

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

제네릭 타입으로 확장 가능하게 설계돼 있어서 활용도도 높고, 제어/비제어 방식까지 모두 지원되는 구조라 완성도가 높은 컴포넌트인 것 같습니다! 수고하셨습니다👍

@LeeCh0129 LeeCh0129 merged commit 769a436 into develop Jul 21, 2025
2 checks passed
@LeeCh0129 LeeCh0129 deleted the feature/23 branch July 21, 2025 15:42
@minimo-9 minimo-9 changed the title Feature/23 공통 Dropdown 컴포넌트 구현 Feat/23 공통 Dropdown 컴포넌트 구현 Jul 30, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 기능구현

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] 공통 컴포넌트 드롭다운

5 participants