Skip to content

Feat(design-system): Dropdown 구현#61

Merged
constantly-dev merged 8 commits intodevelopfrom
feat/#28/dropdown
Sep 9, 2025
Merged

Feat(design-system): Dropdown 구현#61
constantly-dev merged 8 commits intodevelopfrom
feat/#28/dropdown

Conversation

@constantly-dev
Copy link
Member

@constantly-dev constantly-dev commented Sep 7, 2025

📌 Related Issues

관련된 Issue를 태그해주세요. (e.g. - close #25)

📄 Tasks

  • Dropdown 컴포넌트 구현

⭐ PR Point (To Reviewer)

현재 figma기준으로 Dropdown이 일부 domain에 종속된다고 생각할 수 있을 것 같아요. 하지만 이후에 재사용 되는 경우 해당 디자인을 최대한 활용하는 방향으로 디자이너 분들과 이야기를 통해 이를 최대한 공통으로 쓸 수 있도록 설계하여 pds에 추가하였어요.

📌 설계에 대한 고민

일단 pds에 추가한 컴포넌트인 만큼 이를 어떻게 설계할지 고민이 되었어요. 단순 options만 보여주는 dropdown이면 쉬웠겠지만, 아래 추가하는 버튼이 따로 존재했고 이는 이후 확장되는 컴포넌트에서는 사용되지 않을 가능성도 있기 때문에 분기가 필요할 것 같았어요.

이를 위해 제가 생각한 방법은 2가지 정도였어요.

  1. 합성 컴포넌트 패턴으로 설계
  2. props로 단순 분기 처리해서 내부적으로 UI 분리

최종적으로 2번을 선택했어요. 그 이유는 합성 컴포넌트 패턴을 쓰는 이유가 대표적으로 props drilling 방지, 공통된 컴포넌트 순서 변경 사용 시 용이 등이 있을 것 같은데 해당 컴포넌트에서는 조합으로 순서 변경을 잘하게 할 필요가 없다고 생각했어요. 단순 추가 버튼이 있고 없고 정도였기 때문이에요!

그래서 일단 2번으로 분기 처리를 했고, 이후에 만약 더 Dropdown이 복잡해지거나 조합을 했을 때의 장점이 생기도록 변경이 된다면 다시 재설계를 고민해볼게요 👍

📌 인터페이스 및 사용방법

인터페이스는 아래와 같아요.

interface DropdownProps {
  options: string[]; // dropdown item 리스트
  selectedValue: string | null; // 선택된 item
  onChange: (selected: string | null) => void; // item 선택 change handler
  placeholder: string; // select 값이 없을 때 기본 text (placeholder)
  onAddItem?: () => void; // item 추가 handler
  addItemLabel?: string; // item 추가 text
  limit?: number; // option 한계 개수
  className?: string; // 디자인 추가 custom className
}

기본적으로 options, selectedValue, onChange, placeholder가 필수이고 나머지는 optional이에요!

사용 방법은 아래 예시로 대체할게요!

// initialCategories는 API 연결 후 받아온 list data로 설정

const [categories, setCategories] = useState(initialCategories);
const [selectedCategory, setSelectedCategory] = useState<string | null>(null); // 초기 값은 필요 시 설정

const CATEGORY_LIMIT = 5;

const handleAddNewCategory = () => {
  const newCategory = '추가할 option';

  if (categories.length < CATEGORY_LIMIT) {
    setCategories([...categories, newCategory]);
  } else {
    alert(`카테고리는 최대 ${CATEGORY_LIMIT}개까지 추가할 수 있습니다.`);
  }
};

return (
  <Dropdown
    options={categories}
    selectedValue={selectedCategory}
    onChange={setSelectedCategory}
    placeholder="카테고리를 선택하세요"
    onAddItem={handleAddNewCategory}
    addItemLabel="카테고리 추가"
    limit={CATEGORY_LIMIT}
  />
);

📷 Screenshot

UI에 대한 자세한 설명 및 확인은 크로마틱을 참고해주세요!


bandicam.2025-09-07.13-25-55-640.mp4

Summary by CodeRabbit

  • 신기능

    • 디자인 시스템에 드롭다운 컴포넌트 추가: 플레이스홀더/선택값 표시, 옵션 선택 반영 및 하이라이트, 열림/닫힘 토글과 아이콘 회전, 선택 제한과 조건부 “항목 추가” 버튼 지원.
    • 아이콘에 회전 애니메이션 옵션 추가: 회전 시 부드러운 트랜지션 적용 가능(hasRotateAnimation).
  • 문서

    • Storybook 예제 추가: 기본, 추가 버튼 알림, 제한 도달, 다수 옵션 시나리오와 데모용 컨트롤러 포함.
  • 작업

    • 패키지 설정(rootDir) 추가 및 컴포넌트 공개(재수출) 반영.

@coderabbitai
Copy link

coderabbitai bot commented Sep 7, 2025

Walkthrough

새로운 Dropdown React 컴포넌트를 추가하고 Storybook 스토리를 작성했으며, Icon 컴포넌트에 회전 애니메이션 옵션을 추가하고, 컴포넌트 인덱스에 재-exports 및 패키지의 TypeScript 설정에 compilerOptions.rootDir를 추가했습니다.

Changes

Cohort / File(s) Summary
Dropdown 컴포넌트
packages/design-system/src/components/dropdown/Dropdown.tsx
신규 React/TSX 컴포넌트 추가: 제어형 props(options, selectedValue, onChange), 트리거로 열기/닫기(isOpen), 옵션 선택 시 onChange 호출 및 닫기, 선택 하이라이트, onAddItem 기반 추가 버튼 렌더링(옵션 수와 limit 비교), Tailwind 클래스 적용.
스토리북(Stories)
packages/design-system/src/components/dropdown/Dropdown.stories.tsx
Dropdown용 Storybook 추가: meta 설정(한국어 설명 포함), ControlledRender로 로컬 선택 상태 관리, 네 가지 시나리오 스토리(기본, 추가 버튼 알럿, 제한 도달, 많은 옵션).
Icon 컴포넌트 변경
packages/design-system/src/icons/components/icon.tsx
IconPropshasRotateAnimation?: boolean 추가 및 컴포넌트에 해당 prop 처리 추가: hasRotateAnimation이 true일 때 회전 애니메이션 관련 CSS 클래스(transform transition-transform duration-200)를 조건부로 포함하도록 변경.
공개 Export 등록
packages/design-system/src/components/index.ts
export { default as Dropdown } from './dropdown/Dropdown'; 추가로 Dropdown을 공개 재-exports.
TS 설정 업데이트
packages/design-system/tsconfig.json
compilerOptions.rootDir.로 추가.

Sequence Diagram(s)

sequenceDiagram
  actor 사용자 as U
  participant Dropdown as D
  participant 부모 as P
  Note over D: 트리거 버튼(아이콘 포함)
  U->>D: 트리거 클릭
  D->>D: isOpen 토글 (아이콘 회전: hasRotateAnimation 시 애니메이션)
  alt 옵션 선택
    U->>D: 옵션 클릭
    D->>P: onChange(selected)
    D->>D: isOpen = false
  else 아이템 추가 (onAddItem 제공 && limit 미도달)
    U->>D: "Add item" 클릭
    D->>P: onAddItem()
    D->>D: isOpen = false
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • Feat(design-system): progress bar 구현 #25 — 디자인 시스템의 components/index.ts에 컴포넌트 재-exports를 추가하는 변경과 관련(여기서는 Dropdown, 해당 PR은 Progress 등). 강한 코드 레벨 연관성.

Suggested labels

🛠️ Feature

Suggested reviewers

  • jllee000
  • jjangminii
  • karnelll

Pre-merge checks (2 passed, 3 warnings)

❌ Failed checks (3 warnings)
Check name Status Explanation Resolution
Linked Issues Check ⚠️ Warning 이 PR은 #28(드롭다운 컴포넌트 구현) 요구사항을 충족하지만 #25(프로그레스 컴포넌트 구현) 요구사항은 전혀 포함되지 않아 연결된 이슈 중 일부가 이행되지 않았습니다. 프로그레스 컴포넌트(#25) 구현 관련 변경사항을 추가하거나 이슈 링크에서 #25를 제거하여 연결된 이슈가 모두 이행되도록 수정해주세요.
Out of Scope Changes Check ⚠️ Warning 이 PR에는 드롭다운 컴포넌트 구현과 직접 관련 없는 아이콘 컴포넌트의 hasRotateAnimation API 추가 및 tsconfig.json의 rootDir 설정 변경이 포함되어 있어 범위를 벗어난 변경사항이 존재합니다. 아이콘 애니메이션 기능 및 tsconfig 설정 변경은 별도 PR로 분리하거나 이 PR에서 제거하여 범위에 맞는 변경만 남겨주세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed 주된 변경사항인 디자인 시스템용 Dropdown 컴포넌트 구현을 명확하고 간결하게 요약하고 있어 PR 타이틀로 적합합니다.
Description Check ✅ Passed PR 템플릿에 명시된 관련 이슈, 작업 목록, 검토 포인트, 스크린샷 섹션이 모두 충실히 작성되어 있어 설명이 구체적이고 완성되었습니다.

Poem

"귀가 팔랑, 버튼 톡—열렸네 🥕
줄 선 옵션들 속, 누굴 고를래요?
한 줄 더 원해? Add로 퐁—넣어줘요.
아이콘 빙글, 토끼는 깡충—
오늘도 UI에 홉, 배포는 냠!"

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.

✨ 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/#28/dropdown

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot added the feat 기능 개발하라 개발 달려라 달려 label Sep 7, 2025
@constantly-dev constantly-dev changed the title Feat(design-system) Dropdown 구현 Feat(design-system): Dropdown 구현 Sep 7, 2025
@github-actions
Copy link

github-actions bot commented Sep 7, 2025

✅ Storybook chromatic 배포 확인:
🐿️ storybook

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

🧹 Nitpick comments (5)
packages/design-system/tsconfig.json (1)

5-5: check-types 전용 tsconfig 분리

  • packages/design-system/tsconfig.build.json 생성:
    {
      "extends": "./tsconfig.json",
      "compilerOptions": {
        "rootDir": "src",
        "outDir": "dist"
      },
      "include": ["src"],
      "exclude": ["**/*.stories.*", "**/__tests__/**", ".storybook", "turbo"]
    }
  • package.json의 check-types 스크립트를 tsc -p tsconfig.build.json으로 변경 (build 스크립트(vite build)는 그대로 유지)
  • (선택) package.json의 files에 "dist"만 포함하거나 .npmignore를 활용해 .storybook·turbo 등 불필요 파일 배포 제외
packages/design-system/src/components/dropdown/Dropdown.tsx (4)

40-41: 고정 폭 제거로 재사용성 향상(w-[24.8rem] → w-full).
현재 고정 폭 때문에 className로 폭 제어가 어렵습니다. 부모 컨테이너의 폭을 따르도록 내부도 w-full 권장.

-        className={`body4-r flex h-[4.4rem] w-[24.8rem] items-center justify-between rounded-[4px] border px-[0.8rem] py-[1.2rem] transition-colors duration-200 ${isOpen ? 'border-main500' : 'border-gray200'}`}
+        className={`body4-r flex h-[4.4rem] w-full items-center justify-between rounded-[4px] border px-[0.8rem] py-[1.2rem] transition-colors duration-200 ${isOpen ? 'border-main500' : 'border-gray200'}`}
@@
-        <div className="common-shadow absolute z-10 mt-[1.5rem] w-[24.8rem] rounded-[0.4rem] bg-white">
+        <div className="common-shadow absolute z-10 mt-[1.5rem] w-full rounded-[0.4rem] bg-white">

Also applies to: 56-56


7-8: onChange 타입과 구현 불일치: null을 보낼 길이 없습니다.
현재 컴포넌트 내부에서는 null을 전달하지 않으므로 API를 간결하게 하거나(추천) clear 동작을 설계해야 합니다.

옵션 A(간결화, 권장): onChange에서 null 제거

-  onChange: (selected: string | null) => void;
+  onChange: (selected: string) => void;
@@
-  const handleSelect = (option: string) => {
-    onChange(option);
+  const handleSelect = (option: string) => {
+    onChange(option);
     setIsOpen(false);
   };

옵션 B(클리어 지원): clear 버튼/메뉴아이템을 추가하고 onChange(null) 호출을 실제로 구현.
원하시면 B안으로 패치 제안 드리겠습니다.

Also applies to: 27-30


35-36: 외부 클릭·ESC로 닫힘 처리 추가 제안.
드롭다운은 보통 바깥 클릭/ESC로 닫힙니다. 간단한 ref+이벤트 바인딩으로 해결 가능.

-import { useId, useState } from 'react';
+import { useEffect, useId, useRef, useState } from 'react';
@@
-  return (
-    <div className={`relative w-full ${className}`}>
+  const containerRef = useRef<HTMLDivElement>(null);
+  useEffect(() => {
+    if (!isOpen) return;
+    const onDocClick = (e: MouseEvent) => {
+      if (!containerRef.current?.contains(e.target as Node)) setIsOpen(false);
+    };
+    const onKey = (e: KeyboardEvent) => {
+      if (e.key === 'Escape') setIsOpen(false);
+    };
+    document.addEventListener('mousedown', onDocClick);
+    document.addEventListener('keydown', onKey);
+    return () => {
+      document.removeEventListener('mousedown', onDocClick);
+      document.removeEventListener('keydown', onKey);
+    };
+  }, [isOpen]);
+
+  return (
+    <div ref={containerRef} className={`relative w-full ${className}`}>

Also applies to: 55-56, 69-81


1-3: 가능하면 Radix UI Select/DropdownMenu 채택 고려.
진입장벽 대비 성숙한 접근성(포커스 이동, 타입어헤드, 스크린리더)과 포탈·경계 처리까지 무료로 얻습니다. #25에서도 Radix를 이미 사용 중입니다.

대안: @radix-ui/react-select로 마이그레이션(Trigger, Content, Item 구성). 필요시 초기 마이그레이션 코드 초안 제공 가능합니다.

Also applies to: 35-86

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 16afe23 and 9b68f19.

📒 Files selected for processing (3)
  • packages/design-system/src/components/dropdown/Dropdown.tsx (1 hunks)
  • packages/design-system/src/components/index.ts (1 hunks)
  • packages/design-system/tsconfig.json (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: constantly-dev
PR: Pinback-Team/pinback-client#2
File: pnpm-workspace.yaml:3-3
Timestamp: 2025-08-18T13:48:59.065Z
Learning: constantly-dev는 docs 디렉터리를 컨벤션 문서 추가용으로 사용할 예정이라고 명시했습니다.
⏰ 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: storybook
🔇 Additional comments (1)
packages/design-system/src/components/index.ts (1)

5-5: 공개 export 연동 OK.
드롭다운 기본 export를 컴포넌트 인덱스에 안전하게 노출했습니다. 배럴 내 다른 default export 스타일과도 일관적입니다.

Comment on lines +15 to +24
const Dropdown = ({
options,
selectedValue,
onChange,
placeholder,
onAddItem,
addItemLabel,
limit,
className = '',
}: DropdownProps) => {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

빈 라벨/플레이스홀더 노출 위험 수정: 기본 placeholder·addItemLabel 기본값을 부여하세요.
addItemLabel이 미지정이면 “빈 버튼”이 렌더링되고, placeholder 미지정 시 빈 텍스트가 나옵니다.

-  placeholder,
+  placeholder = '선택하세요',
   onAddItem,
-  addItemLabel,
+  addItemLabel = '추가',
@@
-        <span className={selectedValue ? 'text-black' : 'text-font-gray-3'}>
-          {selectedValue || placeholder}
+        <span className={selectedValue !== null ? 'text-black' : 'text-font-gray-3'}>
+          {selectedValue ?? placeholder}
         </span>
@@
-              {addItemLabel}
+              {addItemLabel}

Also applies to: 42-44, 69-81

🤖 Prompt for AI Agents
In packages/design-system/src/components/dropdown/Dropdown.tsx around lines
15-24 (and also address similar spots at 42-44 and 69-81), the component can
render an empty placeholder or an empty "add" button when placeholder or
addItemLabel are not provided; update the props destructuring to supply sensible
defaults (e.g., placeholder: 'Select...', addItemLabel: 'Add') or otherwise
ensure fallback strings are used wherever placeholder or addItemLabel are
rendered so the UI never shows an empty label/button; apply the same default or
fallback logic to the other affected blocks mentioned.

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

🧹 Nitpick comments (5)
packages/design-system/src/components/dropdown/Dropdown.stories.tsx (5)

63-63: any 제거로 타입 안전성 확보

ControlledRenderargs: any는 스토리-컴포넌트 간 계약을 흐립니다. 컴포넌트 기반으로 정확히 타이핑하세요.

적용 제안:

-function ControlledRender(args: any) {
+function ControlledRender(args: ComponentProps<typeof Dropdown>) {

63-75: 스토리 Controls와 내부 state 동기화 누락

스토리 Controls에서 selectedValue를 변경해도 내부 value가 초기값 이후로 갱신되지 않습니다. 동기화가 필요합니다.

적용 제안:

 function ControlledRender(args: ComponentProps<typeof Dropdown>) {
   const [value, setValue] = useState<string | null>(args.selectedValue ?? null);
 
+  useEffect(() => {
+    setValue(args.selectedValue ?? null);
+  }, [args.selectedValue]);
+
   return (
     <div className="h-[20rem]">
       <Dropdown
         {...args}
         selectedValue={value}

24-27: selectedValue 컨트롤 비활성화 권장(혼동 방지)

해당 스토리는 내부에서 제어하고 있어 Controls의 text 입력이 오작동/혼선을 유발할 수 있습니다. 비활성화가 안전합니다.

적용 제안:

-    selectedValue: {
-      control: 'text',
-      description: '현재 선택된 값(제어 컴포넌트에서 사용).',
-    },
+    selectedValue: {
+      control: false,
+      description: '현재 선택된 값(제어 컴포넌트에서 사용).',
+    },

91-101: alert 대신 Action Logger 사용

Chromatic/CI 환경과 상호작용 로그 일관성을 위해 alert는 지양하고 argTypes의 action 바인딩에 맡기세요.

적용 제안:

 export const WithAddItemAlert: Story = {
   name: '추가 버튼 클릭 시 (test Alert)',
   args: {
     options: ['사과', '바나나'],
     limit: 5,
     addItemLabel: '새 항목 추가',
-    onAddItem: () => {
-      alert('추가 버튼이 클릭되었습니다.');
-    },
   },
   render: ControlledRender,
 };

원하시면 play 함수를 추가해 “추가” 버튼 클릭 액션을 상호작용 테스트로 기록하도록 도와드릴게요.


1-2: 스토리북 타입 import 경로 통일

  • packages/design-system/package.json devDependencies에 @storybook/react 추가
  • 아래 스토리 파일들에서
    - import type { Meta, StoryObj } from '@storybook/react-vite';
    + import type { Meta, StoryObj } from '@storybook/react';
    로 변경
    • src/components/dropdown/Dropdown.stories.tsx
    • src/components/level/Level.stories.tsx
    • src/components/toast/Toast.stories.tsx
    • src/components/progress/Progress.stories.tsx
    • src/components/textarea/Textarea.stories.tsx
    • src/components/input/Input.stories.tsx
    • src/components/button/Button.stories.tsx
    • src/components/chip/Chip.stories.tsx
    • src/components/card/Card.stories.tsx
    • src/components/badge/Badge.stories.tsx
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 37c8e7f and b5ccfca.

📒 Files selected for processing (1)
  • packages/design-system/src/components/dropdown/Dropdown.stories.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: constantly-dev
PR: Pinback-Team/pinback-client#2
File: pnpm-workspace.yaml:3-3
Timestamp: 2025-08-18T13:48:59.065Z
Learning: constantly-dev는 docs 디렉터리를 컨벤션 문서 추가용으로 사용할 예정이라고 명시했습니다.
🪛 GitHub Check: lint
packages/design-system/src/components/dropdown/Dropdown.stories.tsx

[warning] 63-63:
Unexpected any. Specify a different type

🔇 Additional comments (1)
packages/design-system/src/components/dropdown/Dropdown.stories.tsx (1)

5-18: 스토리 구성/문서화 전반은 명확합니다

메타/argTypes/데모 시나리오(기본, 초기 선택, 제한 도달, 다수 옵션) 구성이 깔끔하고, 문서 설명도 충분합니다.

Also applies to: 19-49, 50-57, 80-89, 104-119, 121-127

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

🧹 Nitpick comments (4)
packages/design-system/src/components/dropdown/Dropdown.stories.tsx (4)

1-2: any 제거 + StoryFn 타입 적용, 그리고 외부 컨트롤 변경과 상태 동기화

  • lint 경고(Unexpected any)를 없애고, 스토리 render 콜백을 Storybook 타입으로 명확히 하는 편이 좋습니다.
  • Controls 패널에서 selectedValue를 바꿨을 때 내부 state가 동기화되지 않는 문제도 useEffect로 해결하세요.
- import type { Meta, StoryObj } from '@storybook/react-vite';
- import { useState } from 'react';
+ import type { Meta, StoryObj, StoryFn } from '@storybook/react-vite';
+ import { useEffect, useState } from 'react';

@@
-function ControlledRender(args: any) {
-  const [value, setValue] = useState<string | null>(args.selectedValue ?? null);
-
-  return (
+const ControlledRender: StoryFn<typeof Dropdown> = (args) => {
+  const [value, setValue] = useState<string | null>(args.selectedValue ?? null);
+  useEffect(() => {
+    setValue(args.selectedValue ?? null);
+  }, [args.selectedValue]);
+
+  return (
     <div className="h-[25rem]">
       <Dropdown
         {...args}
         selectedValue={value}
         onChange={(next: string | null) => {
           setValue(next);
           args.onChange?.(next);
         }}
       />
     </div>
   );
-}
+};

Also applies to: 63-76


24-31: selectedValue를 text 컨트롤 대신 select 컨트롤로 제한

selectedValue는 options 중 하나(또는 null)여야 하므로 text 컨트롤은 잘못된 상태를 만들 수 있습니다. 기본 args와 일치하도록 select 컨트롤을 사용하면 UX와 일관성이 좋아집니다.

 selectedValue: {
-  control: 'text',
-  description: '현재 선택된 값(제어 컴포넌트에서 사용).',
+  control: { type: 'select' },
+  options: [null, '사과', '바나나', '체리', '포도'],
+  description: '현재 선택된 값(제어 컴포넌트에서 사용).',
 },

85-96: WithAddItemAlert: alert 대신 addon-actions 활용 권장

argTypes에서 이미 onAddItem을 action으로 설정했으므로, 스토리에서 alert를 직접 띄우지 않아도 액션 패널로 충분히 관찰 가능합니다. Chromatic 등 자동화에도 영향을 덜 줍니다.

 export const WithAddItemAlert: Story = {
   name: '추가 버튼 클릭 시 (test Alert)',
   args: {
     options: ['사과', '바나나'],
     limit: 5,
     addItemLabel: '새 항목 추가',
-    onAddItem: () => {
-      alert('추가 버튼이 클릭되었습니다.');
-    },
   },
   render: ControlledRender,
 };

5-9: meta 선언은 ‘satisfies’로 타입 보존

const meta = { ... } satisfies Meta<typeof Dropdown> 패턴을 쓰면 과잉 확장을 막으면서도 추론된 리터럴 타입을 보존할 수 있어 Storybook 설정 실수를 조기에 잡기 좋습니다.

-const meta: Meta<typeof Dropdown> = {
+const meta = {
   title: 'Components/Dropdown',
   component: Dropdown,
   tags: ['autodocs'],
   parameters: {
@@
-};
+} satisfies Meta<typeof Dropdown>;

Also applies to: 57-57

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b5ccfca and e7f34d8.

📒 Files selected for processing (2)
  • packages/design-system/src/components/dropdown/Dropdown.stories.tsx (1 hunks)
  • packages/design-system/src/components/dropdown/Dropdown.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/design-system/src/components/dropdown/Dropdown.tsx
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: constantly-dev
PR: Pinback-Team/pinback-client#2
File: pnpm-workspace.yaml:3-3
Timestamp: 2025-08-18T13:48:59.065Z
Learning: constantly-dev는 docs 디렉터리를 컨벤션 문서 추가용으로 사용할 예정이라고 명시했습니다.
🪛 GitHub Check: lint
packages/design-system/src/components/dropdown/Dropdown.stories.tsx

[warning] 63-63:
Unexpected any. Specify a different type

Copy link
Collaborator

@jjangminii jjangminii left a comment

Choose a reason for hiding this comment

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

인터페이스가 명확하고 필수/옵셔널 props 구분 및 사용 예시가 좋아서 사용할 때 좋을 것 같아요 고생하셨습니다-!
지금 단뎨에선 props 분기가 더 간단할것 같다고 생각이 드네요 추후 확장된다면 합성 패턴도 고민해보는게 좋겠네요-!!

Copy link
Collaborator

@jllee000 jllee000 left a comment

Choose a reason for hiding this comment

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

깔끔하니 고생 많으셨습니다!
간단한 질문만 확인해주시고,, 아이콘의 애니메이션 적용 관련해서 남겨주신 부분에 대한 저의 생각도 공유해보았습니다!

Comment on lines 47 to 51
width={16}
height={16}
// TODO: Icon 컴포넌트 내부에서 animation 관련 처리 고민하기
// rotate={isOpen ? 180 : undefined}
className={`transform transition-transform duration-200 ${isOpen ? 'rotate-180' : ''}`}
Copy link
Collaborator

Choose a reason for hiding this comment

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

이부분도 고민해보았는데요!
isOpen에 따라서 회전 180이냐 0이냐 rotate 상태가 바뀌니까 이걸 transition 처리로 스무스(?)하게만 적용해주면 되는거라!

<Icon name="ic_arrow_down_disable" rotate={isOpen ? 180 : 0} animateRotate />
애니메이션 줄 지 의사를 boolean으로 전달후에

Icon 내에서 animateRotate 에 따른
combined 객체에 분기를 처리해두는 방향은 어떠한 지 제안합니다!

const combined = clsx( 'inline-block', rotateClass, animateRotate && 'transform transition-transform duration-200', className );

Copy link
Member Author

Choose a reason for hiding this comment

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

오 이렇게 처리해도 가능하겠네요! 이렇게 Icon파일에서 한 번에 적용하면 오히려 모든 icon의 rotate animation이 통일되니까 디자인 시스템의 의미도 갖을 것 같아요!
저는 좋은 생각 같은데 다른 분들의 생각은 어떠신가요? 괜찮으시다면 반영하겠습니다!

@jllee000 @jjangminii

Copy link
Collaborator

Choose a reason for hiding this comment

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

(회전이나 애니메이션 들이 아이콘 단위로 많이 쓰이지는 않지만)
사이드바에도 비슷한 아이콘 회전이 있는 거 같아서! 이렇게 관리해도 좋을 듯해요!
animateRotate추가하고 combined분기만 하면 되니, 코드 수정에도 엄청 큰 공수가 들지는 않아서!!

</button>

{isOpen && (
<div className="common-shadow ds-scrollbar absolute z-10 mt-[1.5rem] h-[20.4rem] w-full overflow-y-auto rounded-[0.4rem] bg-white p-[0.8rem]">
Copy link
Collaborator

Choose a reason for hiding this comment

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

ds-scrollbar로 커스텀 스크롤바 스타일이 적용될 수있군녀 알아갑니다!

Copy link
Collaborator

Choose a reason for hiding this comment

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

제가 만들어서 스타일 토큰해뒀습니당

Comment on lines +10 to +11
addItemLabel?: string;
limit?: number;
Copy link
Collaborator

Choose a reason for hiding this comment

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

리밋 속성을 통해서, 카테고리 추가 노출여부를 제어할 수 있어서! 확장성 측면에서 좋은 것 같네요!

++ 대신 디자인적으로 궁금한 점이, 카테고리 리스트개수가 적은 경우에도 그 박스의 height가 고정값일까요?

Copy link
Member Author

Choose a reason for hiding this comment

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

++ 대신 디자인적으로 궁금한 점이, 카테고리 리스트개수가 적은 경우에도 그 박스의 height가 고정값일까요?

네네 일단 그렇게 구현이 되어있어요. 리스트가 많아지는 경우에 스크롤을 보여주려면 box에 height값을 줄 수 밖에 없어서..!
개수에 따라 height를 다르게 분기처리 할 수 있기는 하지만 현재도 괜찮다고 일단 생각이 드는데 어떻게 생각하시나요??
애매하다면 QA때 디자인 분들께 질문 드려도 좋을 것 같아요 👍

Copy link
Collaborator

Choose a reason for hiding this comment

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

아하, 그러네요 스크롤 적용하려면 Height 지정을 해두긴 하네요,, 그렇다고 개수에 따라 height 분기하는 건 오히려 너무 불필요한 거 같아서! 저는 좋아요! QA때 디자이너 분들 피드백에 따라 추후에 수정해보는 걸로 해용!

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

🧹 Nitpick comments (3)
packages/design-system/src/icons/components/icon.tsx (3)

2-3: React 기본 import 대신 타입 전용 import로 전환해 런타임 의존 제거
JSX 자동 런타임에서는 기본 import가 불필요하고, 이 파일에서는 타입 위치에서만 사용됩니다. 타입 전용 import로 교체해 번들 영향 최소화하세요.

-import React from 'react';
+import type { SVGProps } from 'react';

58-68: IconProps: SVGProps로 치환하고 className 중복 선언 제거 제안
className은 이미 SVGProps<SVGSVGElement>에 포함됩니다. 중복 선언을 제거하고 위의 타입 import와 일관되게 사용하세요.

-interface IconProps extends React.SVGProps<SVGSVGElement> {
+interface IconProps extends SVGProps<SVGSVGElement> {
   name: IconName;
   size?: number | string;
   width?: number | string;
   height?: number | string;
   color?: IconColor;
-  className?: string;
   rotate?: IconRotate;
   hasRotateAnimation?: boolean;
   ariaHidden?: boolean;
 }

94-99: Tailwind: transform 유틸은 불필요 — transition-transform만으로 충분
rotate-* 유틸이 transform을 이미 설정합니다. 중복 클래스를 제거해 약간의 CSS 절감이 가능합니다.

-    hasRotateAnimation && 'transform transition-transform duration-200',
+    hasRotateAnimation && 'transition-transform duration-200',
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e7f34d8 and 54e1850.

📒 Files selected for processing (2)
  • packages/design-system/src/components/dropdown/Dropdown.tsx (1 hunks)
  • packages/design-system/src/icons/components/icon.tsx (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/design-system/src/components/dropdown/Dropdown.tsx
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: constantly-dev
PR: Pinback-Team/pinback-client#2
File: pnpm-workspace.yaml:3-3
Timestamp: 2025-08-18T13:48:59.065Z
Learning: constantly-dev는 docs 디렉터리를 컨벤션 문서 추가용으로 사용할 예정이라고 명시했습니다.
🔇 Additional comments (1)
packages/design-system/src/icons/components/icon.tsx (1)

78-79: 기본값(false) 선택 적절 — 기존 API와의 호환 유지
새 prop 미지정 시 동작이 변하지 않아 안전합니다. LGTM.

@constantly-dev constantly-dev merged commit de429a0 into develop Sep 9, 2025
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat 기능 개발하라 개발 달려라 달려

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] Dropdown 컴포넌트 구현

3 participants