[FEAT] 메인 레이아웃 셸 구현 (네비게이션/타임 사이드바, 헤더, 라우트 그룹)#111
Conversation
- (main) 레이아웃 전체 스크롤을 막고 main 영역만 내부 스크롤되도록 했습니다 - NavigationSidebar를 왼쪽에 fixed로 고정했습니다 - (with-time-sidebar) 라우트 그룹을 신설해 home/today에만 TimeSidebar가 적용되도록 했습니다 - TimeSidebar를 오른쪽에 fixed로 고정하고 레일 형태로 접고 펼 수 있도록 했습니다 - 레이아웃의 클라이언트 로직을 WithTimeSidebarContainer, MainShellContainer로 분리했습니다 - 페이지별 Header 조합(FocusHeaderContainer, StatisticsHeaderContainer, HomeHeaderContainer)을 추가하고 공통 빈 Header 렌더링을 제거했습니다 - NavigationSidebarContext로 왼쪽 사이드바 열림/닫힘 상태를 페이지 헤더와 공유하도록 했습니다 - 접힘 애니메이션을 width 대신 transform/opacity 기반으로 전환했습니다 (width:0 렌더링 버그 우회)
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Walkthrough
Changes메인 레이아웃 셸 구현
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant NavigationSidebarContext
participant MainShellContainer
participant WithTimeSidebarContainer
participant TimeSidebar
User->>NavigationSidebarContext: toggle()
NavigationSidebarContext->>MainShellContainer: isOpen 갱신
MainShellContainer->>MainShellContainer: main 마진 전환
User->>WithTimeSidebarContainer: TimeSidebar 접기 버튼 클릭
WithTimeSidebarContainer->>WithTimeSidebarContainer: isOpen 반전
WithTimeSidebarContainer->>TimeSidebar: isOpen 전달
TimeSidebar->>TimeSidebar: 너비/패널 표시 전환
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ 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 |
Timo Performance ReportBundle Size — timo-web
Lighthouse — timo-web
Image Optimization — timo-web
측정 커밋: |
There was a problem hiding this comment.
Actionable comments posted: 10
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/timo-web/app/`[locale]/(main)/_containers/MainShellContainer.tsx:
- Around line 22-26: The MainShellContainer component is building conditional
classes with a template literal instead of the shared cn utility, creating
inconsistency with NavigationSidebar. Update the main element className in
MainShellContainer to use cn for the static classes plus the isOpen-dependent
margin class from MAIN_MARGIN_CLASS_NAME, keeping the conditional class
composition consistent and easier to read.
In
`@apps/timo-web/app/`[locale]/(main)/(with-time-sidebar)/_containers/WithTimeSidebarContainer.tsx:
- Around line 11-14: The margin values in CONTENT_MARGIN_CLASS_NAME are
duplicated from TimeSidebar width constants and are hardcoded in a separate
place. Extract the shared sidebar width/margin values into a common constant
module and update WithTimeSidebarContainer and TimeSidebar to reference the same
symbols (for example the CONTENT_MARGIN_CLASS_NAME mapping and the
TIME_SIDEBAR_SIZE_WIDTH_CLASS_NAME/TIME_SIDEBAR_COLLAPSED_WIDTH_CLASS_NAME
definitions) so layout changes stay in sync.
- Around line 23-31: The wrapper in WithTimeSidebarContainer is adding a nested
scroll container via h-full overflow-y-auto, which conflicts with the main
scroll handled by MainShellContainer. Remove the overflow/height scrolling
behavior from this wrapper and keep it only for the margin transition classes so
scrolling is managed in a single place by the top-level main element.
- Around line 23-29: In WithTimeSidebarContainer, the className is being built
with a template literal while related components like TimeSidebar and
TimeSidebarHeader use the shared cn utility from `@repo/timo-design-system`.
Update the container’s wrapper div to use cn for combining the static classes
with the open/collapsed margin classes from CONTENT_MARGIN_CLASS_NAME so class
merging stays consistent and twMerge can resolve conflicts safely.
In
`@apps/timo-web/app/`[locale]/(main)/(with-time-sidebar)/home/_containers/HomeHeaderContainer.tsx:
- Line 8: `HomeHeaderContainer`의 `view` 상태가 `VIEW_OPTIONS`의 리터럴 유니언을
`useState<string>`로 다시 넓히고 있습니다. `VIEW_OPTIONS`를 기준으로 `typeof
VIEW_OPTIONS[number]` 타입 별칭을 만들어 `view` 상태와 `onChange` 처리에서 그 타입만 받도록 좁히고, 잘못된
문자열이 들어오지 않게 타입 안전성을 유지하도록 수정하세요.
In `@apps/timo-web/app/`[locale]/(main)/(with-time-sidebar)/home/page.tsx:
- Around line 8-10: Remove the unnecessary empty className from the section
element in the home page component. In the page.tsx render for
HomeTodoContainer, delete the empty className attribute on the section so the
markup stays clean unless a real style hook is needed later.
In
`@apps/timo-web/app/`[locale]/(main)/statistics/_containers/StatisticsHeaderContainer.tsx:
- Around line 12-13: The helper currently named addMonths in
StatisticsHeaderContainer does not match the required function-prefix
convention, so rename it to a get-prefixed name such as getAddedMonth to reflect
that it returns a computed value. Update all call sites in the same container,
including handlePrev and handleNext, to use the new function name so the naming
is consistent throughout the component.
In `@apps/timo-web/components/layout/sidebar/navigation/NavigationSidebar.tsx`:
- Around line 83-91: The NavigationSidebar component only hides the drawer
visually when isOpen is false, so the links can still receive focus from
keyboard and assistive tech. Update the aside in NavigationSidebar to toggle
aria-hidden and, if supported in this app, inert based on isOpen so the closed
sidebar is fully removed from the accessibility and tab order. Keep the change
localized to the existing isOpen conditional in NavigationSidebar.
In `@apps/timo-web/components/layout/sidebar/time/TimeSidebar.tsx`:
- Around line 42-49: The sidebar collapse animation in TimeSidebar is still
driven by width changes via transition-[width], which should be replaced with a
transform/opacity-based approach. Refactor the aside in TimeSidebar to use a
fixed-size container with transform-origin and a scaleX or translateX pattern,
and animate opacity as needed instead of changing width. Keep the existing
size/open state logic, but route it through transform-based classes so the
collapse feels smoother and avoids layout reflow.
In `@apps/timo-web/components/layout/sidebar/time/TimeSidebarHeader.tsx`:
- Around line 22-31: The TimeSidebarHeader text is only visually hidden when
isOpen is false, so it can still be announced by assistive tech. Update the
header content in TimeSidebarHeader to include aria-hidden={!isOpen} on the text
container (the <p> wrapping day/weekday), alongside the existing opacity and
pointer-events classes, so the content is fully hidden from screen readers when
closed.
🪄 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: ASSERTIVE
Plan: Pro Plus
Run ID: 2bd311b9-c993-4b20-b95f-b68ee9c13381
📒 Files selected for processing (33)
apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/_containers/WithTimeSidebarContainer.tsxapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/HomeDateInformation.tsxapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/HomeTodoCard.tsxapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeHeaderContainer.tsxapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeTodoContainer.tsxapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_mocks/todo-mock.tsapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_queries/.gitkeepapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_types/todo-type.tsapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_utils/date.tsapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_utils/todo-time.tsapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/page.tsxapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/layout.tsxapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_components/.gitkeepapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/.gitkeepapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_queries/.gitkeepapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/page.tsxapps/timo-web/app/[locale]/(main)/_containers/MainShellContainer.tsxapps/timo-web/app/[locale]/(main)/focus/_containers/FocusHeaderContainer.tsxapps/timo-web/app/[locale]/(main)/focus/page.tsxapps/timo-web/app/[locale]/(main)/home/page.tsxapps/timo-web/app/[locale]/(main)/layout.tsxapps/timo-web/app/[locale]/(main)/statistics/_containers/StatisticsHeaderContainer.tsxapps/timo-web/app/[locale]/(main)/statistics/page.tsxapps/timo-web/app/[locale]/(main)/today/_components/.gitkeepapps/timo-web/app/[locale]/(main)/today/_containers/.gitkeepapps/timo-web/app/[locale]/(main)/today/_queries/.gitkeepapps/timo-web/components/layout/sidebar/navigation/NavigationSidebar.tsxapps/timo-web/components/layout/sidebar/navigation/NavigationSidebarContext.tsxapps/timo-web/components/layout/sidebar/time/TimeSidebar.tsxapps/timo-web/components/layout/sidebar/time/TimeSidebarHeader.tsxapps/timo-web/components/layout/sidebar/time/TimeboxPanel.tsxapps/timo-web/components/layout/sidebar/time/TimerPanel.tsxapps/timo-web/components/layout/time-sidebar/TimeSidebar.tsx
💤 Files with no reviewable changes (2)
- apps/timo-web/components/layout/time-sidebar/TimeSidebar.tsx
- apps/timo-web/app/[locale]/(main)/home/page.tsx
- TimeSidebar의 폭 상수와 WithTimeSidebarContainer의 마진 상수가 서로 다른 파일에 중복 정의되어 있어 time-sidebar-size.ts로 공유하도록 했습니다 - WithTimeSidebarContainer의 콘텐츠 래퍼에서 main과 중복되던 h-full overflow-y-auto를 제거해 스크롤 소유자를 하나로 정리했습니다 - 접힘 폭을 Figma 스펙에 맞춰 72px에서 68px로 수정했습니다
- 아이콘을 열림/닫힘 상태에 따라 스왑하던 방식을 단일 아이콘 회전으로 바꿨습니다 - 아이콘이 순간적으로 바뀌지 않고 transition-transform으로 부드럽게 180도 회전하도록 했습니다
- HomeHeaderContainer의 view state가 useState<string>으로 선언되어 VIEW_OPTIONS의 리터럴 유니언 타입이 넓어지고 있었습니다 - ViewOption 타입과 타입 가드를 추가해 DropdownView의 string 기반 onChange 경계에서 안전하게 좁혀 받도록 했습니다
- 템플릿 리터럴로 조건부 클래스를 조합하던 곳을 cn 유틸 호출로 통일했습니다
- TagIcon의 템플릿 리터럴 조건부 클래스를 cn 유틸 호출로 통일했습니다
- 설정 메뉴에 mt-auto를 줬지만 nav가 늘어날 공간이 없어 실제로는 하단에 붙지 않고 있었습니다 - 상단 메뉴 그룹과 설정 메뉴를 분리하고 nav를 flex-1 justify-between으로 바꿔 설정 메뉴가 사이드바 맨 아래에 고정되도록 했습니다
- Timebox 탭 패널의 상단 padding을 pt-3에서 pt-[21px]로 조정했습니다
- TodayHeaderContainer를 새로 만들어 투데이 페이지에 사이드바 버튼만 있는 헤더를 붙였습니다 - StatisticsHeaderContainer의 기존 WeeklyNav 옆에 사이드바 버튼을 추가했습니다 - SettingsHeaderContainer를 만들어 settings/layout.tsx에 붙여, settings/account/policy 전체에 일괄 적용되도록 했습니다
- 로고 이미지를 Link로 감싸 클릭하면 홈으로 이동하도록 했습니다
- NavigationSidebar가 접혔을 때 aria-hidden/inert를 추가해 화면에서 사라진 로고·메뉴 링크가 키보드 포커스와 스크린리더 대상에서 제외되도록 했습니다 - TimeSidebarHeader의 날짜 텍스트도 접혔을 때 aria-hidden을 추가해 opacity로만 숨기던 것을 스크린리더에서도 실제로 숨겼습니다 - home/page.tsx의 불필요한 빈 className을 정리했습니다
jjangminii
left a comment
There was a problem hiding this comment.
너비가 줄어들지 않는 버그를 억지로 파고들지 않고 동일한 UX를 내는 방향으로 전환한 판단이 좋았어요! PR 설명에 버그 상황과 우회 이유를 명확히 기록해두셔서, 나중에 다른 팀원이 같은 상황을 만나거나 코드를 볼 때 맥락을 바로 파악할 수 있을 것 같아요 👍
TimeSidebar와 WithTimeSidebarContainer에 흩어져 있던 상수도 한 파일로 모아주셔서 관리가 훨씬 수월해진 것 같아요. 까다로운 레이아웃 요소가 많았는데 꼼꼼하게 잘 구현해주셨어요, 고생하셨습니다
다만 사이드바 크기 분기처리가 안되어있는거 같아요. 확인해주세요-!
| className={cn( | ||
| "transition-[margin-right] duration-200 ease-in-out", | ||
| isOpen | ||
| ? TIME_SIDEBAR_MARGIN_CLASS_NAME.sm |
There was a problem hiding this comment.
사이드바가 홈, 오늘 페이지 각각 sm, lg 인데 지금 sm 일 때만 적용된거 같아요-! 확인 부탁드립니당
There was a problem hiding this comment.
그렇네요! path에 따라 사이즈 분기가 다르게 수정해 두겠습니다!
ehye1
left a comment
There was a problem hiding this comment.
와우 레이아웃이 완성되면서 멋있어졌네요
수고하셨습니다 !!
| {isOpen && ( | ||
| <> | ||
| <div className="px-4.5"> | ||
| <TogglePanel |
There was a problem hiding this comment.
궁금한 것이 있습니다.. TimeSiderbar가 isOpen 조건부 렌더링으로 내용을 보여주고 있는데 사이드바 접었다 펼칠때마다 상태가 초기화되지는 않나요??
There was a problem hiding this comment.
근본적인 해결은 실제 API 연결 시 zustand로 전역 상태로 관리하는 방향을 논의해 봐야 될 것 같아요!
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (2)
apps/timo-web/app/[locale]/(main)/statistics/_containers/StatisticsHeaderContainer.tsx (1)
20-21: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
addMonths네이밍, 아직 안 고쳐졌어요.컨벤션상
get/create/check/convert/filter접두사 중 하나를 써야 하는데, 값을 계산해 반환하는 함수이니getAddedMonth가 더 어울립니다.✏️ 제안
- const handlePrev = () => setCurrentMonth((prev) => addMonths(prev, -1)); - const handleNext = () => setCurrentMonth((prev) => addMonths(prev, 1)); + const handlePrev = () => setCurrentMonth((prev) => getAddedMonth(prev, -1)); + const handleNext = () => setCurrentMonth((prev) => getAddedMonth(prev, 1));함수 정의부(
addMonths)도getAddedMonth로 함께 변경해주세요.As per path instructions, "함수 접두사:
get(반환) /create(생성) /check(확인) /convert(변환) /filter(필터링)".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/timo-web/app/`[locale]/(main)/statistics/_containers/StatisticsHeaderContainer.tsx around lines 20 - 21, The month-shifting helper still uses the nonconforming addMonths name, so rename the utility and all of its call sites to getAddedMonth to match the required get/create/check/convert/filter convention. Update StatisticsHeaderContainer so handlePrev and handleNext call the renamed helper, and also change the function definition/import where addMonths is declared so the identifier is consistent everywhere.Source: Path instructions
apps/timo-web/components/layout/sidebar/time/TimeSidebar.tsx (1)
39-47: 🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy liftwidth 트랜지션 이슈, 아직 그대로네요 😅
PR 목표에 명시된 "transform/opacity 기반 collapse 애니메이션"과 달리 여전히
transition-[width]로 폭을 직접 전환하고 있습니다. width 애니메이션은 리플로우를 유발해 성능이 떨어지고, 접기 시 콘텐츠가 눌리는 시각적 이슈도 남아있습니다.
scaleX+transform-origin조합이나 고정 컨테이너 +translateX방식으로 리팩터링을 고려해주세요.
관련 문서: MDN - CSS transitions performance♻️ 참고 diff
<aside className={cn( - "border-timo-gray-500 fixed top-5 right-0 bottom-5 z-10 flex flex-col overflow-hidden border-l bg-white transition-[width] duration-200 ease-in-out", - isOpen - ? TIME_SIDEBAR_WIDTH_CLASS_NAME[size] - : TIME_SIDEBAR_COLLAPSED_WIDTH_CLASS_NAME, + "border-timo-gray-500 fixed top-5 right-0 bottom-5 z-10 flex flex-col overflow-hidden border-l bg-white origin-right transition-transform duration-200 ease-in-out", + TIME_SIDEBAR_WIDTH_CLASS_NAME[size], + isOpen ? "scale-x-100" : "scale-x-0", )} >🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/timo-web/components/layout/sidebar/time/TimeSidebar.tsx` around lines 39 - 47, The collapse animation in TimeSidebar still uses direct width transitions via the outer aside className, which causes reflow and the remaining visual squeeze issue. Refactor the sidebar’s open/closed animation to use transform/opacity instead of transition-[width], for example by animating scaleX with a proper transform-origin or by keeping a fixed container and sliding the panel with translateX, while preserving the existing TIME_SIDEBAR_WIDTH_CLASS_NAME and TIME_SIDEBAR_COLLAPSED_WIDTH_CLASS_NAME state logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@apps/timo-web/app/`[locale]/(main)/(with-time-sidebar)/today/_containers/TodayHeaderContainer.tsx:
- Around line 1-12: TodayHeaderContainer duplicates the same
`useNavigationSidebar` to `Header.SidebarButton` wiring used by
`SettingsHeaderContainer` and likely other header containers. Extract this
repeated pattern into a shared reusable component or preset (for example a
`NavigationSidebarHeader` built on `Header` and `useNavigationSidebar`) and have
each page layout use that shared component instead of keeping separate
near-identical containers.
In `@apps/timo-web/app/`[locale]/(main)/(with-time-sidebar)/today/page.tsx:
- Around line 1-5: TodayPage currently renders only TodayHeaderContainer, so the
main task list area is missing. Update TodayPage to match HomePage’s layout by
returning a section wrapper and adding the Today-specific body container
alongside TodayHeaderContainer. Use the existing TodayPage and
TodayHeaderContainer symbols to locate this file, and wire in the appropriate
Today content container so the page renders both header and body.
---
Duplicate comments:
In
`@apps/timo-web/app/`[locale]/(main)/statistics/_containers/StatisticsHeaderContainer.tsx:
- Around line 20-21: The month-shifting helper still uses the nonconforming
addMonths name, so rename the utility and all of its call sites to getAddedMonth
to match the required get/create/check/convert/filter convention. Update
StatisticsHeaderContainer so handlePrev and handleNext call the renamed helper,
and also change the function definition/import where addMonths is declared so
the identifier is consistent everywhere.
In `@apps/timo-web/components/layout/sidebar/time/TimeSidebar.tsx`:
- Around line 39-47: The collapse animation in TimeSidebar still uses direct
width transitions via the outer aside className, which causes reflow and the
remaining visual squeeze issue. Refactor the sidebar’s open/closed animation to
use transform/opacity instead of transition-[width], for example by animating
scaleX with a proper transform-origin or by keeping a fixed container and
sliding the panel with translateX, while preserving the existing
TIME_SIDEBAR_WIDTH_CLASS_NAME and TIME_SIDEBAR_COLLAPSED_WIDTH_CLASS_NAME state
logic.
🪄 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: ASSERTIVE
Plan: Pro Plus
Run ID: 5f70f738-24d6-489b-9fca-5d90c3c47ccb
📒 Files selected for processing (11)
apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/_containers/WithTimeSidebarContainer.tsxapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/page.tsxapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayHeaderContainer.tsxapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/page.tsxapps/timo-web/app/[locale]/(main)/settings/_containers/.gitkeepapps/timo-web/app/[locale]/(main)/settings/_containers/SettingsHeaderContainer.tsxapps/timo-web/app/[locale]/(main)/settings/layout.tsxapps/timo-web/app/[locale]/(main)/statistics/_containers/StatisticsHeaderContainer.tsxapps/timo-web/components/layout/sidebar/navigation/NavigationSidebar.tsxapps/timo-web/components/layout/sidebar/time/TimeSidebar.tsxapps/timo-web/components/layout/sidebar/time/TimeSidebarHeader.tsx
| "use client"; | ||
|
|
||
| import { Header } from "@/components/layout/header/Header"; | ||
| import { useNavigationSidebar } from "@/components/layout/sidebar/navigation/NavigationSidebarContext"; | ||
|
|
||
| export const TodayHeaderContainer = () => { | ||
| const { isOpen, toggle } = useNavigationSidebar(); | ||
|
|
||
| return ( | ||
| <Header left={<Header.SidebarButton isOpen={isOpen} onClick={toggle} />} /> | ||
| ); | ||
| }; |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift
패턴 자체는 깔끔해요, 다만 중복이 좀 신경 쓰이네요.
SettingsHeaderContainer와 로직이 완전히 동일하고, 스택 개요상 FocusHeaderContainer, StatisticsHeaderContainer도 같은 패턴일 가능성이 높습니다(useNavigationSidebar → Header.SidebarButton 연결). 사이드바 버튼만 필요한 헤더라면 공용 컴포넌트(예: Header.NavigationSidebarButton 같은 프리셋)로 추출해서 각 도메인에서 반복 작성하지 않도록 하면 좋을 것 같아요.
React 공식 문서의 Custom Hooks로 로직 재사용하기 참고하시면 도움이 될 것 같습니다.
♻️ 제안 방향 예시
// components/layout/header/NavigationSidebarHeader.tsx
"use client";
import { Header } from "`@/components/layout/header/Header`";
import { useNavigationSidebar } from "`@/components/layout/sidebar/navigation/NavigationSidebarContext`";
export const NavigationSidebarHeader = () => {
const { isOpen, toggle } = useNavigationSidebar();
return (
<Header left={<Header.SidebarButton isOpen={isOpen} onClick={toggle} />} />
);
};각 페이지 layout에서 <NavigationSidebarHeader />를 바로 사용하면 TodayHeaderContainer, SettingsHeaderContainer 같은 개별 컨테이너 파일을 줄일 수 있습니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@apps/timo-web/app/`[locale]/(main)/(with-time-sidebar)/today/_containers/TodayHeaderContainer.tsx
around lines 1 - 12, TodayHeaderContainer duplicates the same
`useNavigationSidebar` to `Header.SidebarButton` wiring used by
`SettingsHeaderContainer` and likely other header containers. Extract this
repeated pattern into a shared reusable component or preset (for example a
`NavigationSidebarHeader` built on `Header` and `useNavigationSidebar`) and have
each page layout use that shared component instead of keeping separate
near-identical containers.
There was a problem hiding this comment.
책임 분리 가능성에 따라 지금을 유지하도록 하겠습니다
There was a problem hiding this comment.
The PR description images show the Timo app dashboard in various states (Today view with tasks, empty Today view, Focus Mode, Statistics with month navigation, and a collapsed/blank state) — these illustrate the overall (main) layout shell, sidebar, and header behavior being implemented in this PR, consistent with the described objectives.
@kimminna 네, 이해했습니다. 각 페이지 헤더 컨테이너가 앞으로 서로 다른 책임을 가질 가능성을 고려해 지금처럼 개별 파일로 유지하는 방향, 알겠습니다. 현재 코드는 그대로 두겠습니다.
✏️ Learnings added
Learnt from: kimminna
Repo: Team-Timo/Timo-client PR: 111
File: apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayHeaderContainer.tsx:1-12
Timestamp: 2026-07-07T15:03:13.503Z
Learning: In the Team-Timo/Timo-client repository, `TodayHeaderContainer`, `SettingsHeaderContainer`, `FocusHeaderContainer`, and `StatisticsHeaderContainer` (under `apps/timo-web/app/[locale]/(main)/.../_containers/`) intentionally duplicate the same `useNavigationSidebar()` → `Header.SidebarButton` wiring pattern instead of being extracted into a shared component. This is a deliberate choice (per kimminna) to preserve responsibility separation, since each page's header container is expected to diverge with page-specific logic over time. Do not suggest consolidating these into a single shared `NavigationSidebarHeader`-style component.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
ISSUE 🔗
close #110
What is this PR? 🔍
(main)라우트 그룹의 전체 레이아웃 셸(왼쪽 네비게이션 사이드바, 오른쪽 타임 사이드바, 페이지별 헤더)을 구현하고, 둘 다 접기/펼치기가 가능하도록 만들었습니다. 이후 코드리뷰 반영과 세부 UI 다듬기를 거쳤습니다.배경
(main)/layout.tsx가min-h-screen이라 콘텐츠가 길어지면 페이지 전체가 스크롤되고, 사이드바들도 고정되어 있지 않아 스크롤 시 함께 밀렸습니다. 홈/투데이 외 페이지에도 타임 사이드바 레이아웃이 섞여 있었고, 헤더는MainLayout에 빈<Header/>로 하드코딩되어 페이지별 커스터마이징이 불가능했습니다.(main)레이아웃을h-screen overflow-hidden으로 고정하고main만 내부 스크롤하도록 바꿨습니다. 두 사이드바를fixed로 고정하고, 각각 Context/로컬 state로 열림 상태를 관리해 접기/펼치기 애니메이션을 붙였습니다. 헤더는 페이지별 컨테이너에서 조합하도록 분리했습니다.전체 레이아웃 스크롤/고정
(main)레이아웃이 전체 스크롤되지 않고main만 내부 스크롤되도록 바꿨습니다.min-h-screen구조에서는 콘텐츠가 뷰포트보다 길어지면 페이지 전체가 스크롤되어 고정되어야 할 사이드바도 함께 밀렸습니다.h-screen overflow-hidden으로,main을overflow-y-auto로 바꿨습니다.왼쪽 네비게이션 사이드바 (NavigationSidebar)
fixed left-0으로 고정하고,NavigationSidebarContext를 통해 접기/펼치기 상태를 페이지 헤더의 토글 버튼과 공유하도록 했습니다. 설정 메뉴가 하단에 고정되도록 수정했고, 로고 클릭 시 홈으로 이동하도록 했습니다.FocusHeaderContainer,HomeHeaderContainer등)의 헤더 안에 있고, 사이드바 자체와main의 좌측 여백은 레이아웃 쪽(MainShellContainer)에 있어 서로 다른 컴포넌트 트리이기 때문에 props로 상태를 넘길 수 없었습니다. 또한 설정 메뉴에mt-auto가 있었지만nav에 여유 공간이 없어 실제로는 하단에 붙지 않고 있었습니다.width를w-55↔w-0으로 애니메이션했는데, 이 프로젝트 Tailwind 설정에서 특정aside요소가width: 0(임의값w-[0px]포함, 인라인!important까지)으로 지정해도 실제 렌더링 폭이 40px(패딩만큼)로 고정되는 원인 불명의 버그를 발견해,width애니메이션 대신transform: translateX(-100%)+opacity로 전환해 우회했습니다. 설정 메뉴는 상단 4개 메뉴와 분리해nav를flex-1 justify-between으로 바꿔 사이드바 맨 아래에 고정되도록 했습니다. 로고는Link로 감싸 홈 이동 링크를 추가했습니다.width:0버그의 근본 원인은 확인하지 못했습니다. 재현되면 별도 이슈로 추적이 필요합니다.오른쪽 타임 사이드바 (TimeSidebar) 및 라우트 그룹
(with-time-sidebar)라우트 그룹을 신설해 home/today에만TimeSidebar가 적용되도록 하고,TimeSidebar를fixed right-0으로 고정 + 레일 형태로 접고 펼 수 있게 만들었습니다. 접힘 폭을 Figma 스펙에 맞춰 68px로, Timebox 패널 상단 여백을 조정했습니다.useState기반 접힘 상태,TimeSidebar렌더링)을WithTimeSidebarContainer로 분리해layout.tsx는 서버 컴포넌트로 유지했습니다. 접힌 상태는w-17(68px) 폭의 레일로 유지하고,main카드의rounded-8여백에 맞춰top-5/bottom-5인셋과rounded-tr-8/rounded-br-8을 적용해 카드 밖으로 튀어나오지 않게 했습니다. 코드리뷰 지적으로,TimeSidebar의 폭 상수와WithTimeSidebarContainer의 마진 상수가 서로 다른 파일에 중복 정의되어 있던 것을time-sidebar-size.ts공용 모듈로 추출해 두 파일이 같은 값을 참조하도록 했고,WithTimeSidebarContainer의 콘텐츠 래퍼가main과 동일하게h-full overflow-y-auto를 가지고 있어 스크롤 소유자가 두 곳이 되던 문제도 래퍼에서 해당 클래스를 제거해 정리했습니다.페이지별 Header 조합
MainLayout이 렌더링하던 공통 빈<Header/>를 제거하고,FocusHeaderContainer(사이드바 버튼만),TodayHeaderContainer(사이드바 버튼만, 신규),SettingsHeaderContainer(사이드바 버튼만, 신규 —settings/layout.tsx에 붙여 account/policy 하위 페이지에도 일괄 적용),StatisticsHeaderContainer(사이드바 버튼 + 월 이동 버튼),HomeHeaderContainer(사이드바 버튼 + 오늘 버튼 + 드롭다운)로 페이지마다 다른 헤더를 조합하도록 분리했습니다.Header컴파운드 컴포넌트(Header.SidebarButton,Header.TodayButton,Header.WeeklyNav,Header.ViewDropdown)를 페이지별 컨테이너에서 조립합니다.Header가"use client"컴포넌트라서, 이를 사용하는 컨테이너(FocusHeaderContainer)도"use client"가 필요했습니다 — 서버 컴포넌트에서Header.SidebarButton처럼 컴파운드 프로퍼티에 접근하면 RSC 클라이언트 레퍼런스가 하위 프로퍼티를 들고 있지 않아undefined가 되는 런타임 에러가 발생했습니다.HomeHeaderContainer의 드롭다운 state는 처음useState<string>으로 선언해VIEW_OPTIONS의 리터럴 유니언이 넓어지는 문제가 있었는데,ViewOption타입과 타입 가드를 추가해DropdownView의string기반onChange경계에서 안전하게 좁혀 받도록 수정했습니다.HomeHeaderContainer의 드롭다운 옵션(기본/7일)은 실제 기능 요구사항이 확정되지 않아 임시 placeholder입니다.그 외
transition-transform/rotate-180으로 바꿔 부드럽게 회전하도록 했습니다.MainShellContainer,WithTimeSidebarContainer,Timer,TimerControlButton,TagIcon)을cn유틸 호출로 통일했습니다. 동작 변경은 없습니다.To Reviewers
NavigationSidebarContext가 정말 필요한 구조인지 봐주세요 — 토글 버튼(페이지 헤더)과 사이드바/main(레이아웃)이 서로 다른 컴포넌트 트리라 prop으로 넘길 방법이 없어서 도입했습니다.w-0/w-[0px]가 특정aside에서 렌더링되지 않는 버그는 원인을 찾지 못하고transform/opacity로 우회했습니다. 다른 곳에서도 재현되는지 확인이 필요합니다.HomeHeaderContainer의 뷰 드롭다운 옵션은 placeholder이니, 실제 기획이 나오면 별도로 반영이 필요합니다.Screenshot 📷
home
today
focus
statistics
settings
Test Checklist ✔
pnpm check-types통과 (모노레포 전체)pnpm lint통과 (모노레포 전체)/ko/home에서 사이드바 접기/펼치기 동작 확인 (width 렌더링 버그 발견 및 transform 방식으로 수정 확인)pnpm build— 미실행