Skip to content

[FEAT] 메인 레이아웃 셸 구현 (네비게이션/타임 사이드바, 헤더, 라우트 그룹)#111

Merged
kimminna merged 12 commits into
developfrom
feat/web/110-main-layout-shell
Jul 7, 2026
Merged

[FEAT] 메인 레이아웃 셸 구현 (네비게이션/타임 사이드바, 헤더, 라우트 그룹)#111
kimminna merged 12 commits into
developfrom
feat/web/110-main-layout-shell

Conversation

@kimminna

@kimminna kimminna commented Jul 7, 2026

Copy link
Copy Markdown
Member

ISSUE 🔗

close #110



What is this PR? 🔍

(main) 라우트 그룹의 전체 레이아웃 셸(왼쪽 네비게이션 사이드바, 오른쪽 타임 사이드바, 페이지별 헤더)을 구현하고, 둘 다 접기/펼치기가 가능하도록 만들었습니다. 이후 코드리뷰 반영과 세부 UI 다듬기를 거쳤습니다.

배경

  • 기존 구조: (main)/layout.tsxmin-h-screen이라 콘텐츠가 길어지면 페이지 전체가 스크롤되고, 사이드바들도 고정되어 있지 않아 스크롤 시 함께 밀렸습니다. 홈/투데이 외 페이지에도 타임 사이드바 레이아웃이 섞여 있었고, 헤더는 MainLayout에 빈 <Header/>로 하드코딩되어 페이지별 커스터마이징이 불가능했습니다.
  • 발생 문제: 레이아웃 위치가 스크롤에 종속적이었고, 사이드바 접기/펼치기 요구사항을 반영할 구조가 없었습니다.
  • 해결 방향: (main) 레이아웃을 h-screen overflow-hidden으로 고정하고 main만 내부 스크롤하도록 바꿨습니다. 두 사이드바를 fixed로 고정하고, 각각 Context/로컬 state로 열림 상태를 관리해 접기/펼치기 애니메이션을 붙였습니다. 헤더는 페이지별 컨테이너에서 조합하도록 분리했습니다.

전체 레이아웃 스크롤/고정

  • 변경 요약: (main) 레이아웃이 전체 스크롤되지 않고 main만 내부 스크롤되도록 바꿨습니다.
  • 이유: min-h-screen 구조에서는 콘텐츠가 뷰포트보다 길어지면 페이지 전체가 스크롤되어 고정되어야 할 사이드바도 함께 밀렸습니다.
  • 구현 방식: 최상위 wrapper를 h-screen overflow-hidden으로, mainoverflow-y-auto로 바꿨습니다.

왼쪽 네비게이션 사이드바 (NavigationSidebar)

  • 변경 요약: fixed left-0으로 고정하고, NavigationSidebarContext를 통해 접기/펼치기 상태를 페이지 헤더의 토글 버튼과 공유하도록 했습니다. 설정 메뉴가 하단에 고정되도록 수정했고, 로고 클릭 시 홈으로 이동하도록 했습니다.
  • 이유: 토글 버튼은 각 페이지(FocusHeaderContainer, HomeHeaderContainer 등)의 헤더 안에 있고, 사이드바 자체와 main의 좌측 여백은 레이아웃 쪽(MainShellContainer)에 있어 서로 다른 컴포넌트 트리이기 때문에 props로 상태를 넘길 수 없었습니다. 또한 설정 메뉴에 mt-auto가 있었지만 nav에 여유 공간이 없어 실제로는 하단에 붙지 않고 있었습니다.
  • 구현 방식: Figma(node 902-34524, 902-34539) 확인 결과 접힘 상태는 별도 레일 없이 완전히 사라지고 헤더 안의 토글 버튼만 남는 형태였습니다. widthw-55w-0으로 애니메이션했는데, 이 프로젝트 Tailwind 설정에서 특정 aside 요소가 width: 0(임의값 w-[0px] 포함, 인라인 !important까지)으로 지정해도 실제 렌더링 폭이 40px(패딩만큼)로 고정되는 원인 불명의 버그를 발견해, width 애니메이션 대신 transform: translateX(-100%) + opacity로 전환해 우회했습니다. 설정 메뉴는 상단 4개 메뉴와 분리해 navflex-1 justify-between으로 바꿔 사이드바 맨 아래에 고정되도록 했습니다. 로고는 Link로 감싸 홈 이동 링크를 추가했습니다.
  • 경계 · 제약: width:0 버그의 근본 원인은 확인하지 못했습니다. 재현되면 별도 이슈로 추적이 필요합니다.

오른쪽 타임 사이드바 (TimeSidebar) 및 라우트 그룹

  • 변경 요약: (with-time-sidebar) 라우트 그룹을 신설해 home/today에만 TimeSidebar가 적용되도록 하고, TimeSidebarfixed right-0으로 고정 + 레일 형태로 접고 펼 수 있게 만들었습니다. 접힘 폭을 Figma 스펙에 맞춰 68px로, Timebox 패널 상단 여백을 조정했습니다.
  • 이유: 타임박스/타이머 패널은 홈·투데이 화면에서만 필요하고, focus/statistics/settings에는 필요하지 않았습니다.
  • 구현 방식: 레이아웃의 클라이언트 로직(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 타입과 타입 가드를 추가해 DropdownViewstring 기반 onChange 경계에서 안전하게 좁혀 받도록 수정했습니다.
  • 경계 · 제약: HomeHeaderContainer의 드롭다운 옵션(기본/7일)은 실제 기능 요구사항이 확정되지 않아 임시 placeholder입니다.

그 외

  • DropdownView 아이콘: 열림/닫힘 상태에 따라 두 아이콘을 스왑하던 방식을 단일 아이콘 + transition-transform/rotate-180으로 바꿔 부드럽게 회전하도록 했습니다.
  • cn 유틸 통일: 템플릿 리터럴로 조건부 클래스를 조합하던 곳(MainShellContainer, WithTimeSidebarContainer, Timer, TimerControlButton, TagIcon)을 cn 유틸 호출로 통일했습니다. 동작 변경은 없습니다.



To Reviewers

  • NavigationSidebarContext가 정말 필요한 구조인지 봐주세요 — 토글 버튼(페이지 헤더)과 사이드바/main(레이아웃)이 서로 다른 컴포넌트 트리라 prop으로 넘길 방법이 없어서 도입했습니다.
  • Tailwind w-0/w-[0px]가 특정 aside에서 렌더링되지 않는 버그는 원인을 찾지 못하고 transform/opacity로 우회했습니다. 다른 곳에서도 재현되는지 확인이 필요합니다.
  • HomeHeaderContainer의 뷰 드롭다운 옵션은 placeholder이니, 실제 기획이 나오면 별도로 반영이 필요합니다.



Screenshot 📷

home

image

today

image

focus

image

statistics

image

settings

image



Test Checklist ✔

  • pnpm check-types 통과 (모노레포 전체)
  • pnpm lint 통과 (모노레포 전체)
  • Playwright로 /ko/home에서 사이드바 접기/펼치기 동작 확인 (width 렌더링 버그 발견 및 transform 방식으로 수정 확인)
  • pnpm build — 미실행

- (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 렌더링 버그 우회)
@vercel

vercel Bot commented Jul 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
timo Ready Ready Preview, Comment Jul 7, 2026 2:37pm

@github-actions github-actions Bot requested review from jjangminii and yumin-kim2 July 7, 2026 09:27
@github-actions github-actions Bot added the ⏰ Timo-web Timo 웹 서비스 label Jul 7, 2026
@github-actions github-actions Bot added ✨ Feature 새로운 기능(기능성) 구현 ♦️ 민아 민아상 labels Jul 7, 2026
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

(main) 라우트 그룹이 네비게이션/타임 사이드바 상태를 공유하는 셸 구조로 바뀌었고, 홈·포커스·통계 페이지는 전용 헤더를 렌더링합니다. 홈 관련 import 경로와 일부 공용 UI의 className 조합도 함께 정리됐습니다.

Changes

메인 레이아웃 셸 구현

Layer / File(s) Summary
네비게이션 사이드바 상태
apps/timo-web/components/layout/sidebar/navigation/NavigationSidebarContext.tsx, apps/timo-web/components/layout/sidebar/navigation/NavigationSidebar.tsx, apps/timo-web/app/[locale]/(main)/_containers/MainShellContainer.tsx, apps/timo-web/app/[locale]/(main)/layout.tsx
isOpen/toggle를 제공하는 Context와 훅이 추가되고, NavigationSidebarMainShellContainer가 이 상태에 따라 aside/main 배치를 전환합니다. MainLayout은 기존 직접 배치 대신 MainShellContainer를 사용합니다.
타임 사이드바 접기와 메인 인셋
apps/timo-web/components/layout/sidebar/time/time-sidebar-size.ts, apps/timo-web/components/layout/sidebar/time/TimeSidebar.tsx, apps/timo-web/components/layout/sidebar/time/TimeSidebarHeader.tsx, apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/_containers/WithTimeSidebarContainer.tsx, apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/layout.tsx
TimeSidebar가 탭 상태와 접힘 상태를 관리하고, 헤더 표시와 컨테이너 margin-right가 접힘 상태에 맞춰 토글됩니다. (with-time-sidebar) 레이아웃은 이 컨테이너를 감싸도록 추가됩니다.
홈·포커스·통계 페이지
apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/page.tsx, apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeHeaderContainer.tsx, apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeTodoContainer.tsx, apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/HomeTodoCard.tsx, apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_mocks/todo-mock.ts, apps/timo-web/app/[locale]/(main)/home/page.tsx, apps/timo-web/app/[locale]/(main)/focus/_containers/FocusHeaderContainer.tsx, apps/timo-web/app/[locale]/(main)/focus/page.tsx, apps/timo-web/app/[locale]/(main)/statistics/_containers/StatisticsHeaderContainer.tsx, apps/timo-web/app/[locale]/(main)/statistics/page.tsx, apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsHeaderContainer.tsx, apps/timo-web/app/[locale]/(main)/settings/layout.tsx
(with-time-sidebar) 홈 페이지가 새 헤더/투두 컨테이너를 조합해 렌더링하고, 홈 관련 타입/유틸 import 경로가 새 구조로 갱신됩니다. 포커스, 통계, 설정 페이지도 각각 전용 헤더 컨테이너를 렌더링하도록 바뀝니다.
공용 UI className 정리
apps/timo-web/components/timer/Timer.tsx, apps/timo-web/components/timer/TimerControlButton.tsx, packages/timo-design-system/src/components/dropdown-view/DropdownView.tsx, packages/timo-design-system/src/components/tag/tag-icon/TagIcon.tsx
cn 기반 className 조합으로 바뀌고, 드롭다운 아이콘/너비 표현이 단순화됩니다.

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: 너비/패널 표시 전환
Loading

Possibly related PRs

Suggested reviewers: ehye1, yumin-kim2, jjangminii

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed 링크된 #110의 핵심 요구사항인 고정 사이드바, with-time-sidebar, 페이지별 헤더, 내부 스크롤, 상태 공유가 모두 구현됐습니다.
Out of Scope Changes check ✅ Passed 보조 리팩터링도 새 레이아웃 셸과 헤더 UI를 지원하는 범위로 보이며, 명백한 무관 변경은 보이지 않습니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed 메인 레이아웃 셸, 사이드바, 헤더, 라우트 그룹 변경을 잘 요약한 제목입니다.
Description check ✅ Passed 설명이 실제 변경 범위와 일치하며 레이아웃 셸 구현 목적을 충분히 담고 있습니다.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/web/110-main-layout-shell

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

Timo Performance Report

Bundle Size — timo-web
라우트 크기 First Load JS
/[locale] 0 B 🟡 205.78 kB
/[locale]/focus 0 B 🟡 205.78 kB
/[locale]/home 0 B 🟡 205.78 kB
/[locale]/onboarding 0 B 🟡 205.78 kB
/[locale]/settings 0 B 🟡 205.78 kB
/[locale]/settings/account 0 B 🟡 205.78 kB
/[locale]/settings/policy 0 B 🟡 205.78 kB
/[locale]/statistics 0 B 🟡 205.78 kB
/[locale]/today 0 B 🟡 205.78 kB

공유 번들: 205.78 kB
🟢 < 200kB  |  🟡 < 350kB  |  🔴 ≥ 350kB (First Load JS · gzip)

Lighthouse — timo-web

⚠️ Lighthouse 결과를 가져오지 못했습니다.

Image Optimization — timo-web

public/ 디렉토리에 이미지가 없습니다.

측정 커밋: c8e9f6b

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between e945d20 and 742012f.

📒 Files selected for processing (33)
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/_containers/WithTimeSidebarContainer.tsx
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/HomeDateInformation.tsx
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/HomeTodoCard.tsx
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeHeaderContainer.tsx
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeTodoContainer.tsx
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_mocks/todo-mock.ts
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_queries/.gitkeep
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_types/todo-type.ts
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_utils/date.ts
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_utils/todo-time.ts
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/page.tsx
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/layout.tsx
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_components/.gitkeep
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/.gitkeep
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_queries/.gitkeep
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/page.tsx
  • apps/timo-web/app/[locale]/(main)/_containers/MainShellContainer.tsx
  • apps/timo-web/app/[locale]/(main)/focus/_containers/FocusHeaderContainer.tsx
  • apps/timo-web/app/[locale]/(main)/focus/page.tsx
  • apps/timo-web/app/[locale]/(main)/home/page.tsx
  • apps/timo-web/app/[locale]/(main)/layout.tsx
  • apps/timo-web/app/[locale]/(main)/statistics/_containers/StatisticsHeaderContainer.tsx
  • apps/timo-web/app/[locale]/(main)/statistics/page.tsx
  • apps/timo-web/app/[locale]/(main)/today/_components/.gitkeep
  • apps/timo-web/app/[locale]/(main)/today/_containers/.gitkeep
  • apps/timo-web/app/[locale]/(main)/today/_queries/.gitkeep
  • apps/timo-web/components/layout/sidebar/navigation/NavigationSidebar.tsx
  • apps/timo-web/components/layout/sidebar/navigation/NavigationSidebarContext.tsx
  • apps/timo-web/components/layout/sidebar/time/TimeSidebar.tsx
  • apps/timo-web/components/layout/sidebar/time/TimeSidebarHeader.tsx
  • apps/timo-web/components/layout/sidebar/time/TimeboxPanel.tsx
  • apps/timo-web/components/layout/sidebar/time/TimerPanel.tsx
  • apps/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

Comment thread apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/page.tsx Outdated
Comment thread apps/timo-web/components/layout/sidebar/time/TimeSidebar.tsx
Comment thread apps/timo-web/components/layout/sidebar/time/TimeSidebarHeader.tsx
kimminna added 5 commits July 7, 2026 20:25
- 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 유틸 호출로 통일했습니다
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

Storybook Preview

항목 링크
Storybook 열기
Chromatic 빌드 확인

마지막 업데이트: 2026-07-07 14:37 UTC

kimminna added 2 commits July 7, 2026 20:39
- 설정 메뉴에 mt-auto를 줬지만 nav가 늘어날 공간이 없어 실제로는 하단에 붙지 않고 있었습니다
- 상단 메뉴 그룹과 설정 메뉴를 분리하고 nav를 flex-1 justify-between으로 바꿔 설정 메뉴가 사이드바 맨 아래에 고정되도록 했습니다
- Timebox 탭 패널의 상단 padding을 pt-3에서 pt-[21px]로 조정했습니다
kimminna added 2 commits July 7, 2026 20:58
- TodayHeaderContainer를 새로 만들어 투데이 페이지에 사이드바 버튼만 있는 헤더를 붙였습니다
- StatisticsHeaderContainer의 기존 WeeklyNav 옆에 사이드바 버튼을 추가했습니다
- SettingsHeaderContainer를 만들어 settings/layout.tsx에 붙여, settings/account/policy 전체에 일괄 적용되도록 했습니다
- 로고 이미지를 Link로 감싸 클릭하면 홈으로 이동하도록 했습니다
- NavigationSidebar가 접혔을 때 aria-hidden/inert를 추가해 화면에서 사라진 로고·메뉴 링크가 키보드 포커스와 스크린리더 대상에서 제외되도록 했습니다
- TimeSidebarHeader의 날짜 텍스트도 접혔을 때 aria-hidden을 추가해 opacity로만 숨기던 것을 스크린리더에서도 실제로 숨겼습니다
- home/page.tsx의 불필요한 빈 className을 정리했습니다

@jjangminii jjangminii left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

너비가 줄어들지 않는 버그를 억지로 파고들지 않고 동일한 UX를 내는 방향으로 전환한 판단이 좋았어요! PR 설명에 버그 상황과 우회 이유를 명확히 기록해두셔서, 나중에 다른 팀원이 같은 상황을 만나거나 코드를 볼 때 맥락을 바로 파악할 수 있을 것 같아요 👍
TimeSidebar와 WithTimeSidebarContainer에 흩어져 있던 상수도 한 파일로 모아주셔서 관리가 훨씬 수월해진 것 같아요. 까다로운 레이아웃 요소가 많았는데 꼼꼼하게 잘 구현해주셨어요, 고생하셨습니다

다만 사이드바 크기 분기처리가 안되어있는거 같아요. 확인해주세요-!

className={cn(
"transition-[margin-right] duration-200 ease-in-out",
isOpen
? TIME_SIDEBAR_MARGIN_CLASS_NAME.sm

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

사이드바가 홈, 오늘 페이지 각각 sm, lg 인데 지금 sm 일 때만 적용된거 같아요-! 확인 부탁드립니당

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

그렇네요! path에 따라 사이즈 분기가 다르게 수정해 두겠습니다!

@ehye1 ehye1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

와우 레이아웃이 완성되면서 멋있어졌네요
수고하셨습니다 !!

Comment on lines +54 to +57
{isOpen && (
<>
<div className="px-4.5">
<TogglePanel

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

궁금한 것이 있습니다.. TimeSiderbar가 isOpen 조건부 렌더링으로 내용을 보여주고 있는데 사이드바 접었다 펼칠때마다 상태가 초기화되지는 않나요??

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

근본적인 해결은 실제 API 연결 시 zustand로 전역 상태로 관리하는 방향을 논의해 봐야 될 것 같아요!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 (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 lift

width 트랜지션 이슈, 아직 그대로네요 😅

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

📥 Commits

Reviewing files that changed from the base of the PR and between e7b84be and 46d6868.

📒 Files selected for processing (11)
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/_containers/WithTimeSidebarContainer.tsx
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/page.tsx
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayHeaderContainer.tsx
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/page.tsx
  • apps/timo-web/app/[locale]/(main)/settings/_containers/.gitkeep
  • apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsHeaderContainer.tsx
  • apps/timo-web/app/[locale]/(main)/settings/layout.tsx
  • apps/timo-web/app/[locale]/(main)/statistics/_containers/StatisticsHeaderContainer.tsx
  • apps/timo-web/components/layout/sidebar/navigation/NavigationSidebar.tsx
  • apps/timo-web/components/layout/sidebar/time/TimeSidebar.tsx
  • apps/timo-web/components/layout/sidebar/time/TimeSidebarHeader.tsx

Comment on lines +1 to +12
"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} />} />
);
};

@coderabbitai coderabbitai Bot Jul 7, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

패턴 자체는 깔끔해요, 다만 중복이 좀 신경 쓰이네요.

SettingsHeaderContainer와 로직이 완전히 동일하고, 스택 개요상 FocusHeaderContainer, StatisticsHeaderContainer도 같은 패턴일 가능성이 높습니다(useNavigationSidebarHeader.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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

책임 분리 가능성에 따라 지금을 유지하도록 하겠습니다

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

@jjangminii jjangminii left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

반영 확인했습니다~~

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 새로운 기능(기능성) 구현 ⌚ Timo-Design-system Timo 디자인 시스템 ⏰ Timo-web Timo 웹 서비스 ♦️ 민아 민아상

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] 메인 레이아웃 셸 구현 (네비게이션/타임 사이드바, 헤더, 라우트 그룹)

3 participants