[feat] 채팅 목록 UI 및 헤더 레이아웃 개선#262
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (2)
Walkthrough헤더 컴포넌트들(여러 하위 헤더 및 Header 인덱스/타입)에 optional Possibly related PRs
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Pull request overview
Refactors the app layout/header system to compute the content top inset from the actual rendered header height (instead of fixed Tailwind padding classes), and updates the chat list page UI (list spacing, context menu, and modals) to match the new design for Issue #261.
Changes:
- Introduce a measured header inset (
useLayoutHeaderInset) and apply it asmainpadding-top; propagate aheaderRefthrough all header variants. - Remove the old header “contentPaddingClassName” mechanism and unify header implementations around
BackTitleHeader. - Update chat list UI (row padding, context menu styling, leave/rename modals) and add a new search icon asset.
Reviewed changes
Copilot reviewed 18 out of 19 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/pages/Chat/index.tsx | Chat list layout + leave/rename modals updated; removes bottom spacer usage. |
| src/pages/Chat/components/ChatRoomContextMenu.tsx | Context menu sizing/styling and click handling updated. |
| src/components/layout/index.tsx | Applies dynamic header inset to <main> and passes headerRef into Header. |
| src/components/layout/hooks/useLayoutHeaderInset.ts | New hook to measure header height (ResizeObserver + viewport resize listeners). |
| src/components/layout/Header/types.ts | Extends header render context to include headerRef. |
| src/components/layout/Header/routeTitles.ts | Adds /chats title mapping. |
| src/components/layout/Header/presentation.ts | Removes fixed padding-class approach from header presentation. |
| src/components/layout/Header/index.tsx | Routes headerRef into all header renderers. |
| src/components/layout/Header/components/* | Header variants updated to accept and attach headerRef (directly or via BackTitleHeader). |
| src/assets/svg/big-search-icon.svg | Adds new search icon asset used by chat list header. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -29,29 +29,33 @@ export default function ChatRoomContextMenu({ x, y, title, items, onClose }: Cha | |||
| const adjustedX = x + MENU_WIDTH > window.innerWidth ? x - MENU_WIDTH : x; | |||
| const adjustedY = y + menuHeight > window.innerHeight ? y - menuHeight : y; | |||
|
|
|||
There was a problem hiding this comment.
adjustedY is computed from menuHeight using fixed constants, but the rendered menu no longer has fixed header/item heights (gap-based layout, different paddings, no explicit height). This can place the menu partially off-screen near the bottom because the real height can differ from menuHeight. Consider measuring the actual menu height via menuRef.current.getBoundingClientRect().height (after render) and using that for adjustment, or reintroducing explicit sizing that matches the constants.
| <BottomModal isOpen={changeRoomName !== null} onClose={() => setChangeRoomName(null)} className="px-6 py-4"> | ||
| <div className="mb-11 flex items-center"> | ||
| <button className="fixed" type="button" aria-label="닫기" onClick={() => setChangeRoomName(null)}> | ||
| <ChevronLeftIcon /> | ||
| </button> | ||
| <div className="px-30 text-center font-semibold">이름 변경</div> | ||
| <div className="text-text-700 w-full text-center leading-[1.6] font-semibold">이름 변경</div> |
There was a problem hiding this comment.
The close button in the BottomModal header is styled with position: fixed but without any inset coordinates. This removes it from the flex layout and relies on the browser’s static-position behavior for fixed elements, which can lead to inconsistent placement and overlap across devices. Prefer positioning it within the modal header (e.g., make the header container relative and use absolute/sticky with explicit left/top), or keep it in-flow and reserve space so the title stays centered.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (7)
src/pages/Chat/components/ChatRoomContextMenu.tsx (2)
43-43: Typography 토큰 사용 고려
text-[14px] leading-[1.6]이 반복 사용되고 있습니다. 코딩 가이드라인에 따르면text-body1,text-sub2등 semantic typography 유틸리티 사용이 권장됩니다.디자인 시스템에 맞는 토큰이 있다면 교체를 고려해 주세요.
Also applies to: 51-51
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Chat/components/ChatRoomContextMenu.tsx` at line 43, The component ChatRoomContextMenu uses hardcoded utility classes `text-[14px] leading-[1.6]` (e.g., the title div rendering `{title}` and the similar subtitle line) which should be replaced with the design-system semantic typography tokens (e.g., `text-body1`, `text-sub2` or the equivalent in our token set); update both occurrences in ChatRoomContextMenu (the title div and the other text div at the indicated spots) to use the semantic utility classes, ensuring you pick tokens that match the original size/line-height and that the tokens exist in the project (or add them to the token map if missing).
32-35:onClose()중복 호출 가능성 있음
clickItemHandler가 항상onClose()를 호출하지만,contextMenuItems의 일부 항목(예: "알림 끄기/켜기")은onClick내부에서 이미setContextMenu(null)을 호출하고 있습니다. 결과적으로 동일한 상태 setter가 연속 2회 호출됩니다.기능상 문제는 없지만, 메뉴 닫기 책임을 한 곳으로 통일하는 것이 좋습니다:
- Option A: 부모의
contextMenuItems에서setContextMenu(null)제거 (권장 - 이 컴포넌트에서 닫기 책임 담당)- Option B:
clickItemHandler에서onClose()제거하고 각 item에서 직접 처리♻️ Option A - 부모 컴포넌트 수정 권장
// src/pages/Chat/index.tsx - contextMenuItems 수정 { label: room.isMuted ? '알림 켜기' : '알림 끄기', onClick: () => { toggleMute(room.roomId); // setContextMenu(null); ← 제거 (ChatRoomContextMenu에서 처리) }, },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Chat/components/ChatRoomContextMenu.tsx` around lines 32 - 35, The clickItemHandler currently always calls onClose(), which can duplicate setContextMenu(null) when some contextMenuItems (e.g., the toggle mute item in the parent) call setContextMenu(null) themselves; pick Option A (recommended) and remove setContextMenu(null) from the parent contextMenuItems so ChatRoomContextMenu exclusively handles closing. Concretely, edit the parent contextMenuItems (where labels like '알림 끄기/켜기' and handlers such as toggleMute(room.roomId) are defined) to remove any explicit setContextMenu(null) calls, leaving clickItemHandler and onClose in ChatRoomContextMenu as the single place that closes the menu. Ensure no other item handlers call setContextMenu(null) after this change.src/components/layout/Header/components/ChatHeader.tsx (1)
64-66:cn()사용 권장템플릿 리터럴 대신 이미 import된
cn()을 사용하면 일관성이 향상됩니다.♻️ 제안 수정
- className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${ - isMuted ? 'bg-gray-300' : 'bg-primary' - } disabled:cursor-not-allowed disabled:opacity-60`} + className={cn( + 'relative inline-flex h-6 w-11 items-center rounded-full transition-colors disabled:cursor-not-allowed disabled:opacity-60', + isMuted ? 'bg-gray-300' : 'bg-primary' + )}As per coding guidelines: "Use
cn()utility (clsx + tailwind-merge) for conditional class merging"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/layout/Header/components/ChatHeader.tsx` around lines 64 - 66, The className for the toggle in ChatHeader.tsx is built with a template literal; replace it with the imported cn() utility to handle conditional classes and merging. Update the element in ChatHeader (the JSX using className with isMuted) to call cn('relative inline-flex h-6 w-11 items-center rounded-full transition-colors disabled:cursor-not-allowed disabled:opacity-60', { 'bg-gray-300': isMuted, 'bg-primary': !isMuted }) so tailwind-merge and clsx behavior are used consistently across the codebase.src/components/layout/Header/components/BackTitleHeader.tsx (1)
44-44: 타이포그래피 토큰 사용 검토
leading-[1.6]은 커스텀 값입니다. 가능하다면 시맨틱 타이포그래피 유틸리티(text-h1~text-body3등)를 먼저 고려해 보세요.As per coding guidelines: "Use semantic typography utilities (
text-h1,text-body1, etc.) before custom CSS"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/layout/Header/components/BackTitleHeader.tsx` at line 44, The h1 in BackTitleHeader uses a custom line-height class leading-[1.6]; replace this with the project's semantic typography utility (e.g., text-h1 / text-body1 / text-body3) to follow the guideline. Update the className composition in the <h1> element (the cn call using titleClassName) to remove leading-[1.6] and include the appropriate semantic utility so typography is driven by text-*-style tokens instead of a hardcoded leading value.src/components/layout/Header/components/ProfileHeader.tsx (1)
8-8: 높이 값 충돌 가능성
h-11(44px)과min-h-[63px]이 함께 사용되어 있습니다.min-h-[63px]이 항상 우선되므로h-11은 실질적으로 무의미합니다. 의도된 동작인지 확인해 주세요.♻️ 제안 수정 (min-h만 유지)
- className="fixed top-0 right-0 left-0 z-30 flex h-11 min-h-[63px] items-center justify-end rounded-b-3xl bg-white px-4 py-2 pt-2 pb-3 shadow-[0_2px_2px_0_rgba(0,0,0,0.05)]" + className="fixed top-0 right-0 left-0 z-30 flex min-h-[63px] items-center justify-end rounded-b-3xl bg-white px-4 py-2 pt-2 pb-3 shadow-[0_2px_2px_0_rgba(0,0,0,0.05)]"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/layout/Header/components/ProfileHeader.tsx` at line 8, The class list in ProfileHeader's JSX uses both h-11 and min-h-[63px], causing h-11 to be ignored; update the className on the ProfileHeader element (the string containing "fixed top-0 ...") to remove the redundant h-11 and keep min-h-[63px] (or, if a fixed 44px height was intended, remove min-h-[63px] and keep h-11) so only one height constraint is applied and the layout behaves deterministically.src/pages/Chat/index.tsx (2)
269-270: 하드코드 색상 대신 테마 토큰 사용을 권장합니다.
bg-[#f3f5f8]대신 프로젝트 토큰 클래스로 맞추면 일관성과 다크/테마 확장성이 좋아집니다.As per coding guidelines, "Prioritize color tokens from
src/styles/theme.css(e.g.,indigo-*,blue-*,background,primary) over hardcoded colors".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Chat/index.tsx` around lines 269 - 270, Replace the hardcoded background color in the outer container JSX (the div with className containing "bg-[`#f3f5f8`]") with the project's theme token class (e.g., use "bg-background" or the appropriate "indigo-*" / "blue-*" token from src/styles/theme.css) so the component (index.tsx -> the Chat page container) uses theme tokens instead of a hex value; update the className string accordingly and ensure no other logic is changed.
293-333: 모달 텍스트는 semantic typography 유틸 우선 적용이 좋습니다.
text-[15px],leading-[22px]같은 임의값보다text-body*,text-sub*토큰을 우선 쓰는 쪽이 페이지 전반 일관성에 유리합니다.As per coding guidelines, "Use semantic typography utilities (
text-h1,text-body1, etc.) before custom CSS".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Chat/index.tsx` around lines 293 - 333, The modal markup in the Chat page uses hardcoded typography classes (e.g., text-[15px], leading-[22px]) which violates the semantic typography rule; update the Modal and BottomModal content to use the project's semantic typography utilities (for example replace occurrences in the leave-room modal and the change-room-name modal around JSX that references leaveRoom, deleteChat, newRoomName, changeName) — swap the inline classes like text-[15px] and leading-[22px] for the appropriate tokens (e.g., text-body*, text-sub*, or whatever semantic tokens your design system provides) so all modal headings, body copy, and buttons use consistent semantic classes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/components/layout/hooks/useLayoutHeaderInset.ts`:
- Around line 13-21: When hasHeader is false you must reset the header inset
state so stale values don't persist; in useLayoutHeaderInset, before returning
on the if (!hasHeader) branch call setHeaderInset('0px') (or the component's
intended empty inset value) to clear previous headerInset, leaving the rest of
measureInset and headerRef logic unchanged.
---
Nitpick comments:
In `@src/components/layout/Header/components/BackTitleHeader.tsx`:
- Line 44: The h1 in BackTitleHeader uses a custom line-height class
leading-[1.6]; replace this with the project's semantic typography utility
(e.g., text-h1 / text-body1 / text-body3) to follow the guideline. Update the
className composition in the <h1> element (the cn call using titleClassName) to
remove leading-[1.6] and include the appropriate semantic utility so typography
is driven by text-*-style tokens instead of a hardcoded leading value.
In `@src/components/layout/Header/components/ChatHeader.tsx`:
- Around line 64-66: The className for the toggle in ChatHeader.tsx is built
with a template literal; replace it with the imported cn() utility to handle
conditional classes and merging. Update the element in ChatHeader (the JSX using
className with isMuted) to call cn('relative inline-flex h-6 w-11 items-center
rounded-full transition-colors disabled:cursor-not-allowed disabled:opacity-60',
{ 'bg-gray-300': isMuted, 'bg-primary': !isMuted }) so tailwind-merge and clsx
behavior are used consistently across the codebase.
In `@src/components/layout/Header/components/ProfileHeader.tsx`:
- Line 8: The class list in ProfileHeader's JSX uses both h-11 and min-h-[63px],
causing h-11 to be ignored; update the className on the ProfileHeader element
(the string containing "fixed top-0 ...") to remove the redundant h-11 and keep
min-h-[63px] (or, if a fixed 44px height was intended, remove min-h-[63px] and
keep h-11) so only one height constraint is applied and the layout behaves
deterministically.
In `@src/pages/Chat/components/ChatRoomContextMenu.tsx`:
- Line 43: The component ChatRoomContextMenu uses hardcoded utility classes
`text-[14px] leading-[1.6]` (e.g., the title div rendering `{title}` and the
similar subtitle line) which should be replaced with the design-system semantic
typography tokens (e.g., `text-body1`, `text-sub2` or the equivalent in our
token set); update both occurrences in ChatRoomContextMenu (the title div and
the other text div at the indicated spots) to use the semantic utility classes,
ensuring you pick tokens that match the original size/line-height and that the
tokens exist in the project (or add them to the token map if missing).
- Around line 32-35: The clickItemHandler currently always calls onClose(),
which can duplicate setContextMenu(null) when some contextMenuItems (e.g., the
toggle mute item in the parent) call setContextMenu(null) themselves; pick
Option A (recommended) and remove setContextMenu(null) from the parent
contextMenuItems so ChatRoomContextMenu exclusively handles closing. Concretely,
edit the parent contextMenuItems (where labels like '알림 끄기/켜기' and handlers such
as toggleMute(room.roomId) are defined) to remove any explicit
setContextMenu(null) calls, leaving clickItemHandler and onClose in
ChatRoomContextMenu as the single place that closes the menu. Ensure no other
item handlers call setContextMenu(null) after this change.
In `@src/pages/Chat/index.tsx`:
- Around line 269-270: Replace the hardcoded background color in the outer
container JSX (the div with className containing "bg-[`#f3f5f8`]") with the
project's theme token class (e.g., use "bg-background" or the appropriate
"indigo-*" / "blue-*" token from src/styles/theme.css) so the component
(index.tsx -> the Chat page container) uses theme tokens instead of a hex value;
update the className string accordingly and ensure no other logic is changed.
- Around line 293-333: The modal markup in the Chat page uses hardcoded
typography classes (e.g., text-[15px], leading-[22px]) which violates the
semantic typography rule; update the Modal and BottomModal content to use the
project's semantic typography utilities (for example replace occurrences in the
leave-room modal and the change-room-name modal around JSX that references
leaveRoom, deleteChat, newRoomName, changeName) — swap the inline classes like
text-[15px] and leading-[22px] for the appropriate tokens (e.g., text-body*,
text-sub*, or whatever semantic tokens your design system provides) so all modal
headings, body copy, and buttons use consistent semantic classes.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 71095bce-a8b4-4985-a162-7d505da31a6e
⛔ Files ignored due to path filters (1)
src/assets/svg/big-search-icon.svgis excluded by!**/*.svg,!src/assets/**and included by**
📒 Files selected for processing (18)
src/components/layout/Header/components/BackTitleHeader.tsxsrc/components/layout/Header/components/ChatHeader.tsxsrc/components/layout/Header/components/ChatListHeader.tsxsrc/components/layout/Header/components/DefaultHeader.tsxsrc/components/layout/Header/components/InfoHeader.tsxsrc/components/layout/Header/components/ManagerHeader.tsxsrc/components/layout/Header/components/PlainSubpageHeader.tsxsrc/components/layout/Header/components/ProfileHeader.tsxsrc/components/layout/Header/components/ScheduleHeader.tsxsrc/components/layout/Header/components/SubpageHeader.tsxsrc/components/layout/Header/index.tsxsrc/components/layout/Header/presentation.tssrc/components/layout/Header/routeTitles.tssrc/components/layout/Header/types.tssrc/components/layout/hooks/useLayoutHeaderInset.tssrc/components/layout/index.tsxsrc/pages/Chat/components/ChatRoomContextMenu.tsxsrc/pages/Chat/index.tsx
💤 Files with no reviewable changes (1)
- src/components/layout/Header/presentation.ts
| if (!hasHeader) { | ||
| return; | ||
| } | ||
|
|
||
| const measureInset = () => { | ||
| const nextInset = `${Math.ceil(headerRef.current?.getBoundingClientRect().height ?? 0)}px`; | ||
|
|
||
| setHeaderInset((previousInset) => (previousInset === nextInset ? previousInset : nextInset)); | ||
| }; |
There was a problem hiding this comment.
hasHeader=false일 때 inset 상태를 초기화해야 합니다.
현재는 헤더가 사라진 라우트로 이동해도 이전 headerInset 값이 남을 수 있어 상단 여백이 잘못 유지됩니다.
수정 제안
useLayoutEffect(() => {
if (!hasHeader) {
+ setHeaderInset((previousInset) => (previousInset === '0px' ? previousInset : '0px'));
return;
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/layout/hooks/useLayoutHeaderInset.ts` around lines 13 - 21,
When hasHeader is false you must reset the header inset state so stale values
don't persist; in useLayoutHeaderInset, before returning on the if (!hasHeader)
branch call setHeaderInset('0px') (or the component's intended empty inset
value) to clear previous headerInset, leaving the rest of measureInset and
headerRef logic unchanged.
✨ 요약
😎 해결한 이슈
✅ 검증
Summary by CodeRabbit
릴리스 노트
New Features
Style