Skip to content

Conversation

@yuj2n
Copy link
Contributor

@yuj2n yuj2n commented Jun 13, 2025

📌 변경 사항 개요

공동 사용자 프로필 구현

✨ 요약

공동 사용자 프로필 구현

📝 상세 내용

✨feat:

  • 재사용성을 위해 사용자 프로필과 닉네임 분리
  • 공동 작업자 목록 컴포넌트 구현
  • 한 명의 공동 작업자 아바타 렌더링 구현
  • 사이드바와 연결 및 공동 작업자 프로필 이미지 삽입
  • 대시보드 수정 페이지에 사이드바 적용
  • 공동 작업자 프로필 툴팁 컴포넌트 구현
  • 공동 작업자 프로필에 툴팁 적용
  • 반응형으로 툴팁이 보이지 않던 문제 Portal 적용으로 해결

🫧modify:

  • 바뀐 사용자 정보 컴포넌트 변경
  • 테스트 페이지에 헤더 적용

📁chore: 폴더 구조 변경

🎨style:

  • 헤더 반응형 대비 스타일 수정
  • 헤더에 다크모드 배경색 적용을 위한 스타일 추가
  • 드롭다운 디자인 수정

🐛fix: React의 createPortal API로 드롭다운 툴팁 가려지는 문제 수정

🔗 관련 이슈

🖼️ 스크린샷

20250614_054706.mp4

✅ 체크리스트

  • 브랜치 네이밍 컨벤션을 준수했습니다
  • 커밋 컨벤션을 준수했습니다
  • 코드가 프로젝트의 스타일 가이드라인을 준수합니다

💡 참고 사항

  • Dropdown 컴포넌트에 할 일 카드 모달을 위한 너비 작성해두었습니다! 필요하신 경우에 늘이거나 줄여 쓰시면 될 것 같습니다 : )
  • 툴팁과 드롭다운의 경우 헤더 반응형 구현으로 인해 가려지는 문제가 있어 React의 createPortal API로 해결했습니다!!
  • 반응형에 따른 드롭다운 이동을 위해 ref 객체를 하나 더 추가하였습니다
  • z-index도 가려지는 문제 해결법 중 하나지만 반응형으로 인해 부모 컴포넌트가 overflow-hidden이기 때문에 z-index를 주어도 툴팁 자체가 그 부모 안에 갇혀 있기 때문에 해결 X -> 따라서 Portal 사용이 훨씬 안정적
  • tester 페이지에서 헤더와 사이드 바 적용 확인 가능합니다!!
  • 현재는 한 컴포넌트에 많은 내용이 들어가 있어 가독성이 떨어질 수 있어 추후에 분리할 예정입니다!!

지윤님 질문 답

  // 드롭다운이 열릴 때 위치 계산 및 윈도우 이벤트 바인딩
  useEffect(() => {
    function updateCoords() {
      if (open && triggerRef.current) {
        const rect = triggerRef.current.getBoundingClientRect() // 트리거 위치 측정

        let left = rect.left
        if (align === 'center') left = rect.left + rect.width / 2
        else if (align === 'right') left = rect.right

        setCoords({
          top: rect.bottom + window.scrollY, // 화면 스크롤 반영
          left,
        })
      }
    }
  • 주석에도 달려있듯이 드롭다운이 열릴 때 드롭다운 열림 여부 상태 변수인 open으로 열림 감지 후 triggerRef로 트리거(드롭다운)의 화면 위치 측정 후 align 값에 따라서 좌우 정렬 위치를 계산하고(rect.left → 트리거의 왼쪽에 맞춤 & rect.left + rect.width / 2 → 가운데 맞춤 & rect.right → 오른쪽에 맞춤) 최종적으로 드롭다운의 위치 상태를 업데이트하여 화면 스크롤을 반영하는 흐름입니다.

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features
    • 새로운 아바타(Avatar), 사용자 정보(UserInfo), 협업자 목록(CollaboratorList), 협업자 아이템(CollaboratorItem), 툴팁(Tooltip) 컴포넌트가 추가되었습니다.
    • 대시보드 편집 페이지에 사이드바가 추가되었습니다.
  • Bug Fixes
    • 헤더에서 기존 단일 프로필 이미지 대신 여러 협업자를 표시하는 목록으로 개선되었습니다.
  • Refactor
    • Dropdown 컴포넌트가 React 포털을 사용하도록 변경되어 위치 계산과 외부 클릭 감지가 개선되었습니다.
    • Header 및 관련 레이아웃이 정돈되고 스타일이 향상되었습니다.
  • Chores
    • 불필요한 Profile 컴포넌트가 삭제되었습니다.

@yuj2n yuj2n added this to the 1차 구현 기간 milestone Jun 13, 2025
@yuj2n yuj2n self-assigned this Jun 13, 2025
@yuj2n yuj2n added ✨Feat 기능 개발 🐛Fix 버그 수정 🎨Style UI, 스타일 관련 수정 💄Modify 자잘한 수정 📁Chore 폴더 변경 또는 디렉토리 작업 labels Jun 13, 2025
@coderabbitai
Copy link

coderabbitai bot commented Jun 13, 2025

Walkthrough

이번 변경에서는 Header와 Sidebar 구성 요소가 대시보드 및 테스트 페이지에 추가되고, 사용자 아바타 및 협업자 표시 기능이 신설되었습니다. 기존 Profile 컴포넌트는 Avatar로 대체되었으며, Dropdown, Header, UserDropdown 등에서 레이아웃 및 표시 방식이 개선되었습니다.

Changes

파일/그룹 변경 요약
src/app/dashboard/[id]/edit/layout.tsx, src/app/tester/page.tsx Header 및 Sidebar 컴포넌트 추가, 레이아웃 구조 조정
src/app/shared/components/common/Avatar.tsx Avatar 컴포넌트 신설: 닉네임 기반 이니셜/색상 또는 이미지 아바타 렌더링
src/app/shared/components/common/Profile.tsx Profile 컴포넌트 삭제 (Avatar로 대체)
src/app/shared/components/common/CollaboratorItem.tsx CollaboratorItem 컴포넌트 신설: Avatar와 닉네임 표시, 클릭 핸들러 지원
src/app/shared/components/common/UserInfo.tsx UserInfo 컴포넌트 신설: Avatar와 닉네임 함께 표시
src/app/shared/components/common/header/Collaborator/Tooltip.tsx Tooltip 컴포넌트 신설: 마우스 오버 시 툴팁 표시
src/app/shared/components/common/header/Collaborator/CollaboratorList.tsx CollaboratorList 컴포넌트 신설: 최대 4명 협업자 표시 및 추가 인원 툴팁 처리
src/app/shared/components/common/header/Header.tsx Header 레이아웃/스타일 개선, CollaboratorList 도입
src/app/shared/components/common/header/UserDropdown.tsx Profile → UserInfo로 교체, Dropdown 정렬/너비 및 버튼 패딩 조정
src/app/shared/components/common/Dropdown/Dropdown.tsx Dropdown 포탈 기반 렌더링, 위치/정렬 개선, 외부 클릭 처리 강화

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Header
    participant CollaboratorList
    participant CollaboratorItem
    participant Avatar
    participant Tooltip

    User->>Header: 페이지 진입
    Header->>CollaboratorList: 협업자 목록 렌더링
    CollaboratorList->>CollaboratorItem: 각 협업자 렌더링
    CollaboratorItem->>Avatar: 닉네임/이미지로 아바타 표시
    CollaboratorItem->>Tooltip: (마우스 오버 시) 닉네임 툴팁 표시
Loading

Possibly related PRs

  • ✨ feat: 사이드바 컴포넌트 구현 #42: Sidebar 컴포넌트와 대시보드 내비게이션 UI 및 타입 구현 PR로, 이번 변경에서 Sidebar가 실제로 대시보드 레이아웃에 도입된 점과 직접적으로 연결됩니다.

Suggested reviewers

  • Insung-Jo
  • LeeCh0129

Poem

🐇
새 Header와 Sidebar가 등장했네,
아바타 속 이니셜이 반짝이며 웃고,
협업자 리스트가 줄지어 인사해요.
툴팁은 살짝, 닉네임을 속삭이고,
변화의 바람에 토끼도 깡총!
오늘도 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.

npm error Exit handler never called!
npm error This is an error with npm itself. Please report this error at:
npm error https://github.com/npm/cli/issues
npm error A complete log of this run can be found in: /.npm/_logs/2025-06-13T21_11_08_181Z-debug-0.log

✨ 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: 6

🧹 Nitpick comments (9)
src/app/shared/components/common/header/Collaborator/Tooltip.tsx (1)

57-62: 접근성 ‑ role/aria 속성 추가 권장

스크린리더 사용자를 위해 툴팁 컨테이너에 role="tooltip" 및 트리거 요소에 aria-describedby를 연결해 주시면 접근성이 향상됩니다.

src/app/shared/components/common/Avatar.tsx (1)

41-52: Image 컴포넌트에 sizes 지정 권장

Next.js Imagesizes 속성이 없으면 LCP 최적화가 제한됩니다. 예상 레이아웃 폭이 고정이라면 sizes="36px"(또는 비율에 맞게) 등을 지정해주세요.

src/app/shared/components/common/Dropdown/Dropdown.tsx (1)

46-53: 가로 스크롤에 따른 좌표 오차

left 계산 시 window.scrollX가 빠져 있습니다. 툴팁과 동일 문제로, 가로 스크롤 환경에서 드롭다운이 어긋날 수 있습니다.

src/app/shared/components/common/header/UserDropdown.tsx (1)

26-28: 드롭다운 너비가 너무 좁아 텍스트가 줄바꿈됩니다

width="w-6"getWidthValue에서 6rem(≈96 px)로 매핑되어 한글 두 줄 버튼 텍스트가 줄바꿈될 수 있습니다. 최소 8–10 rem 정도로 확장하거나 자동 너비(min-w-max)를 고려해주세요.

src/app/shared/components/common/UserInfo.tsx (1)

11-18: className prop 누락으로 재사용성 제한

다른 곳에서 스타일을 추가하고 싶어도 UserInfo 자체에 className을 전달할 수 없습니다. Avatar 컴포넌트와 동일한 패턴으로 className?: string을 받아서 래퍼 div에 전달하면 재사용성이 높아집니다.

-type UserInfoProps = {
+type UserInfoProps = {
   nickname: string
   imageUrl?: string
   size?: number
+  className?: string
 }
 
-export function UserInfo({ nickname, imageUrl, size = 36 }: UserInfoProps) {
-  return (
-    <div className="flex items-center gap-4">
+export function UserInfo({ nickname, imageUrl, size = 36, className }: UserInfoProps) {
+  return (
+    <div className={cn('flex items-center gap-4', className)}>
       <Avatar nickname={nickname} imageUrl={imageUrl} size={size} />
       <span className="text-sm font-semibold">{nickname}</span>
     </div>
   )
 }
src/app/shared/components/common/CollaboratorItem.tsx (2)

18-24: gap-3 클래스 불필요

컨테이너 안에 Avatar만 존재하므로 gap-3은 의미 없습니다. 삭제하면 불필요한 margin 계산을 줄일 수 있습니다.

-    <div className={cn('flex items-center gap-3', className)} onClick={onClick}>
+    <div className={cn('flex items-center', className)} onClick={onClick}>

7-13: forwardRef 미지원

다른 컴포넌트에서 이 요소의 DOM 참조가 필요할 수 있으므로 forwardRef 패턴 적용을 고려하세요.
지금 당장은 필요 없어 보이지만 협업자 아바타 클릭 시 포커스 관리, 접근성 향상 등에 유용합니다.

src/app/shared/components/common/header/Header.tsx (1)

56-59: 시각적 구분 기호 | 의 접근성

수직 막대 |만 Text 노드로 존재해 스크린리더가 ‘vertical bar’로 읽을 수 있습니다.
aria-hidden 속성을 주거나 의미 없는 장식은 CSS ::before 활용으로 대체하세요.

- <UserDropdown />|{/* 다크모드 토글 */}
+ <UserDropdown />
+ <span aria-hidden="true" className="mx-4 select-none text-gray-300">|</span>
+ {/* 다크모드 토글 */}
src/app/shared/components/common/header/Collaborator/CollaboratorList.tsx (1)

23-27: extraCount 음수 처리 불필요하지만 가드 추가 권장

현재 로직상 음수일 때 렌더링되지 않지만, 명시적으로 Math.max(0, length - MAX_VISIBLE) 로 계산하면 의도 전달이 명확해집니다.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between bc76955 and 03c1917.

📒 Files selected for processing (11)
  • src/app/dashboard/[id]/edit/layout.tsx (1 hunks)
  • src/app/shared/components/common/Avatar.tsx (1 hunks)
  • src/app/shared/components/common/CollaboratorItem.tsx (1 hunks)
  • src/app/shared/components/common/Dropdown/Dropdown.tsx (1 hunks)
  • src/app/shared/components/common/Profile.tsx (0 hunks)
  • src/app/shared/components/common/UserInfo.tsx (1 hunks)
  • src/app/shared/components/common/header/Collaborator/CollaboratorList.tsx (1 hunks)
  • src/app/shared/components/common/header/Collaborator/Tooltip.tsx (1 hunks)
  • src/app/shared/components/common/header/Header.tsx (1 hunks)
  • src/app/shared/components/common/header/UserDropdown.tsx (2 hunks)
  • src/app/tester/page.tsx (2 hunks)
💤 Files with no reviewable changes (1)
  • src/app/shared/components/common/Profile.tsx
🧰 Additional context used
🧬 Code Graph Analysis (6)
src/app/dashboard/[id]/edit/layout.tsx (1)
src/app/shared/components/common/sidebar/Sidebar.tsx (1)
  • Sidebar (10-110)
src/app/shared/components/common/UserInfo.tsx (1)
src/app/shared/components/common/Avatar.tsx (1)
  • Avatar (37-66)
src/app/shared/components/common/header/Header.tsx (4)
src/app/shared/lib/cn.ts (1)
  • cn (4-6)
src/app/shared/components/common/header/Collaborator/CollaboratorList.tsx (1)
  • CollaboratorList (22-56)
src/app/shared/components/common/header/UserDropdown.tsx (1)
  • UserDropdown (8-43)
src/app/shared/components/ThemeToggle.tsx (1)
  • ThemeToggle (6-27)
src/app/shared/components/common/CollaboratorItem.tsx (2)
src/app/shared/lib/cn.ts (1)
  • cn (4-6)
src/app/shared/components/common/Avatar.tsx (1)
  • Avatar (37-66)
src/app/shared/components/common/header/Collaborator/CollaboratorList.tsx (2)
src/app/shared/components/common/header/Collaborator/Tooltip.tsx (1)
  • Tooltip (11-67)
src/app/shared/components/common/CollaboratorItem.tsx (1)
  • CollaboratorItem (15-27)
src/app/shared/components/common/header/UserDropdown.tsx (1)
src/app/shared/components/common/UserInfo.tsx (1)
  • UserInfo (11-18)
🔇 Additional comments (5)
src/app/shared/components/common/header/Collaborator/Tooltip.tsx (1)

19-27: 좌표 계산이 스크롤/리사이즈에 따라 갱신되지 않습니다.

툴팁이 열린 상태에서 사용자가 스크롤(특히 모바일)·창 리사이즈를 하면 위치가 어긋납니다. Dropdown처럼 scroll/resize 이벤트 리스너를 추가해 updateCoords를 호출하도록 개선해 주세요.
[ suggest_essential_refactor ]

src/app/shared/components/common/Avatar.tsx (1)

30-35: Tailwind 색상값과의 불일치 가능성

customColors는 임의 hex 값입니다. 다크 모드 배경과 대비가 불충분할 수 있으니 디자인팀과 색상 대비를 재확인해 주세요.

src/app/shared/components/common/UserInfo.tsx (1)

13-15: LGTM – 핵심 로직 이상 없음

Avatar 이용, 닉네임 표시 모두 정상적으로 동작합니다.

src/app/tester/page.tsx (1)

24-25: Header 아래 여백 고려 필요

Header 자체가 pl-300 을 가지고 있어 사이드바 폭(300)과 중복될 수 있습니다.
현재 <div className="flex"> 바로 아래에 사이드바가 오면서 또다시 ml-300을 주고 있어, 작은 화면에서 수평 스크롤이 생길 가능성이 있습니다. 레이아웃 좌우 패딩을 한 곳에서만 관리하는 편이 안전합니다.

src/app/shared/components/common/header/Header.tsx (1)

19-23: 대시보드명 하드코딩

'대시보드 명' 텍스트와 왕관 이미지는 동적 데이터로 대체될 가능성이 큽니다. prop 또는 context로 주입하도록 TODO 댓글이라도 남겨두세요.

Comment on lines 33 to 36
position: 'absolute',
left: coords.left,
top: coords.top,
transform: 'translateX(-50%)',
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

가로 스크롤 보정 누락

left 계산 시 window.scrollX가 반영되지 않아 가로 스크롤이 있는 페이지에서 위치가 밀릴 수 있습니다.

- left: coords.left,
+ left: coords.left + window.scrollX,
📝 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
position: 'absolute',
left: coords.left,
top: coords.top,
transform: 'translateX(-50%)',
position: 'absolute',
- left: coords.left,
+ left: coords.left + window.scrollX,
top: coords.top,
transform: 'translateX(-50%)',
🤖 Prompt for AI Agents
In src/app/shared/components/common/header/Collaborator/Tooltip.tsx around lines
33 to 36, the left position is set using coords.left without accounting for
horizontal scrolling. To fix this, add window.scrollX to coords.left when
setting the left style property to correctly position the tooltip on pages with
horizontal scroll.

Comment on lines 27 to 39
const getWidthValue = (width: string) => {
switch (width) {
case 'w-5':
{
/* 할 일 카드 모달 너비*/
}
return '5rem'
case 'w-6':
return '6rem'
default:
return undefined
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

getWidthValue가 대부분의 width 프리셋을 처리하지 못합니다

현재 'w-48' (default) 등 Tailwind 클래스를 전달하면 반환값이 undefined가 되어 드롭다운 너비가 의도치않게 내용물에 종속됩니다. 필요 프리셋을 모두 매핑하거나, 직접 rem 값을 받도록 단순화하는 편이 안전합니다.

🤖 Prompt for AI Agents
In src/app/shared/components/common/Dropdown/Dropdown.tsx between lines 27 and
39, the getWidthValue function only handles a few width presets and returns
undefined for most Tailwind width classes like 'w-48', causing the dropdown
width to depend on content unexpectedly. To fix this, either extend the switch
statement to cover all necessary Tailwind width classes with their corresponding
rem values or simplify the function to accept direct rem values as input,
ensuring the dropdown width is always set correctly.

Comment on lines 11 to 15
<div>
<Sidebar />
<Header />
<div>{children}</div> {/* 여기에 page.tsx 내용이 들어옴 */}
</div>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

콘텐츠가 사이드바 아래로 겹칠 가능성

Sidebarfixed left-0 w-300으로 배치되는데, 레이아웃 컨테이너에 좌측 패딩이 없어 본문이 가려질 수 있습니다.

- <div>
+ <div className="pl-300">

같은 방식으로 오프셋을 주거나 CSS Grid 레이아웃을 권장합니다.

📝 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
<div>
<Sidebar />
<Header />
<div>{children}</div> {/* 여기에 page.tsx 내용이 들어옴 */}
</div>
<div className="pl-300">
<Sidebar />
<Header />
<div>{children}</div> {/* 여기에 page.tsx 내용이 들어옴 */}
</div>
🤖 Prompt for AI Agents
In src/app/dashboard/[id]/edit/layout.tsx around lines 11 to 15, the main
content div does not have left padding or margin to offset the fixed Sidebar,
causing the content to be overlapped by the Sidebar. To fix this, add left
padding or margin to the container wrapping the children equal to the Sidebar's
width, or refactor the layout to use CSS Grid to properly allocate space for the
Sidebar and main content.

Comment on lines +28 to +47
<nav className="Text-gray hidden gap-8 text-sm md:flex">
<Link
href="/dashboard"
className={cn(
'Border-btn flex items-center gap-6 rounded-md border px-12 py-6',
pathname === '/dashboard' && 'font-semibold',
)}
>
<div className="relative flex size-12">
<Image src="/images/management.png" fill alt="관리 버튼" />
</div>
관리
</Link>
<Link
href="/modal"
className={cn(
'Border-btn mr-16 flex items-center gap-6 rounded-md border px-12 py-6',
pathname === '/modal' && 'font-semibold',
)}
>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

경로 비교 로직이 세분화 URL을 처리하지 못함

pathname === '/dashboard' 비교는 /dashboard/123 같은 서브경로에서 비활성 처리됩니다. startsWith 또는 정규식으로 교체하면 하위 경로까지 활성화 표시를 유지할 수 있습니다.

- pathname === '/dashboard' && 'font-semibold',
+ pathname?.startsWith('/dashboard') && 'font-semibold',
📝 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
<nav className="Text-gray hidden gap-8 text-sm md:flex">
<Link
href="/dashboard"
className={cn(
'Border-btn flex items-center gap-6 rounded-md border px-12 py-6',
pathname === '/dashboard' && 'font-semibold',
)}
>
<div className="relative flex size-12">
<Image src="/images/management.png" fill alt="관리 버튼" />
</div>
관리
</Link>
<Link
href="/modal"
className={cn(
'Border-btn mr-16 flex items-center gap-6 rounded-md border px-12 py-6',
pathname === '/modal' && 'font-semibold',
)}
>
<nav className="Text-gray hidden gap-8 text-sm md:flex">
<Link
href="/dashboard"
className={cn(
'Border-btn flex items-center gap-6 rounded-md border px-12 py-6',
pathname?.startsWith('/dashboard') && 'font-semibold',
)}
>
<div className="relative flex size-12">
<Image src="/images/management.png" fill alt="관리 버튼" />
</div>
관리
</Link>
<Link
href="/modal"
className={cn(
'Border-btn mr-16 flex items-center gap-6 rounded-md border px-12 py-6',
pathname === '/modal' && 'font-semibold',
)}
>
🤖 Prompt for AI Agents
In src/app/shared/components/common/header/Header.tsx between lines 28 and 47,
the current pathname comparison uses strict equality (pathname ===
'/dashboard'), which fails to activate the link for subpaths like
'/dashboard/123'. Replace this equality check with a startsWith method or a
regular expression to ensure the link remains active for all subpaths under
'/dashboard'.

Comment on lines +6 to +13
export const mockCollaborators = [
{ nickname: '홍길동', imageUrl: '/images/collaborator.png' },
{ nickname: '김철수', imageUrl: '/images/collaborator.png' },
{ nickname: '이영희', imageUrl: '/images/collaborator.png' },
{ nickname: '뚜비', imageUrl: '/images/collaborator.png' },
{ nickname: '두룹', imageUrl: '/images/collaborator.png' },
{ nickname: 'ㅋ', imageUrl: '/images/collaborator.png' },
]
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

목 데이터 기본값 사용 주의

기본값으로 mockCollaborators를 노출하면 실제 데이터가 없을 때 가짜 사용자가 화면에 나타나 버그로 오인될 수 있습니다. 필수 prop으로 변경하거나 개발 모드에서만 주입하세요.

🤖 Prompt for AI Agents
In src/app/shared/components/common/header/Collaborator/CollaboratorList.tsx
lines 6 to 13, the mockCollaborators array is exported and used as a default
value, which can cause fake users to appear in production and be mistaken for
bugs. To fix this, remove the default export of mockCollaborators as a default
prop, require the collaborators prop explicitly, and only inject
mockCollaborators in development mode for testing purposes.

Comment on lines 31 to 33
{visibleCollaborators.map((collab) => (
<Tooltip key={collab.nickname} content={collab.nickname}>
<div className="flex flex-col items-center text-xs">
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

key 충돌 가능성

nickname이 동일한 사용자가 여러 명이면 React key 충돌이 발생합니다. index를 보조로 사용하거나 uuid를 받아오도록 수정하세요.

- {visibleCollaborators.map((collab) => (
-   <Tooltip key={collab.nickname} content={collab.nickname}>
+ {visibleCollaborators.map((collab, idx) => (
+   <Tooltip key={`${collab.nickname}-${idx}`} content={collab.nickname}>
📝 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
{visibleCollaborators.map((collab) => (
<Tooltip key={collab.nickname} content={collab.nickname}>
<div className="flex flex-col items-center text-xs">
{visibleCollaborators.map((collab, idx) => (
<Tooltip key={`${collab.nickname}-${idx}`} content={collab.nickname}>
<div className="flex flex-col items-center text-xs">
🤖 Prompt for AI Agents
In src/app/shared/components/common/header/Collaborator/CollaboratorList.tsx
around lines 31 to 33, the React key uses collab.nickname which can cause key
collisions if multiple users share the same nickname. To fix this, modify the
key to include a unique identifier such as a UUID if available, or alternatively
append the index from the map function to the nickname to ensure uniqueness.

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

♻️ Duplicate comments (1)
src/app/shared/components/common/Dropdown/Dropdown.tsx (1)

30-40: getWidthValue 함수 범용성 여전히 부족

이전 리뷰에서 이미 지적된 내용이지만('w-48' 등 대부분의 프리셋 미지원), 구현이 그대로입니다. 미해결된 동일 이슈이므로 재확인 바랍니다.

🧹 Nitpick comments (3)
src/app/shared/components/common/Dropdown/Dropdown.tsx (3)

116-116: 배경 클래스 대소문자 오류

Tailwind 클래스는 소문자 bg-white입니다. 현재 BG-white는 무효 클래스라 배경색이 적용되지 않습니다.

-          className="BG-white"
+          className="bg-white"

44-57: resize/scroll 이벤트 핸들링 스로틀링 권장

빈번한 리사이즈·스크롤 시 updateCoords가 매 프레임 호출돼 성능 저하 위험이 있습니다. requestAnimationFrame 또는 lodash.throttle로 간단히 스로틀링을 적용하세요.


97-119: 접근성(ARIA) 속성 누락

드롭다운·트리거에 role="menu" / aria-haspopup="true" / aria-expanded 등을 지정하면 스크린리더 지원이 개선됩니다. 고려해 주세요.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 03c1917 and abdd43a.

📒 Files selected for processing (4)
  • src/app/shared/components/common/Dropdown/Dropdown.tsx (1 hunks)
  • src/app/shared/components/common/header/Collaborator/CollaboratorList.tsx (1 hunks)
  • src/app/shared/components/common/header/Collaborator/Tooltip.tsx (1 hunks)
  • src/app/shared/components/common/header/Header.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/app/shared/components/common/header/Header.tsx
  • src/app/shared/components/common/header/Collaborator/CollaboratorList.tsx
  • src/app/shared/components/common/header/Collaborator/Tooltip.tsx

Comment on lines +71 to +88
// 드롭다운 외부 클릭 시 닫기
useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (
menuRef.current &&
!menuRef.current.contains(event.target as Node) &&
triggerRef.current &&
!triggerRef.current.contains(event.target as Node)
) {
setOpen(false)
}
}

if (open) {
document.addEventListener('mousedown', handleClickOutside)
} else {
document.removeEventListener('mousedown', handleClickOutside)
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

모바일 환경 외부 터치 감지 누락

mousedown만 등록되어 터치 디바이스에서 닫힘이 동작하지 않습니다. touchstart도 함께 바인딩해 주세요.

-      document.addEventListener('mousedown', handleClickOutside)
+      document.addEventListener('mousedown', handleClickOutside)
+      document.addEventListener('touchstart', handleClickOutside, { passive: true })
 ...
-      document.removeEventListener('mousedown', handleClickOutside)
+      document.removeEventListener('mousedown', handleClickOutside)
+      document.removeEventListener('touchstart', handleClickOutside)
📝 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
// 드롭다운 외부 클릭 시 닫기
useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (
menuRef.current &&
!menuRef.current.contains(event.target as Node) &&
triggerRef.current &&
!triggerRef.current.contains(event.target as Node)
) {
setOpen(false)
}
}
if (open) {
document.addEventListener('mousedown', handleClickOutside)
} else {
document.removeEventListener('mousedown', handleClickOutside)
}
// 드롭다운 외부 클릭 시 닫기
useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (
menuRef.current &&
!menuRef.current.contains(event.target as Node) &&
triggerRef.current &&
!triggerRef.current.contains(event.target as Node)
) {
setOpen(false)
}
}
if (open) {
document.addEventListener('mousedown', handleClickOutside)
document.addEventListener('touchstart', handleClickOutside, { passive: true })
} else {
document.removeEventListener('mousedown', handleClickOutside)
document.removeEventListener('touchstart', handleClickOutside)
}
}, [open])
🤖 Prompt for AI Agents
In src/app/shared/components/common/Dropdown/Dropdown.tsx around lines 71 to 88,
the event listener only listens for 'mousedown' events, which causes the
dropdown not to close on touch devices. To fix this, add an event listener for
'touchstart' alongside 'mousedown' when the dropdown is open, and remove both
listeners when it is closed, ensuring touch interactions also trigger the close
behavior.

Comment on lines +34 to +36
return '5rem'
case 'w-6':
return '6rem'
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Tailwind 스케일과 불일치한 rem 매핑

'w-6' → 1.5 rem이 맞지만 6 rem으로 잘못 매핑되어 UI 가로폭이 4배 이상 넓어집니다. 숫자를 직접 변환하도록 로직을 일반화하세요.

-      case 'w-6':
-        return '6rem'
+      case 'w-6':
+        return '1.5rem'
+
+      // tailwind `w-{n}` => n*0.25rem 기본 스케일 처리 예시
+      default: {
+        const match = width.match(/^w-(\d+)$/)
+        if (match) return `${Number(match[1]) * 0.25}rem`
+        return undefined
+      }
📝 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
return '5rem'
case 'w-6':
return '6rem'
return '5rem'
case 'w-6':
return '1.5rem'
// tailwind `w-{n}` => n*0.25rem 기본 스케일 처리 예시
default: {
const match = width.match(/^w-(\d+)$/)
if (match) return `${Number(match[1]) * 0.25}rem`
return undefined
}
🤖 Prompt for AI Agents
In src/app/shared/components/common/Dropdown/Dropdown.tsx around lines 34 to 36,
the mapping from Tailwind width classes to rem values is incorrect, specifically
'w-6' is mapped to '6rem' instead of the correct '1.5rem'. Fix this by
generalizing the logic to convert the numeric part of the class (e.g., 6) to rem
by multiplying it by 0.25, ensuring all width classes map correctly to their
Tailwind rem equivalents.

Copy link
Contributor

@dkslel1225 dkslel1225 left a comment

Choose a reason for hiding this comment

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

공동 사용자 프로필 구현 수고하셨습니다!
Dropdown.tsx에서, 반응형에 따른 위치 조정이 어떻게 이루어지는지 흐름에 대해 간단하게 설명해주실수 있을까용

Copy link

@Insung-Jo Insung-Jo 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 +96 to +121
const menu = open
? createPortal(
<div
ref={menuRef}
style={{
position: 'absolute',
top: coords.top,
left: coords.left,
transform:
align === 'center'
? 'translateX(-50%)'
: align === 'right'
? 'translateX(-100%)'
: undefined,
width: getWidthValue(width),
minWidth: getWidthValue(width),
boxShadow: '0 2px 8px rgba(0,0,0,0.15)', // 그림자
borderRadius: 8, // 모서리 둥글게
zIndex: 9999, // 위로 띄우기
}}
className="BG-white" // 커스텀 배경 클래스 (Tailwind 예시)
>
{children}
</div>,
document.body, // body에 포탈로 삽입
)

Choose a reason for hiding this comment

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

createPortal이 어떤 역할을 해주는 건가요?

@yuj2n yuj2n merged commit 7fbb797 into develop Jun 14, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

📁Chore 폴더 변경 또는 디렉토리 작업 ✨Feat 기능 개발 🐛Fix 버그 수정 💄Modify 자잘한 수정 🎨Style UI, 스타일 관련 수정

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants