Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"use client";

import { cn } from "@repo/timo-design-system/utils";
import { useState } from "react";

import {
TIME_SIDEBAR_COLLAPSED_MARGIN_CLASS_NAME,
TIME_SIDEBAR_MARGIN_CLASS_NAME,
type TimeSidebarSize,
} from "@/components/layout/sidebar/time/time-sidebar-size";
import { TimeSidebar } from "@/components/layout/sidebar/time/TimeSidebar";
import { ROUTES } from "@/constants/routes";
import { usePathname } from "@/i18n/navigation";

interface WithTimeSidebarContainerProps {
children: React.ReactNode;
}

export const WithTimeSidebarContainer = ({
children,
}: WithTimeSidebarContainerProps) => {
const [isOpen, setIsOpen] = useState<boolean>(true);

const pathname = usePathname();

const size: TimeSidebarSize = pathname.startsWith(ROUTES.TODAY) ? "lg" : "sm";

return (
<>
<div
className={cn(
"transition-[margin-right] duration-200 ease-in-out",
isOpen
? TIME_SIDEBAR_MARGIN_CLASS_NAME[size]
: TIME_SIDEBAR_COLLAPSED_MARGIN_CLASS_NAME,
)}
>
Comment thread
kimminna marked this conversation as resolved.
{children}
</div>
Comment thread
kimminna marked this conversation as resolved.

<TimeSidebar
size={size}
isOpen={isOpen}
onToggleCollapse={() => setIsOpen((prev) => !prev)}
/>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ import { cn } from "@repo/timo-design-system/utils";
import type {
TodoPriorityTypes,
TodoTimerStatusTypes,
} from "@/app/[locale]/(main)/home/_types/todo-type";
} from "@/app/[locale]/(main)/(with-time-sidebar)/home/_types/todo-type";

import { convertDurationToTimeText } from "@/app/[locale]/(main)/home/_utils/todo-time";
import { convertDurationToTimeText } from "@/app/[locale]/(main)/(with-time-sidebar)/home/_utils/todo-time";

const PRIORITY_MAP: Record<
TodoPriorityTypes,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"use client";

import { useState } from "react";

import { Header } from "@/components/layout/header/Header";
import { useNavigationSidebar } from "@/components/layout/sidebar/navigation/NavigationSidebarContext";

const VIEW_OPTIONS = ["기본", "7일"] as const;

type ViewOption = (typeof VIEW_OPTIONS)[number];

const isViewOption = (value: string): value is ViewOption =>
(VIEW_OPTIONS as readonly string[]).includes(value);

export const HomeHeaderContainer = () => {
const [view, setView] = useState<ViewOption>(VIEW_OPTIONS[0]);
const { isOpen, toggle } = useNavigationSidebar();

const handleChangeView = (value: string) => {
if (isViewOption(value)) {
setView(value);
}
};

return (
<Header
left={
<>
<Header.SidebarButton isOpen={isOpen} onClick={toggle} />
<Header.TodayButton />
</>
}
right={
<Header.ViewDropdown
items={[...VIEW_OPTIONS]}
value={view}
onChange={handleChangeView}
/>
}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
import { AddTaskButton } from "@repo/timo-design-system/ui";
import { useState } from "react";

import type { Todo } from "@/app/[locale]/(main)/home/_types/todo-type";
import type { Todo } from "@/app/[locale]/(main)/(with-time-sidebar)/home/_types/todo-type";

import { HomeDateInformation } from "@/app/[locale]/(main)/home/_components/HomeDateInformation";
import { HomeTodoCard } from "@/app/[locale]/(main)/home/_components/HomeTodoCard";
import { todoMocks } from "@/app/[locale]/(main)/home/_mocks/todo-mock";
import { HomeDateInformation } from "@/app/[locale]/(main)/(with-time-sidebar)/home/_components/HomeDateInformation";
import { HomeTodoCard } from "@/app/[locale]/(main)/(with-time-sidebar)/home/_components/HomeTodoCard";
import { todoMocks } from "@/app/[locale]/(main)/(with-time-sidebar)/home/_mocks/todo-mock";
import {
convertDateToDateText,
convertDateToDayOfWeek,
} from "@/app/[locale]/(main)/home/_utils/date";
} from "@/app/[locale]/(main)/(with-time-sidebar)/home/_utils/date";

export const HomeTodoContainer = () => {
const [todos, setTodos] = useState<Todo[]>(todoMocks);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Todo } from "@/app/[locale]/(main)/home/_types/todo-type";
import { Todo } from "@/app/[locale]/(main)/(with-time-sidebar)/home/_types/todo-type";

export const todoMocks: Todo[] = [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { HomeHeaderContainer } from "@/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeHeaderContainer";
import { HomeTodoContainer } from "@/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeTodoContainer";

export default function HomePage() {
return (
<>
<HomeHeaderContainer />
<section>
<HomeTodoContainer />
</section>
</>
);
}
11 changes: 11 additions & 0 deletions apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { WithTimeSidebarContainer } from "@/app/[locale]/(main)/(with-time-sidebar)/_containers/WithTimeSidebarContainer";

interface WithTimeSidebarLayoutProps {
children: React.ReactNode;
}

export default function WithTimeSidebarLayout({
children,
}: Readonly<WithTimeSidebarLayoutProps>) {
return <WithTimeSidebarContainer>{children}</WithTimeSidebarContainer>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,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} />} />
);
};
Comment on lines +1 to +12

@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.

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { TodayHeaderContainer } from "@/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayHeaderContainer";

export default function TodayPage() {
return <TodayHeaderContainer />;
}
Comment thread
kimminna marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"use client";

import { cn } from "@repo/timo-design-system/utils";

import { NavigationSidebar } from "@/components/layout/sidebar/navigation/NavigationSidebar";
import {
NavigationSidebarProvider,
useNavigationSidebar,
} from "@/components/layout/sidebar/navigation/NavigationSidebarContext";

interface MainShellContainerProps {
children: React.ReactNode;
}

const MAIN_MARGIN_CLASS_NAME = {
open: "ml-55",
collapsed: "ml-5",
} as const;

const MainContent = ({ children }: { children: React.ReactNode }) => {
const { isOpen } = useNavigationSidebar();

return (
<main
className={cn(
"rounded-8 h-full overflow-y-auto bg-white transition-[margin-left] duration-200 ease-in-out",
isOpen ? MAIN_MARGIN_CLASS_NAME.open : MAIN_MARGIN_CLASS_NAME.collapsed,
)}
>
Comment thread
kimminna marked this conversation as resolved.
{children}
</main>
);
};

export const MainShellContainer = ({ children }: MainShellContainerProps) => {
return (
<NavigationSidebarProvider>
<NavigationSidebar />
<MainContent>{children}</MainContent>
</NavigationSidebarProvider>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"use client";

import { Header } from "@/components/layout/header/Header";
import { useNavigationSidebar } from "@/components/layout/sidebar/navigation/NavigationSidebarContext";

export const FocusHeaderContainer = () => {
const { isOpen, toggle } = useNavigationSidebar();

return (
<Header left={<Header.SidebarButton isOpen={isOpen} onClick={toggle} />} />
);
};
4 changes: 3 additions & 1 deletion apps/timo-web/app/[locale]/(main)/focus/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { FocusHeaderContainer } from "@/app/[locale]/(main)/focus/_containers/FocusHeaderContainer";

export default function FocusPage() {
return <></>;
return <FocusHeaderContainer />;
}
9 changes: 0 additions & 9 deletions apps/timo-web/app/[locale]/(main)/home/page.tsx

This file was deleted.

7 changes: 3 additions & 4 deletions apps/timo-web/app/[locale]/(main)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { NavigationSidebar } from "@/components/layout/sidebar/NavigationSidebar";
import { MainShellContainer } from "@/app/[locale]/(main)/_containers/MainShellContainer";

interface MainLayoutProps {
children: React.ReactNode;
}

export default function MainLayout({ children }: Readonly<MainLayoutProps>) {
return (
<div className="flex min-h-screen">
<NavigationSidebar />
<main className="flex-1">{children}</main>
<div className="bg-timo-gray-300 h-screen overflow-hidden py-5">
<MainShellContainer>{children}</MainShellContainer>
</div>
);
}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"use client";

import { Header } from "@/components/layout/header/Header";
import { useNavigationSidebar } from "@/components/layout/sidebar/navigation/NavigationSidebarContext";

export const SettingsHeaderContainer = () => {
const { isOpen, toggle } = useNavigationSidebar();

return (
<Header left={<Header.SidebarButton isOpen={isOpen} onClick={toggle} />} />
);
};
11 changes: 9 additions & 2 deletions apps/timo-web/app/[locale]/(main)/settings/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
export default function SettingsLayout({
import { SettingsHeaderContainer } from "@/app/[locale]/(main)/settings/_containers/SettingsHeaderContainer";

export default function SettingsLayout({
children,
}: {
children: React.ReactNode;
}) {
return <>{children}</>;
return (
<>
<SettingsHeaderContainer />
{children}
</>
);
}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"use client";

import { useState } from "react";

import { Header } from "@/components/layout/header/Header";
import { useNavigationSidebar } from "@/components/layout/sidebar/navigation/NavigationSidebarContext";

const MONTH_LABEL_FORMATTER = new Intl.DateTimeFormat("ko-KR", {
year: "numeric",
month: "long",
});

const addMonths = (date: Date, amount: number) =>
new Date(date.getFullYear(), date.getMonth() + amount, 1);
Comment thread
kimminna marked this conversation as resolved.

export const StatisticsHeaderContainer = () => {
const [currentMonth, setCurrentMonth] = useState(() => new Date());
const { isOpen, toggle } = useNavigationSidebar();

const handlePrev = () => setCurrentMonth((prev) => addMonths(prev, -1));
const handleNext = () => setCurrentMonth((prev) => addMonths(prev, 1));

return (
<Header
left={
<>
<Header.SidebarButton isOpen={isOpen} onClick={toggle} />
<Header.WeeklyNav
onPrev={handlePrev}
onNext={handleNext}
label={MONTH_LABEL_FORMATTER.format(currentMonth)}
/>
</>
}
/>
);
};
4 changes: 3 additions & 1 deletion apps/timo-web/app/[locale]/(main)/statistics/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { StatisticsHeaderContainer } from "@/app/[locale]/(main)/statistics/_containers/StatisticsHeaderContainer";

export default function StatisticsPage() {
return <></>;
return <StatisticsHeaderContainer />;
}
Empty file.
Empty file.
Empty file.
3 changes: 0 additions & 3 deletions apps/timo-web/app/[locale]/(main)/today/page.tsx

This file was deleted.

Loading
Loading