-
Notifications
You must be signed in to change notification settings - Fork 2
✨Feat: 공동 사용자 프로필 구현 #51
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
Conversation
Walkthrough이번 변경에서는 Header와 Sidebar 구성 요소가 대시보드 및 테스트 페이지에 추가되고, 사용자 아바타 및 협업자 표시 기능이 신설되었습니다. 기존 Profile 컴포넌트는 Avatar로 대체되었으며, Dropdown, Header, UserDropdown 등에서 레이아웃 및 표시 방식이 개선되었습니다. Changes
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: (마우스 오버 시) 닉네임 툴팁 표시
Possibly related PRs
Suggested reviewers
Poem
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
npm error Exit handler never called! ✨ Finishing Touches
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed 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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this 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
Image는sizes속성이 없으면 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:classNameprop 누락으로 재사용성 제한다른 곳에서 스타일을 추가하고 싶어도
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
📒 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 댓글이라도 남겨두세요.
| position: 'absolute', | ||
| left: coords.left, | ||
| top: coords.top, | ||
| transform: 'translateX(-50%)', |
There was a problem hiding this comment.
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.
| 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.
| const getWidthValue = (width: string) => { | ||
| switch (width) { | ||
| case 'w-5': | ||
| { | ||
| /* 할 일 카드 모달 너비*/ | ||
| } | ||
| return '5rem' | ||
| case 'w-6': | ||
| return '6rem' | ||
| default: | ||
| return undefined | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
| <div> | ||
| <Sidebar /> | ||
| <Header /> | ||
| <div>{children}</div> {/* 여기에 page.tsx 내용이 들어옴 */} | ||
| </div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
콘텐츠가 사이드바 아래로 겹칠 가능성
Sidebar가 fixed 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.
| <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.
| <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', | ||
| )} | ||
| > |
There was a problem hiding this comment.
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.
| <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'.
| 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' }, | ||
| ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
목 데이터 기본값 사용 주의
기본값으로 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.
| {visibleCollaborators.map((collab) => ( | ||
| <Tooltip key={collab.nickname} content={collab.nickname}> | ||
| <div className="flex flex-col items-center text-xs"> |
There was a problem hiding this comment.
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.
| {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.
There was a problem hiding this 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
📒 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
| // 드롭다운 외부 클릭 시 닫기 | ||
| 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) | ||
| } |
There was a problem hiding this comment.
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.
| // 드롭다운 외부 클릭 시 닫기 | |
| 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.
| return '5rem' | ||
| case 'w-6': | ||
| return '6rem' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
공동 사용자 프로필 구현 수고하셨습니다!
Dropdown.tsx에서, 반응형에 따른 위치 조정이 어떻게 이루어지는지 흐름에 대해 간단하게 설명해주실수 있을까용
Insung-Jo
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
구현 수고 많으셨습니다! 궁금한 내용은 리뷰로 남겼습니다~
| 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에 포탈로 삽입 | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
createPortal이 어떤 역할을 해주는 건가요?
📌 변경 사항 개요
공동 사용자 프로필 구현
✨ 요약
공동 사용자 프로필 구현
📝 상세 내용
✨feat:
🫧modify:
📁chore: 폴더 구조 변경
🎨style:
🐛fix: React의 createPortal API로 드롭다운 툴팁 가려지는 문제 수정
🔗 관련 이슈
🖼️ 스크린샷
20250614_054706.mp4
✅ 체크리스트
💡 참고 사항
Dropdown컴포넌트에 할 일 카드 모달을 위한 너비 작성해두었습니다! 필요하신 경우에 늘이거나 줄여 쓰시면 될 것 같습니다 : )createPortalAPI로 해결했습니다!!ref객체를 하나 더 추가하였습니다z-index도 가려지는 문제 해결법 중 하나지만 반응형으로 인해 부모 컴포넌트가overflow-hidden이기 때문에z-index를 주어도 툴팁 자체가 그 부모 안에 갇혀 있기 때문에 해결 X -> 따라서Portal사용이 훨씬 안정적지윤님 질문 답
rect.left → 트리거의 왼쪽에 맞춤&rect.left + rect.width / 2 → 가운데 맞춤&rect.right → 오른쪽에 맞춤) 최종적으로 드롭다운의 위치 상태를 업데이트하여 화면 스크롤을 반영하는 흐름입니다.Summary by CodeRabbit
Summary by CodeRabbit