Skip to content
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

[Feat] Dropdown 컴포넌트 추가 #121

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open

Conversation

kongnayeon
Copy link
Member

@kongnayeon kongnayeon commented Feb 11, 2025

관련 이슈

#39

변경 사항

Dropdown 컴포넌트를 구현했습니다!
현재 디자인 시안에서는 select와 dropdown menu 모두 드롭다운 컴포넌트로 표기하고 있어 두 방식을 모두 지원하도록 구현하려 했고, 아래와 같은 방식으로 사용할 수 있어요!
image

menu 모드
<Dropdown>
      <Dropdown.Trigger>
        <Icon name="lineThree" />
      </Dropdown.Trigger>
      <Dropdown.Content align="right">
        <Dropdown.Item value="option1">Option 1</Dropdown.Item>
        <Dropdown.Item value="option2">Option 2</Dropdown.Item>
        <Dropdown.Item value="option3">Option 3</Dropdown.Item>
      </Dropdown.Content>
  </Dropdown>
 
  select 모드
    <Dropdown
          value={selectedValue}
          onValueChange={setSelectedValue}
          placeholder="Select an option"
        >
          <Dropdown.Trigger />
          <Dropdown.Content>
            <Dropdown.Item value="option1">Option 1</Dropdown.Item>
            <Dropdown.Item value="option2">Option 2</Dropdown.Item>
            <Dropdown.Item value="option3">Option 3</Dropdown.Item>
          </Dropdown.Content>
        </Dropdown>

Summary by CodeRabbit

  • New Features
    • 새로운 Dropdown 인터페이스가 추가되어 메뉴 방식과 선택 기능(예: 수정, 삭제)을 제공합니다.
    • 사용자 인터페이스가 개선되어 두 가지 유형의 드롭다운을 통한 옵션 선택이 가능합니다.
    • 시계 아이콘이 추가되어 아이콘 컬렉션이 확장되었습니다.

@kongnayeon kongnayeon self-assigned this Feb 11, 2025
Copy link

coderabbitai bot commented Feb 11, 2025

Walkthrough

이번 변경 사항은 웹 애플리케이션과 UI 패키지에 드롭다운 컴포넌트를 추가하는 작업입니다. 웹 페이지에서는 Home 함수 내에 드롭다운을 추가하고, 상태를 관리하기 위한 useStatehandleSelect 함수를 도입했습니다. UI 패키지에서는 드롭다운 관련 Context, CSS 모듈, 여러 구성 요소(루트, 트리거, 아이템, 컨텐츠)와 타입 정의를 포함하는 파일들이 새로 추가되었으며, Icon 모듈에 IconClock 컴포넌트가 추가되었습니다.

Changes

파일 변경 내용
apps/web/src/app/page.tsx Home 함수에 드롭다운 추가, 상태 관리(useState, handleSelect) 도입, ImageManager 제거
packages/ui/package.json Dropdown 컴포넌트의 exports 항목에 타입 및 import 경로 추가
packages/ui/src/components/Dropdown/... Dropdown 관련 Context, CSS, Root, Trigger, Item, Content, index 파일 및 타입 정의 추가
packages/ui/src/components/Icon/assets.ts, packages/ui/src/components/Icon/assets/IconClock.tsx 새로운 IconClock 컴포넌트 추가 및 icons 객체 업데이트

Sequence Diagram(s)

sequenceDiagram
    participant U as 사용자
    participant DT as DropdownTrigger
    participant DI as DropdownItem
    participant DR as DropdownRoot

    U->>DT: 트리거 클릭
    DT->>DR: 드롭다운 열림 (isOpen 토글)
    U->>DI: 옵션 선택
    DI->>DR: onValueChange 호출 및 상태 업데이트
    DR-->>DT: 선택 값 반영, 드롭다운 닫힘
Loading

Suggested labels

enhancement

Suggested reviewers

  • minseong0324

Poem

나는 깡총깡총 뛰는 코드토끼
드롭다운 속에 새로운 선택의 꿈을 담았네
클릭 하나로 펼쳐지는 마법 같은 UI
작은 버튼이 큰 변화를 이끌었네 🐇
코드 숲 속, 환한 미소로 축하해!

🥕✨

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.

apps/web/src/app/page.tsx

Oops! Something went wrong! :(

ESLint: 9.17.0

Error [ERR_MODULE_NOT_FOUND]: Cannot find package '@repo/eslint-config' imported from /eslint.config.mjs
at packageResolve (node:internal/modules/esm/resolve:839:9)
at moduleResolve (node:internal/modules/esm/resolve:908:18)
at defaultResolve (node:internal/modules/esm/resolve:1038:11)
at ModuleLoader.defaultResolve (node:internal/modules/esm/loader:557:12)
at ModuleLoader.resolve (node:internal/modules/esm/loader:525:25)
at ModuleLoader.getModuleJob (node:internal/modules/esm/loader:246:38)
at ModuleJob._link (node:internal/modules/esm/module_job:126:49)

packages/ui/src/components/Dropdown/Dropdown.tsx

Oops! Something went wrong! :(

ESLint: 9.17.0

Error [ERR_MODULE_NOT_FOUND]: Cannot find package '@repo/eslint-config' imported from /eslint.config.mjs
at packageResolve (node:internal/modules/esm/resolve:839:9)
at moduleResolve (node:internal/modules/esm/resolve:908:18)
at defaultResolve (node:internal/modules/esm/resolve:1038:11)
at ModuleLoader.defaultResolve (node:internal/modules/esm/loader:557:12)
at ModuleLoader.resolve (node:internal/modules/esm/loader:525:25)
at ModuleLoader.getModuleJob (node:internal/modules/esm/loader:246:38)
at ModuleJob._link (node:internal/modules/esm/module_job:126:49)

packages/ui/src/components/Dropdown/Dropdown.context.ts

Oops! Something went wrong! :(

ESLint: 9.17.0

Error [ERR_MODULE_NOT_FOUND]: Cannot find package '@repo/eslint-config' imported from /eslint.config.mjs
at packageResolve (node:internal/modules/esm/resolve:839:9)
at moduleResolve (node:internal/modules/esm/resolve:908:18)
at defaultResolve (node:internal/modules/esm/resolve:1038:11)
at ModuleLoader.defaultResolve (node:internal/modules/esm/loader:557:12)
at ModuleLoader.resolve (node:internal/modules/esm/loader:525:25)
at ModuleLoader.getModuleJob (node:internal/modules/esm/loader:246:38)
at ModuleJob._link (node:internal/modules/esm/module_job:126:49)

  • 8 others
✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ 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.
    • Generate unit testing code for this file.
    • 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 generate unit testing code for this file.
    • @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 generate unit testing code.
    • @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.

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. (Beta)
  • @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.

@kongnayeon kongnayeon changed the title [Feat] Dropdown 컴포넌트 [Feat] Dropdown 컴포넌트 추가 Feb 11, 2025
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: 7

🧹 Nitpick comments (11)
packages/ui/src/components/Icon/assets/IconClock.tsx (2)

3-3: 사용되지 않는 인터페이스를 제거하세요.

IconClockProps 인터페이스는 SVGProps<SVGSVGElement>를 확장하지만 추가 속성이 없고 실제로 사용되지 않습니다.

-interface IconClockProps extends React.SVGProps<SVGSVGElement> {}

18-18: 아이콘 색상을 props로 커스터마이징할 수 있도록 개선하세요.

현재 하드코딩된 색상 값은 재사용성을 제한할 수 있습니다. props를 통해 색상을 전달받도록 수정하는 것이 좋습니다.

-      fill="#788391"
+      fill={props.fill || "#788391"}
packages/ui/src/components/Dropdown/Dropdown.context.ts (2)

4-12: value 타입을 더 구체적으로 정의하면 좋을 것 같습니다.

현재 valuestring | string[] 타입으로 정의되어 있는데, 배열이 필요한 상황이 명확하지 않습니다. 단일 선택만 지원한다면 string 타입으로 충분할 것 같습니다.

-  value: string | string[];
+  value: string;

18-22: 에러 메시지를 더 자세하게 작성하면 좋을 것 같습니다.

현재 에러 메시지가 영어로 되어 있고, 문제 해결을 위한 구체적인 정보가 부족합니다.

-      'Dropdown compound components must be used inside <Dropdown/>'
+      'Dropdown 컴포넌트의 하위 컴포넌트들은 반드시 <Dropdown/> 내부에서 사용되어야 합니다. ' +
+      '현재 컴포넌트가 Dropdown 컨텍스트 외부에서 사용되고 있습니다.'
packages/ui/src/components/Dropdown/DropdownTrigger.tsx (1)

33-33: 기본값 처리 로직을 상수로 분리하면 좋을 것 같습니다.

하드코딩된 문자열을 상수로 분리하면 재사용성과 유지보수성이 향상될 것 같습니다.

+const DEFAULT_PLACEHOLDER = '옵션을 선택하세요.';
+
-        {children || value || placeholder || '옵션을 선택하세요.'}
+        {children || value || placeholder || DEFAULT_PLACEHOLDER}
packages/ui/src/components/Dropdown/Dropdown.css.ts (1)

18-19: 단위 사용을 일관되게 하면 좋을 것 같습니다.

boxShadow에서는 rem 단위를 사용하고 있는데, borderRadius에서는 숫자만 사용하고 있습니다. 일관성을 위해 통일하면 좋을 것 같습니다.

-  borderRadius: tokens.radius[12],
+  borderRadius: `${tokens.radius[12]}rem`,
packages/ui/src/components/Dropdown/DropdownItem.tsx (1)

31-42: ARIA 속성 추가가 필요합니다.

메뉴 아이템의 접근성을 향상시키기 위해 추가적인 ARIA 속성이 필요합니다.

다음과 같이 ARIA 속성을 추가하는 것을 제안합니다:

 <div
   ref={ref}
   className={`${className} ${dropdownItem}`}
   role="menuitem"
   tabIndex={0}
+  aria-selected={context?.value === value}
   onClick={handleClick}
   {...props}
 >
   {children}
 </div>
packages/ui/src/components/Dropdown/Dropdown.tsx (1)

8-38: 예제 코드 문서화 개선이 필요합니다.

현재 예제 코드는 기본적인 사용법만 보여주고 있습니다. 더 다양한 시나리오와 속성 사용에 대한 예제가 필요합니다.

다음과 같은 추가 예제를 문서에 포함하는 것을 제안합니다:

/**
 * @example
 * 
 * // 1. 기본 메뉴 모드 (현재 예제)
 * 
 * // 2. 사용자 정의 트리거와 함께 사용
 * <Dropdown>
 *   <Dropdown.Trigger>
 *     <Button variant="secondary">
 *       <Icon name="settings" />
 *       설정
 *     </Button>
 *   </Dropdown.Trigger>
 *   <Dropdown.Content>
 *     {/* ... */}
 *   </Dropdown.Content>
 * </Dropdown>
 * 
 * // 3. 오른쪽 정렬과 함께 사용
 * <Dropdown>
 *   <Dropdown.Trigger />
 *   <Dropdown.Content align="right">
 *     {/* ... */}
 *   </Dropdown.Content>
 * </Dropdown>
 * 
 * // 4. 비활성화된 아이템 예제
 * <Dropdown>
 *   <Dropdown.Trigger />
 *   <Dropdown.Content>
 *     <Dropdown.Item value="option1">Option 1</Dropdown.Item>
 *     <Dropdown.Item value="option2" disabled>Option 2</Dropdown.Item>
 *   </Dropdown.Content>
 * </Dropdown>
 */
packages/ui/src/components/Dropdown/DropdownContent.tsx (1)

57-67: 접근성 속성 추가가 필요합니다.

드롭다운 컨텐츠의 접근성을 향상시키기 위한 ARIA 속성이 누락되어 있습니다.

다음과 같이 ARIA 속성을 추가하는 것을 제안합니다:

 <div
   ref={mergeRefs(ref, contentRef)}
   className={`${className} ${dropdownContent} ${positionAbove ? dropdownContentAbove : ''} ${
     align === 'right' ? dropdownContentRight : dropdownContentLeft
   }`}
+  role="menu"
+  aria-orientation="vertical"
+  aria-hidden={!isOpen}
   {...props}
 >
   {children}
 </div>
apps/web/src/app/page.tsx (2)

32-36: 불필요한 handleSelect 함수 제거 필요

handleSelect 함수가 정의되어 있지만 사용되지 않고 있습니다. onValueChange에 직접 setSelectedValue를 전달하고 있으므로 이 함수는 제거해도 됩니다.

  const [selectedValue, setSelectedValue] = useState('Select an option');

-  const handleSelect = (value: string) => {
-    setSelectedValue(value);
-  };

610-623: 영문 placeholder 한글화 검토

사용자 인터페이스의 일관성을 위해 영문 placeholder를 한글로 변경하는 것을 고려해보세요.

-          placeholder="Select an option"
+          placeholder="옵션을 선택하세요"

-  const [selectedValue, setSelectedValue] = useState('Select an option');
+  const [selectedValue, setSelectedValue] = useState('옵션을 선택하세요');
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 6adea35 and f1cea3d.

⛔ Files ignored due to path filters (1)
  • packages/ui/src/assets/icons/IconClock.svg is excluded by !**/*.svg
📒 Files selected for processing (12)
  • apps/web/src/app/page.tsx (2 hunks)
  • packages/ui/package.json (1 hunks)
  • packages/ui/src/components/Dropdown/Dropdown.context.ts (1 hunks)
  • packages/ui/src/components/Dropdown/Dropdown.css.ts (1 hunks)
  • packages/ui/src/components/Dropdown/Dropdown.tsx (1 hunks)
  • packages/ui/src/components/Dropdown/DropdownContent.tsx (1 hunks)
  • packages/ui/src/components/Dropdown/DropdownItem.tsx (1 hunks)
  • packages/ui/src/components/Dropdown/DropdownRoot.tsx (1 hunks)
  • packages/ui/src/components/Dropdown/DropdownTrigger.tsx (1 hunks)
  • packages/ui/src/components/Dropdown/index.tsx (1 hunks)
  • packages/ui/src/components/Icon/assets.ts (2 hunks)
  • packages/ui/src/components/Icon/assets/IconClock.tsx (1 hunks)
🔇 Additional comments (3)
packages/ui/src/components/Icon/assets.ts (1)

15-15: 변경사항이 적절합니다!

새로운 아이콘이 알파벳 순서에 맞게 잘 추가되었습니다.

Also applies to: 52-52

packages/ui/src/components/Dropdown/index.tsx (1)

1-7: 내보내기가 깔끔하게 구성되어 있습니다!

컴포넌트와 타입들이 명확하게 구조화되어 있어 사용하기 편리할 것 같습니다.

packages/ui/package.json (1)

90-93: 구성이 올바르게 되어있습니다!

Dropdown 컴포넌트의 export 구성이 다른 컴포넌트들과 일관된 패턴을 따르고 있으며, types와 import 경로가 올바르게 지정되어 있습니다.

Comment on lines +15 to +17
fill-rule="evenodd"
clip-rule="evenodd"
d="M12.8571 12.2951C12.8571 12.3234 12.8434 12.3483 12.8409 12.3757C12.836 12.4338 12.8242 12.491 12.8057 12.5463C12.7747 12.6488 12.724 12.7443 12.6566 12.8274C12.6189 12.8729 12.5794 12.9131 12.5331 12.9509C12.5117 12.968 12.4989 12.9929 12.4757 13.0083L8.42486 15.7083C8.2357 15.8296 8.00645 15.8718 7.78648 15.8258C7.56652 15.7798 7.37342 15.6492 7.24879 15.4622C7.12416 15.2752 7.07795 15.0467 7.12013 14.826C7.16231 14.6052 7.28949 14.4099 7.47429 14.282L11.1429 11.8366V7.83371C11.1429 7.60639 11.2332 7.38837 11.3939 7.22762C11.5547 7.06688 11.7727 6.97657 12 6.97657C12.2273 6.97657 12.4453 7.06688 12.6061 7.22762C12.7668 7.38837 12.8571 7.60639 12.8571 7.83371V12.2951ZM12 3.5C7.02943 3.5 3 7.52943 3 12.5C3 17.4706 7.02943 21.5 12 21.5C16.9706 21.5 21 17.4706 21 12.5C21 7.52943 16.9706 3.5 12 3.5Z"
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

SVG 속성명을 React 컨벤션에 맞게 수정하세요.

React에서 SVG 속성은 camelCase를 사용해야 합니다.

-      fill-rule="evenodd"
-      clip-rule="evenodd"
+      fillRule="evenodd"
+      clipRule="evenodd"
📝 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.

Suggested change
fill-rule="evenodd"
clip-rule="evenodd"
d="M12.8571 12.2951C12.8571 12.3234 12.8434 12.3483 12.8409 12.3757C12.836 12.4338 12.8242 12.491 12.8057 12.5463C12.7747 12.6488 12.724 12.7443 12.6566 12.8274C12.6189 12.8729 12.5794 12.9131 12.5331 12.9509C12.5117 12.968 12.4989 12.9929 12.4757 13.0083L8.42486 15.7083C8.2357 15.8296 8.00645 15.8718 7.78648 15.8258C7.56652 15.7798 7.37342 15.6492 7.24879 15.4622C7.12416 15.2752 7.07795 15.0467 7.12013 14.826C7.16231 14.6052 7.28949 14.4099 7.47429 14.282L11.1429 11.8366V7.83371C11.1429 7.60639 11.2332 7.38837 11.3939 7.22762C11.5547 7.06688 11.7727 6.97657 12 6.97657C12.2273 6.97657 12.4453 7.06688 12.6061 7.22762C12.7668 7.38837 12.8571 7.60639 12.8571 7.83371V12.2951ZM12 3.5C7.02943 3.5 3 7.52943 3 12.5C3 17.4706 7.02943 21.5 12 21.5C16.9706 21.5 21 17.4706 21 12.5C21 7.52943 16.9706 3.5 12 3.5Z"
fillRule="evenodd"
clipRule="evenodd"
d="M12.8571 12.2951C12.8571 12.3234 12.8434 12.3483 12.8409 12.3757C12.836 12.4338 12.8242 12.491 12.8057 12.5463C12.7747 12.6488 12.724 12.7443 12.6566 12.8274C12.6189 12.8729 12.5794 12.9131 12.5331 12.9509C12.5117 12.968 12.4989 12.9929 12.4757 13.0083L8.42486 15.7083C8.2357 15.8296 8.00645 15.8718 7.78648 15.8258C7.56652 15.7798 7.37342 15.6492 7.24879 15.4622C7.12416 15.2752 7.07795 15.0467 7.12013 14.826C7.16231 14.6052 7.28949 14.4099 7.47429 14.282L11.1429 11.8366V7.83371C11.1429 7.60639 11.2332 7.38837 11.3939 7.22762C11.5547 7.06688 11.7727 6.97657 12 6.97657C12.2273 6.97657 12.4453 7.06688 12.6061 7.22762C12.7668 7.38837 12.8571 7.60639 12.8571 7.83371V12.2951ZM12 3.5C7.02943 3.5 3 7.52943 3 12.5C3 17.4706 7.02943 21.5 12 21.5C16.9706 21.5 21 17.4706 21 12.5C21 7.52943 16.9706 3.5 12 3.5Z"

Comment on lines +19 to +28
ref={(node) => {
if (typeof ref === 'function') {
ref(node);
} else if (ref) {
(ref as any).current = node;
}
if (triggerRef) {
(triggerRef as any).current = node;
}
}}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

ref 처리 로직에서 타입 안전성을 개선해야 합니다.

현재 코드에서 as any 타입 단언을 사용하고 있는데, 이는 타입 안전성을 해칠 수 있습니다.

다음과 같이 개선하는 것을 추천드립니다:

-          if (typeof ref === 'function') {
-            ref(node);
-          } else if (ref) {
-            (ref as any).current = node;
-          }
-          if (triggerRef) {
-            (triggerRef as any).current = node;
-          }
+          // ref 병합을 위한 유틸리티 함수 사용
+          if (node) {
+            if (typeof ref === 'function') ref(node);
+            else if (ref) ref.current = node;
+            if (triggerRef) triggerRef.current = node;
+          }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +20 to +29
const handleClick = (
event: React.MouseEvent<HTMLDivElement, MouseEvent>
) => {
if (onClick) onClick(event);
if (context && value !== undefined) {
context.onValueChange?.(value);
// 아이템 클릭 시 드롭다운 닫기
context.setIsOpen(false);
}
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

키보드 접근성 개선이 필요합니다.

현재 구현에서는 클릭 이벤트만 처리되고 있습니다. 키보드 사용자를 위해 키보드 이벤트 처리가 필요합니다.

다음과 같이 키보드 이벤트를 추가하는 것을 제안합니다:

 const handleClick = (
   event: React.MouseEvent<HTMLDivElement, MouseEvent>
 ) => {
   if (onClick) onClick(event);
   if (context && value !== undefined) {
     context.onValueChange?.(value);
     context.setIsOpen(false);
   }
 };

+const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
+  if (event.key === 'Enter' || event.key === ' ') {
+    event.preventDefault();
+    if (onClick) onClick(event as unknown as React.MouseEvent);
+    if (context && value !== undefined) {
+      context.onValueChange?.(value);
+      context.setIsOpen(false);
+    }
+  }
+};

그리고 컴포넌트에 이벤트 핸들러를 추가:

 <div
   ref={ref}
   className={`${className} ${dropdownItem}`}
   role="menuitem"
   tabIndex={0}
   onClick={handleClick}
+  onKeyDown={handleKeyDown}
   {...props}
 >
📝 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.

Suggested change
const handleClick = (
event: React.MouseEvent<HTMLDivElement, MouseEvent>
) => {
if (onClick) onClick(event);
if (context && value !== undefined) {
context.onValueChange?.(value);
// 아이템 클릭 시 드롭다운 닫기
context.setIsOpen(false);
}
};
const handleClick = (
event: React.MouseEvent<HTMLDivElement, MouseEvent>
) => {
if (onClick) onClick(event);
if (context && value !== undefined) {
context.onValueChange?.(value);
// 아이템 클릭 시 드롭다운 닫기
context.setIsOpen(false);
}
};
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
if (onClick) onClick(event as unknown as React.MouseEvent<HTMLDivElement, MouseEvent>);
if (context && value !== undefined) {
context.onValueChange?.(value);
context.setIsOpen(false);
}
}
};
return (
<div
ref={ref}
className={`${className} ${dropdownItem}`}
role="menuitem"
tabIndex={0}
onClick={handleClick}
onKeyDown={handleKeyDown}
{...props}
>
{/* ... other component rendering ... */}
</div>
);

Comment on lines +37 to +46
const contextValue: DropdownContextValue = isSelectMode
? {
value: value!,
onValueChange: onValueChange!,
placeholder,
isOpen,
setIsOpen,
triggerRef,
}
: { value: '', isOpen, setIsOpen, triggerRef };
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

타입 안전성 개선이 필요합니다.

contextValue 생성 시 타입 단언(!.)을 사용하고 있습니다. 이는 런타임 에러의 위험이 있습니다.

다음과 같이 타입 안전한 방식으로 리팩토링하는 것을 제안합니다:

-const contextValue: DropdownContextValue = isSelectMode
-  ? {
-      value: value!,
-      onValueChange: onValueChange!,
-      placeholder,
-      isOpen,
-      setIsOpen,
-      triggerRef,
-    }
-  : { value: '', isOpen, setIsOpen, triggerRef };
+const contextValue: DropdownContextValue = {
+  value: isSelectMode ? value ?? '' : '',
+  onValueChange: isSelectMode ? onValueChange : undefined,
+  placeholder: isSelectMode ? placeholder : undefined,
+  isOpen,
+  setIsOpen,
+  triggerRef,
+};
📝 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.

Suggested change
const contextValue: DropdownContextValue = isSelectMode
? {
value: value!,
onValueChange: onValueChange!,
placeholder,
isOpen,
setIsOpen,
triggerRef,
}
: { value: '', isOpen, setIsOpen, triggerRef };
const contextValue: DropdownContextValue = {
value: isSelectMode ? value ?? '' : '',
onValueChange: isSelectMode ? onValueChange : undefined,
placeholder: isSelectMode ? placeholder : undefined,
isOpen,
setIsOpen,
triggerRef,
};

Comment on lines +30 to +53
useLayoutEffect(() => {
function updatePosition() {
if (isOpen && triggerRef.current && contentRef.current) {
const triggerRect = triggerRef.current.getBoundingClientRect();
const contentRect = contentRef.current.getBoundingClientRect();

if (triggerRect.bottom + contentRect.height > window.innerHeight) {
setPositionAbove(true);
} else {
setPositionAbove(false);
}
}
}

updatePosition();

window.addEventListener('scroll', updatePosition, true);
window.addEventListener('resize', updatePosition);

return () => {
window.removeEventListener('scroll', updatePosition, true);
window.removeEventListener('resize', updatePosition);
};
}, [isOpen, triggerRef]);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

성능 최적화가 필요합니다.

현재 구현에서는 스크롤과 리사이즈 이벤트가 발생할 때마다 위치를 계산합니다. 이는 성능에 영향을 줄 수 있습니다.

다음과 같이 디바운스를 적용하는 것을 제안합니다:

+import { debounce } from '@repo/ui/utils';
+
 useLayoutEffect(() => {
-  function updatePosition() {
+  const updatePosition = debounce(() => {
     if (isOpen && triggerRef.current && contentRef.current) {
       const triggerRect = triggerRef.current.getBoundingClientRect();
       const contentRect = contentRef.current.getBoundingClientRect();

       if (triggerRect.bottom + contentRect.height > window.innerHeight) {
         setPositionAbove(true);
       } else {
         setPositionAbove(false);
       }
     }
-  }
+  }, 100);

   updatePosition();
   window.addEventListener('scroll', updatePosition, true);
   window.addEventListener('resize', updatePosition);

   return () => {
     window.removeEventListener('scroll', updatePosition, true);
     window.removeEventListener('resize', updatePosition);
+    updatePosition.cancel();
   };
 }, [isOpen, triggerRef]);
📝 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.

Suggested change
useLayoutEffect(() => {
function updatePosition() {
if (isOpen && triggerRef.current && contentRef.current) {
const triggerRect = triggerRef.current.getBoundingClientRect();
const contentRect = contentRef.current.getBoundingClientRect();
if (triggerRect.bottom + contentRect.height > window.innerHeight) {
setPositionAbove(true);
} else {
setPositionAbove(false);
}
}
}
updatePosition();
window.addEventListener('scroll', updatePosition, true);
window.addEventListener('resize', updatePosition);
return () => {
window.removeEventListener('scroll', updatePosition, true);
window.removeEventListener('resize', updatePosition);
};
}, [isOpen, triggerRef]);
+import { debounce } from '@repo/ui/utils';
useLayoutEffect(() => {
- function updatePosition() {
- if (isOpen && triggerRef.current && contentRef.current) {
- const triggerRect = triggerRef.current.getBoundingClientRect();
- const contentRect = contentRef.current.getBoundingClientRect();
-
- if (triggerRect.bottom + contentRect.height > window.innerHeight) {
- setPositionAbove(true);
- } else {
- setPositionAbove(false);
- }
- }
- }
+ const updatePosition = debounce(() => {
+ if (isOpen && triggerRef.current && contentRef.current) {
+ const triggerRect = triggerRef.current.getBoundingClientRect();
+ const contentRect = contentRef.current.getBoundingClientRect();
+
+ if (triggerRect.bottom + contentRect.height > window.innerHeight) {
+ setPositionAbove(true);
+ } else {
+ setPositionAbove(false);
+ }
+ }
+ }, 100);
updatePosition();
window.addEventListener('scroll', updatePosition, true);
window.addEventListener('resize', updatePosition);
return () => {
window.removeEventListener('scroll', updatePosition, true);
window.removeEventListener('resize', updatePosition);
+ updatePosition.cancel();
};
}, [isOpen, triggerRef]);

Comment on lines +566 to +600
<Dropdown>
<Dropdown.Trigger>
<IconButton icon="dots" />
</Dropdown.Trigger>
<Dropdown.Content align="right">
<Dropdown.Item
value="option1"
style={{
display: 'flex',
alignItems: 'center',
gap: '1rem',
width: 'fit-content',
}}
>
<Icon name="clock" size="2.4rem" color="grey400" />
<Text fontSize={18} fontWeight="medium" color="grey1000">
수정하기
</Text>
</Dropdown.Item>
<Dropdown.Item
value="option1"
style={{
display: 'flex',
alignItems: 'center',
gap: '1rem',
width: 'fit-content',
}}
>
<Icon name="trash" size="2.4rem" color="grey400" />
<Text fontSize={18} fontWeight="medium" color="grey1000">
삭제하기
</Text>
</Dropdown.Item>
</Dropdown.Content>
</Dropdown>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

중복된 value 속성 수정 필요

Dropdown.Item 컴포넌트에서 동일한 value="option1"을 사용하고 있습니다. 각 아이템은 고유한 값을 가져야 합니다.

-              value="option1"
+              value="edit"

-              value="option1"
+              value="delete"
📝 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.

Suggested change
<Dropdown>
<Dropdown.Trigger>
<IconButton icon="dots" />
</Dropdown.Trigger>
<Dropdown.Content align="right">
<Dropdown.Item
value="option1"
style={{
display: 'flex',
alignItems: 'center',
gap: '1rem',
width: 'fit-content',
}}
>
<Icon name="clock" size="2.4rem" color="grey400" />
<Text fontSize={18} fontWeight="medium" color="grey1000">
수정하기
</Text>
</Dropdown.Item>
<Dropdown.Item
value="option1"
style={{
display: 'flex',
alignItems: 'center',
gap: '1rem',
width: 'fit-content',
}}
>
<Icon name="trash" size="2.4rem" color="grey400" />
<Text fontSize={18} fontWeight="medium" color="grey1000">
삭제하기
</Text>
</Dropdown.Item>
</Dropdown.Content>
</Dropdown>
<Dropdown>
<Dropdown.Trigger>
<IconButton icon="dots" />
</Dropdown.Trigger>
<Dropdown.Content align="right">
<Dropdown.Item
value="edit"
style={{
display: 'flex',
alignItems: 'center',
gap: '1rem',
width: 'fit-content',
}}
>
<Icon name="clock" size="2.4rem" color="grey400" />
<Text fontSize={18} fontWeight="medium" color="grey1000">
수정하기
</Text>
</Dropdown.Item>
<Dropdown.Item
value="delete"
style={{
display: 'flex',
alignItems: 'center',
gap: '1rem',
width: 'fit-content',
}}
>
<Icon name="trash" size="2.4rem" color="grey400" />
<Text fontSize={18} fontWeight="medium" color="grey1000">
삭제하기
</Text>
</Dropdown.Item>
</Dropdown.Content>
</Dropdown>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant