diff --git a/apps/timo-web/components/layout/header/Header.tsx b/apps/timo-web/components/layout/header/Header.tsx
index 5ace3dcd..7729c246 100644
--- a/apps/timo-web/components/layout/header/Header.tsx
+++ b/apps/timo-web/components/layout/header/Header.tsx
@@ -4,7 +4,7 @@ import {
DropdownView,
SidebarButton,
TodayButton,
- WeeklyButton,
+ ChevronButton,
} from "@repo/timo-design-system/ui";
import { cn } from "@repo/timo-design-system/utils";
@@ -39,13 +39,13 @@ export interface HeaderWeeklyNavProps {
const HeaderWeeklyNav = ({ onPrev, onNext, label }: HeaderWeeklyNavProps) => {
return (
-
+
{label && (
{label}
)}
-
+
);
};
diff --git a/packages/timo-design-system/src/components/button/weekly-button/WeeklyButton.stories.tsx b/packages/timo-design-system/src/components/button/chevron-button/ChevronButton.stories.tsx
similarity index 66%
rename from packages/timo-design-system/src/components/button/weekly-button/WeeklyButton.stories.tsx
rename to packages/timo-design-system/src/components/button/chevron-button/ChevronButton.stories.tsx
index 88ccc0be..e5e312e1 100644
--- a/packages/timo-design-system/src/components/button/weekly-button/WeeklyButton.stories.tsx
+++ b/packages/timo-design-system/src/components/button/chevron-button/ChevronButton.stories.tsx
@@ -1,10 +1,10 @@
-import { WeeklyButton } from "./WeeklyButton";
+import { ChevronButton } from "./ChevronButton";
import type { Meta, StoryObj } from "@storybook/react";
const meta = {
- title: "Components/Button/WeeklyButton",
- component: WeeklyButton,
+ title: "Components/Button/ChevronButton",
+ component: ChevronButton,
parameters: {
layout: "centered",
},
@@ -15,7 +15,7 @@ const meta = {
args: {
variant: "left",
},
-} satisfies Meta;
+} satisfies Meta;
export default meta;
type Story = StoryObj;
@@ -24,8 +24,8 @@ export const AllStates: Story = {
parameters: { controls: { disable: true } },
render: () => (
-
-
+
+
),
};
diff --git a/packages/timo-design-system/src/components/button/weekly-button/WeeklyButton.tsx b/packages/timo-design-system/src/components/button/chevron-button/ChevronButton.tsx
similarity index 55%
rename from packages/timo-design-system/src/components/button/weekly-button/WeeklyButton.tsx
rename to packages/timo-design-system/src/components/button/chevron-button/ChevronButton.tsx
index 7d49617d..f3770f93 100644
--- a/packages/timo-design-system/src/components/button/weekly-button/WeeklyButton.tsx
+++ b/packages/timo-design-system/src/components/button/chevron-button/ChevronButton.tsx
@@ -3,38 +3,38 @@ import { cn } from "../../../lib";
import type { ReactNode } from "react";
-export type WeeklyButtonVariantTypes = "left" | "right";
+export type ChevronButtonVariantTypes = "left" | "right";
-const WEEKLY_BUTTON_ICON: Record = {
+const CHEVRON_BUTTON_ICON: Record = {
left: ,
right: ,
};
-const WEEKLY_BUTTON_ARIA_LABEL: Record = {
+const CHEVRON_BUTTON_ARIA_LABEL: Record = {
left: "이전",
right: "다음",
};
-export interface WeeklyButtonProps {
- variant: WeeklyButtonVariantTypes;
+export interface ChevronButtonProps {
+ variant: ChevronButtonVariantTypes;
onClick?: () => void;
className?: string;
}
-export const WeeklyButton = ({
+export const ChevronButton = ({
variant,
onClick,
className,
-}: WeeklyButtonProps) => (
+}: ChevronButtonProps) => (
);
diff --git a/packages/timo-design-system/src/components/calendar/Calendar.stories.tsx b/packages/timo-design-system/src/components/calendar/Calendar.stories.tsx
new file mode 100644
index 00000000..08e75ce5
--- /dev/null
+++ b/packages/timo-design-system/src/components/calendar/Calendar.stories.tsx
@@ -0,0 +1,36 @@
+import { useState } from "react";
+
+import { Calendar } from "./Calendar";
+
+import type { CalendarProps } from "./Calendar";
+import type { Meta, StoryObj } from "@storybook/react";
+
+const meta = {
+ title: "Components/Calendar",
+ component: Calendar,
+ parameters: {
+ layout: "centered",
+ },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+const CalendarDemo = (args: CalendarProps) => {
+ const [selectedDate, setSelectedDate] = useState(
+ new Date(2026, 6, 1),
+ );
+
+ return (
+
+ );
+};
+
+export const Default: Story = {
+ render: (args) => ,
+};
diff --git a/packages/timo-design-system/src/components/calendar/Calendar.tsx b/packages/timo-design-system/src/components/calendar/Calendar.tsx
new file mode 100644
index 00000000..ad80aebe
--- /dev/null
+++ b/packages/timo-design-system/src/components/calendar/Calendar.tsx
@@ -0,0 +1,165 @@
+import { useState } from "react";
+
+import { cn } from "../../lib";
+import { ChevronButton } from "../button/chevron-button/ChevronButton";
+
+const WEEKDAYS = ["S", "M", "T", "W", "T", "F", "S"];
+
+const MONTH_LABELS = [
+ "January",
+ "February",
+ "March",
+ "April",
+ "May",
+ "June",
+ "July",
+ "August",
+ "September",
+ "October",
+ "November",
+ "December",
+];
+
+interface CalendarDate {
+ date: Date;
+ day: number;
+ isCurrentMonth: boolean;
+}
+
+const getCalendarDates = (month: Date): CalendarDate[] => {
+ const year = month.getFullYear();
+ const monthIndex = month.getMonth();
+ const firstDate = new Date(year, monthIndex, 1);
+ const firstDay = firstDate.getDay();
+ const lastDate = new Date(year, monthIndex + 1, 0);
+ const lastDay = lastDate.getDate();
+
+ const calendarCellCount = firstDay + lastDay > 35 ? 42 : 35;
+
+ const startDate = new Date(year, monthIndex, 1 - firstDay);
+
+ return Array.from({ length: calendarCellCount }, (_, index) => {
+ const date = new Date(
+ startDate.getFullYear(),
+ startDate.getMonth(),
+ startDate.getDate() + index,
+ );
+
+ return {
+ date,
+ day: date.getDate(),
+ isCurrentMonth: date.getMonth() === monthIndex,
+ };
+ });
+};
+
+export interface CalendarProps {
+ value?: Date;
+ defaultMonth?: Date;
+ onChange?: (date: Date) => void;
+ className?: string;
+}
+
+export const Calendar = ({
+ value,
+ defaultMonth,
+ onChange,
+ className,
+}: CalendarProps) => {
+ const [visibleMonth, setVisibleMonth] = useState(
+ defaultMonth ?? value ?? new Date(),
+ );
+ const calendarDates = getCalendarDates(visibleMonth);
+ const today = new Date();
+ const isSameDate = (a: Date, b: Date) => {
+ return (
+ a.getFullYear() === b.getFullYear() &&
+ a.getMonth() === b.getMonth() &&
+ a.getDate() === b.getDate()
+ );
+ };
+
+ const handleMoveMonth = (amount: number) => {
+ setVisibleMonth(
+ (prev) => new Date(prev.getFullYear(), prev.getMonth() + amount, 1),
+ );
+ };
+
+ return (
+
+
+
handleMoveMonth(-1)}
+ className="absolute top-0 left-0"
+ />
+
+
+
+ {MONTH_LABELS[visibleMonth.getMonth()]}
+
+
+ {visibleMonth.getFullYear()}
+
+
+
+ handleMoveMonth(1)}
+ className="absolute top-0 right-0"
+ />
+
+
+
+ {WEEKDAYS.map((weekday, index) => (
+
+ {weekday}
+
+ ))}
+
+ {calendarDates.map((calendarDate) => {
+ const isSelected = value
+ ? isSameDate(calendarDate.date, value)
+ : false;
+ const isToday = isSameDate(calendarDate.date, today);
+
+ return (
+
+ );
+ })}
+
+
+ );
+};
diff --git a/packages/timo-design-system/src/components/index.ts b/packages/timo-design-system/src/components/index.ts
index d13011e2..55c38bb2 100644
--- a/packages/timo-design-system/src/components/index.ts
+++ b/packages/timo-design-system/src/components/index.ts
@@ -32,6 +32,7 @@ export type {
} from "./layout/modal/Modal";
export { SidebarButton } from "./button/sidebar-button/SidebarButton";
export { TodayButton } from "./button/today-button/TodayButton";
-export { WeeklyButton } from "./button/weekly-button/WeeklyButton";
+export { ChevronButton } from "./button/chevron-button/ChevronButton";
export { TimeSelector } from "./time/time-selector/TimeSelector";
export { RepeatSelector } from "./repeat/repeat-selector/RepeatSelector";
+export { Calendar } from "./calendar/Calendar";