Skip to content

Feat(client): select dropdown button 구현#47

Merged
jjangminii merged 3 commits intodevelopfrom
feat/#45/select-dropdown-button-component
Sep 4, 2025
Merged

Feat(client): select dropdown button 구현#47
jjangminii merged 3 commits intodevelopfrom
feat/#45/select-dropdown-button-component

Conversation

@jjangminii
Copy link
Collaborator

@jjangminii jjangminii commented Aug 27, 2025

📌 Related Issues

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

📄 Tasks

*select dropdown button 구현

⭐ PR Point (To Reviewer)

  • 공통 클래스는 ITEM_STYLE 상수로 선언 했습니다
  • 루트 div에 ref로 바깥 클릭 닫기 가능하게 했습니다
  • 열림/닫힘 상태 관리 없고 상위에서 관리하려합니다

📷 Screenshot

image

Summary by CodeRabbit

  • 신기능

    • 앱 레이아웃에 옵션 메뉴 버튼이 추가되어 화면에서 바로 접근할 수 있습니다.
    • 메뉴에서 “수정하기”와 “삭제하기” 동작을 바로 실행할 수 있습니다.
  • 접근성

    • 스크린 리더용 레이블이 포함되어 사용성이 향상되었습니다.
  • 스타일

    • 그림자, 모서리 라운딩, 호버/포커스 효과 등 일관된 시각 스타일이 적용되었습니다.

@jjangminii jjangminii requested a review from jllee000 August 27, 2025 07:34
@jjangminii jjangminii self-assigned this Aug 27, 2025
@jjangminii jjangminii linked an issue Aug 27, 2025 that may be closed by this pull request
@coderabbitai
Copy link

coderabbitai bot commented Aug 27, 2025

Walkthrough

레이아웃에 새 OptionsMenuButton 컴포넌트가 추가되고 Layout은 Outlet 앞에 해당 버튼을 렌더링합니다. OptionsMenuButton은 "수정하기"/"삭제하기" 버튼을 노출하며 각각 onEdit/onDelete 콜백을 호출하도록 props를 받습니다.

Changes

Cohort / File(s) Change Summary
Layout에 옵션 메뉴 버튼 통합
apps/client/src/layout/Layout.tsx
OptionsMenuButton을 임포트하고 <Outlet /> 앞에 <OptionsMenuButton /> 렌더링 추가(콜백에 현재 no-op 전달). Layout의 공개 API 변경 없음.
신규 옵션 메뉴 버튼 컴포넌트
apps/client/src/shared/components/optionsMenuButton/OptionsMenuButton.tsx
OptionsMenuButton 컴포넌트 추가. OptionsMenuButtonProps 인터페이스(ref, onEdit, onDelete, className) 및 기본 export 함수 추가. 역할=메뉴 UI 렌더링(두 버튼) 및 클릭 시 콜백 호출, 접근성 속성·스타일 포함.

Sequence Diagram(s)

sequenceDiagram
    actor User as 사용자
    participant Layout as Layout
    participant OMB as OptionsMenuButton

    User->>Layout: 화면 진입
    Layout->>OMB: OptionsMenuButton 렌더링 (Outlet 이전)
    User->>OMB: "수정하기" 클릭
    alt onEdit 제공됨
        OMB-->>Layout: onEdit 콜백 호출
    else 미제공
        OMB-->>User: 동작 없음
    end
    User->>OMB: "삭제하기" 클릭
    alt onDelete 제공됨
        OMB-->>Layout: onDelete 콜백 호출
    else 미제공
        OMB-->>User: 동작 없음
    end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Assessment against linked issues

Objective Addressed Explanation
프로그레스 바 구현 및 분기 지원 (#25) 프로그레스 관련 컴포넌트/스타일/스토리 추가 없음.
사이드바 카테고리 수정/삭제용 선택 드롭다운 버튼 구현 (#45)

Suggested labels

frontend, 🛠️ Feature

Suggested reviewers

  • jllee000
  • constantly-dev
  • karnelll

Poem

새 버튼이 와르르, 옵션이 반짝여요 🐇
수정인지 삭제인지, 두 갈래 길 택해요.
레이아웃 앞자리에 살며시 앉아,
클릭 한 번이면 소곤소곤 답해요.
(\_/) ✨ (•ᴗ•)

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f143daf and a6892ba.

📒 Files selected for processing (2)
  • apps/client/src/layout/Layout.tsx (1 hunks)
  • apps/client/src/shared/components/optionsMenuButton/OptionsMenuButton.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/client/src/layout/Layout.tsx
  • apps/client/src/shared/components/optionsMenuButton/OptionsMenuButton.tsx
✨ 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/#45/select-dropdown-button-component

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
🪧 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 @coderabbitai in a new review comment at the desired location with your query.
  • 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 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 ignore or @coderabbit 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.

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.

@github-actions github-actions bot added the feat 기능 개발하라 개발 달려라 달려 label Aug 27, 2025
@github-actions
Copy link

github-actions bot commented Aug 27, 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: 1

🧹 Nitpick comments (3)
apps/client/src/shared/components/optionsMenuButton/OptionsMenuButton.tsx (3)

10-14: 키보드 접근성: outline 제거 대신 명확한 focus 스타일 제공

outline-none은 포커스 가시성을 저하시킵니다. 디자인 토큰에 맞는 포커스 스타일로 대체하세요.

-  'hover:bg-gray100 focus-visible:bg-gray100 active:bg-gray200 ' +
-  'outline-none transition-colors';
+  'hover:bg-gray100 focus-visible:bg-gray100 active:bg-gray200 ' +
+  'focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-300 transition-colors';

25-31: ARIA 역할 재검토: menu 패턴 불완전 구현

role="menu"를 사용하면 화살표 키 내비게이션, role="menuitem" 등의 패턴 구현이 전제됩니다. 현재는 일반 버튼 두 개라 패턴 미충족 상태입니다. 두 가지 중 하나를 권장합니다.

  • 단순 액션 목록이라면 role="group"로 완화:
- role="menu"
+ role="group"
  • 혹은 진짜 메뉴로 갈 경우: 각 항목에 role="menuitem" 부여, roving tabindex/arrow-key 처리 또는 Radix DropdownMenu 도입.

33-38: 핸들러 미제공 시 비활성화 처리

onEdit/onDelete가 없을 때도 클릭 가능하게 보입니다. 의도치 않은 UX를 막기 위해 disabled/aria-disabled를 적용하고, 스타일에 disabled: 변형을 추가하세요.

-      <button type="button" onClick={onEdit} className={ITEM_STYLE}>
+      <button
+        type="button"
+        onClick={onEdit}
+        disabled={!onEdit}
+        aria-disabled={!onEdit}
+        className={ITEM_STYLE + ' disabled:opacity-50 disabled:cursor-not-allowed'}
+      >
         수정하기
       </button>
-      <button type="button" onClick={onDelete} className={ITEM_STYLE}>
+      <button
+        type="button"
+        onClick={onDelete}
+        disabled={!onDelete}
+        aria-disabled={!onDelete}
+        className={ITEM_STYLE + ' disabled:opacity-50 disabled:cursor-not-allowed'}
+      >
         삭제하기
       </button>
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between bb59c9d and f143daf.

📒 Files selected for processing (2)
  • apps/client/src/layout/Layout.tsx (1 hunks)
  • apps/client/src/shared/components/optionsMenuButton/OptionsMenuButton.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
apps/client/src/layout/Layout.tsx (1)
apps/client/src/shared/components/optionsMenuButton/OptionsMenuButton.tsx (1)
  • OptionsMenuButton (16-41)
apps/client/src/shared/components/optionsMenuButton/OptionsMenuButton.tsx (1)
packages/design-system/src/lib/utils.ts (1)
  • cn (108-110)
🔇 Additional comments (1)
apps/client/src/shared/components/optionsMenuButton/OptionsMenuButton.tsx (1)

1-1: ref 전달 방식 오류: forwardRef로 전환 필요

현재 props에 ref를 선언/구조분해하고 있으나, 함수형 컴포넌트는 React.forwardRef 없이는 ref를 전달받지 못합니다. 실제로 부모에서 <OptionsMenuButton ref={...} /> 사용 시 작동하지 않거나 타입 경고가 납니다. React 네임스페이스 타입(React.Ref) 사용으로 인한 타입 오류 가능성도 있습니다. 아래처럼 forwardRef로 변경하고 props에서 ref를 제거하세요.

- import { cn } from '@pinback/design-system/utils';
+ import { forwardRef } from 'react';
+ import { cn } from '@pinback/design-system/utils';

-export interface OptionsMenuButtonProps {
-  ref?: React.Ref<HTMLDivElement>;
-  onEdit?: () => void;
-  onDelete?: () => void;
-  className?: string;
-}
+export interface OptionsMenuButtonProps {
+  onEdit?: () => void;
+  onDelete?: () => void;
+  className?: string;
+}

-export default function OptionsMenuButton({
-  ref,
-  onEdit,
-  onDelete,
-  className,
-}: OptionsMenuButtonProps) {
+const OptionsMenuButton = forwardRef<HTMLDivElement, OptionsMenuButtonProps>(
+  ({ onEdit, onDelete, className }, ref) => {

-}
+});
+
+export default OptionsMenuButton;

Also applies to: 3-8, 16-21, 41-41

⛔ Skipped due to learnings
Learnt from: jjangminii
PR: Pinback-Team/pinback-client#22
File: packages/design-system/src/components/input/Input.tsx:5-9
Timestamp: 2025-08-23T18:37:59.985Z
Learning: React 19에서는 함수 컴포넌트가 ref를 일반 prop으로 받을 수 있다. interface에서 ref: Ref<HTMLInputElement>로 정의하고 함수 컴포넌트에서 ({ ref, ...props }) => 형태로 받을 수 있어 forwardRef가 불필요하다. React 19.1.1+ 프로젝트에서는 이 방식을 사용해야 한다.
Learnt from: jjangminii
PR: Pinback-Team/pinback-client#22
File: packages/design-system/src/components/input/Input.tsx:5-9
Timestamp: 2025-08-23T18:37:59.985Z
Learning: React 19부터 함수 컴포넌트에서 ref를 일반 prop으로 받을 수 있어 forwardRef가 더 이상 필요하지 않다. InputProps interface에서 ref: Ref<HTMLInputElement>로 정의하고 함수 컴포넌트에서 ({ ref, ...props }) => 형태로 받을 수 있다.

@@ -1,10 +1,13 @@
import { Outlet } from 'react-router-dom';
import OptionsMenuButton from '../shared/components/optionsMenuButton/OptionsMenuButton';
Copy link

@coderabbitai coderabbitai bot Aug 27, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue

전역 레이아웃에 옵션 메뉴 상시 렌더링은 부적절

레이아웃에서 <OptionsMenuButton />를 항상 렌더링하면 모든 페이지에 노출되고, 핸들러 없이 동작하지 않는 버튼이 생깁니다. 사이드바/카테고리 아이템 컨텍스트로 이동하거나 상태로 가드하세요. 병합 전에는 제거 권장합니다.

-import OptionsMenuButton from '../shared/components/optionsMenuButton/OptionsMenuButton';
@@
-      <OptionsMenuButton />

Also applies to: 9-9

🤖 Prompt for AI Agents
In apps/client/src/layout/Layout.tsx around lines 2 and 9, the global Layout
always renders <OptionsMenuButton /> which exposes a non-functional button on
every page; remove the unconditional render from the top-level layout, or
replace it with a conditional guard (prop/state) so it only mounts when the
required handlers/contexts are available; alternatively move the component into
the sidebar or the specific category/item component where its event handlers and
context live, and ensure you pass the necessary callback props and state before
reintroducing it.

Copy link
Member

Choose a reason for hiding this comment

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

절대 경로로 수정!

Copy link

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check tsconfig.json configuration for absolute imports
find . -name "tsconfig.json" -exec cat {} \;
echo "---"
# Check if there's a path alias configuration
find . -name "vite.config.*" -o -name "webpack.config.*" -o -name "next.config.*" | head -5 | xargs cat 2>/dev/null || echo "No bundler config found"
echo "---"
# Check package.json for any import configuration
cat package.json 2>/dev/null | jq '.imports // empty' || echo "No imports in package.json"

Length of output: 6374


@constantly-dev, 네! tsconfig.json에서 설정된 절대 경로 alias를 확인했습니다. 다음과 같이 수정해주세요:

import OptionsMenuButton from '@shared/components/optionsMenuButton/OptionsMenuButton';

현재 상대 경로 ../shared/components/optionsMenuButton/OptionsMenuButton@shared/components/optionsMenuButton/OptionsMenuButton로 변경하면 됩니다.

tsconfig.json에 "@shared/*": ["src/shared/*"] 경로 매핑이 설정되어 있고, vite.config.ts에서 tsconfigPaths() 플러그인이 활성화되어 있어 이 절대 경로를 사용할 수 있습니다.

(ノ◕ヮ◕)ノ*:・゚✧


🧠 Learnings used
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 디렉터리를 컨벤션 문서 추가용으로 사용할 예정이라고 명시했습니다.

Copy link
Member

@constantly-dev constantly-dev left a comment

Choose a reason for hiding this comment

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

수고하셨습니다~~
ref로 밖에 클릭 시 닫히는 것 잘 구현해주셨는데, 이외 모달 등도 사용해야 하니 useOutsideClick과 같은 커스텀 훅 제작해도 좋겠네요! 시간나면 작업해보겠습니다~ 👍

@@ -1,10 +1,13 @@
import { Outlet } from 'react-router-dom';
import OptionsMenuButton from '../shared/components/optionsMenuButton/OptionsMenuButton';
Copy link
Member

Choose a reason for hiding this comment

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

절대 경로로 수정!

Comment on lines 5 to 6
onEdit?: () => void;
onDelete?: () => void;
Copy link
Member

Choose a reason for hiding this comment

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

onEditonDelete는 사실상 필수라고 생각이 드는데 쓰지 않는 경우도 존재하나요?

Copy link
Collaborator Author

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 +14
const ITEM_STYLE =
'body4-r text-font-black-1 h-[3.6rem] w-full ' +
'flex items-center justify-center ' +
'hover:bg-gray100 focus-visible:bg-gray100 active:bg-gray200 ' +
'outline-none transition-colors';
Copy link
Member

Choose a reason for hiding this comment

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

버튼 두 개에서 재사용되는 스타일을 분리하신거죠?!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

넵 맞습니다-! 굳이 분리해야할까 싶었지만 코드가 길어져서 가독성이 좋지 않다고 판단되어 분리했습니다

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] select dropdown button 구현

2 participants