Feat(client): select dropdown button 구현#47
Conversation
Walkthrough레이아웃에 새 OptionsMenuButton 컴포넌트가 추가되고 Layout은 Outlet 앞에 해당 버튼을 렌더링합니다. OptionsMenuButton은 "수정하기"/"삭제하기" 버튼을 노출하며 각각 onEdit/onDelete 콜백을 호출하도록 props를 받습니다. Changes
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
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Assessment against linked issues
Suggested labels
Suggested reviewers
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 detailsConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
|
✅ Storybook chromatic 배포 확인: |
There was a problem hiding this comment.
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.
📒 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 }) => 형태로 받을 수 있다.
apps/client/src/layout/Layout.tsx
Outdated
| @@ -1,10 +1,13 @@ | |||
| import { Outlet } from 'react-router-dom'; | |||
| import OptionsMenuButton from '../shared/components/optionsMenuButton/OptionsMenuButton'; | |||
There was a problem hiding this comment.
전역 레이아웃에 옵션 메뉴 상시 렌더링은 부적절
레이아웃에서 <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.
There was a problem hiding this comment.
🧩 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 디렉터리를 컨벤션 문서 추가용으로 사용할 예정이라고 명시했습니다.
constantly-dev
left a comment
There was a problem hiding this comment.
수고하셨습니다~~
ref로 밖에 클릭 시 닫히는 것 잘 구현해주셨는데, 이외 모달 등도 사용해야 하니 useOutsideClick과 같은 커스텀 훅 제작해도 좋겠네요! 시간나면 작업해보겠습니다~ 👍
apps/client/src/layout/Layout.tsx
Outdated
| @@ -1,10 +1,13 @@ | |||
| import { Outlet } from 'react-router-dom'; | |||
| import OptionsMenuButton from '../shared/components/optionsMenuButton/OptionsMenuButton'; | |||
| onEdit?: () => void; | ||
| onDelete?: () => void; |
There was a problem hiding this comment.
onEdit과 onDelete는 사실상 필수라고 생각이 드는데 쓰지 않는 경우도 존재하나요?
There was a problem hiding this comment.
생각해보니 이 부분은 필수인데 옵셔널로 처리해뒀네요 수정하겠습니다-!!
| 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'; |
There was a problem hiding this comment.
버튼 두 개에서 재사용되는 스타일을 분리하신거죠?!
There was a problem hiding this comment.
넵 맞습니다-! 굳이 분리해야할까 싶었지만 코드가 길어져서 가독성이 좋지 않다고 판단되어 분리했습니다
📌 Related Issues
📄 Tasks
*select dropdown button 구현
⭐ PR Point (To Reviewer)
📷 Screenshot
Summary by CodeRabbit
신기능
접근성
스타일