Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
d104b7f
feat(design-system): ChevronSmall 아이콘 소스 및 google-logo 에셋 추가 (#118)
ehye1 Jul 8, 2026
e43b142
feat(web): 온보딩 컴포넌트 구현 (#118)
ehye1 Jul 8, 2026
300728d
feat(web): 온보딩 컴포넌트 next-intl 다국어 매핑 추가 (#118)
ehye1 Jul 8, 2026
da35beb
chore(web): develop 머지 충돌 해결 - messages 파일 병합 (#118)
ehye1 Jul 8, 2026
032108f
fix(web): 코드래빗 리뷰 반영 - OnboardingButton disabled 처리 및 isSelected 컨벤션 …
ehye1 Jul 8, 2026
a5db346
chore(web): 온보딩 페이지 테스트 렌더링 제거 (#118)
ehye1 Jul 8, 2026
0d2c23b
fix(web): OnboardingTimeDropdown 제어/비제어 혼합 패턴 수정 (#118)
ehye1 Jul 9, 2026
db0fc6c
refactor(web): OnboardingButton boolean 변수명 is 접두사 적용 (#118)
ehye1 Jul 9, 2026
bb0fade
refactor(web): OnboardingStepButton div를 ol/li 시맨틱 태그로 교체 (#118)
ehye1 Jul 9, 2026
a30df9b
feat(design-system): ChevronDownGray/Up 아이콘 SVG 소스 추가 (#118)
ehye1 Jul 9, 2026
3fde177
feat(web): OnboardingTimeDropdown AM/PM 표시 및 피그마 레이아웃 반영 (#118)
ehye1 Jul 9, 2026
a45d7f7
fix(web): OnboardingGoogleButton 고정 너비 제거 (#118)
ehye1 Jul 9, 2026
d70f58d
refactor(web): OnboardingButton variant discriminated union으로 재설계 (#118)
ehye1 Jul 9, 2026
6ee9ec6
fix(config): 전역 webkit 스크롤바 너비 실측값으로 수정 (#118)
ehye1 Jul 9, 2026
43c8b5d
chore(web): 온보딩 페이지 렌더링 및 스크롤바 설정 정리 (#118)
ehye1 Jul 9, 2026
43dd6d2
refactor(web): OnboardingTimeField _components에서 _containers로 이동 (#118)
ehye1 Jul 9, 2026
4b960e0
chore(web): 온보딩 _containers .gitkeep 제거 (#118)
ehye1 Jul 9, 2026
6650449
fix(web): OnboardingTimeField의 잘못된 import 경로 수정 (#118)
ehye1 Jul 9, 2026
e6155ae
refactor(web): 온보딩 컴포넌트·컨테이너 계층 분리 (#118)
ehye1 Jul 9, 2026
3c70014
refactor(web): OnboardingGoogleButtonProps 인터페이스 export 제거 (#118)
ehye1 Jul 10, 2026
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
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import {
ChevronLeftIcon,
ChevronSmallRightIcon,
ChevronSmallRightWhiteIcon,
} from "@repo/timo-design-system/icons";
import { cn } from "@repo/timo-design-system/utils";

export interface OnboardingButtonProps {
variant: "next" | "prev" | "start";
label: string;
isActive?: boolean;
disabled?: boolean;
onClick?: () => void;
}

export const OnboardingButton = ({
variant,
label,
isActive = false,
disabled = false,
onClick,
}: OnboardingButtonProps) => {
if (variant === "prev") {
return (
<button
type="button"
onClick={onClick}
className="hover:bg-timo-gray-500 bg-timo-gray-200 flex items-center justify-center gap-2 rounded-[4px] px-4 py-2 transition-colors duration-200 ease-in-out"
>
<ChevronLeftIcon width={18} height={18} />
<span className="typo-headline-m-16 text-timo-gray-900">{label}</span>
</button>
);
}

if (variant === "start") {
return (
<button
type="button"
onClick={onClick}
className="bg-timo-blue-300 flex items-center justify-center gap-2 rounded-[4px] px-4 py-2"
>
<span className="typo-headline-m-16 text-white">{label}</span>
</button>
);
}

const isDisabled = disabled || !isActive;

return (
<button
type="button"
onClick={onClick}
disabled={isDisabled}
className={cn(
"flex items-center justify-center gap-2 rounded-[4px] px-4 py-2",
isActive ? "bg-timo-blue-300" : "bg-timo-gray-200",
)}
>
<span
className={cn(
"typo-headline-m-16",
isActive ? "text-white" : "text-timo-gray-700",
)}
>
{label}
</span>
{isActive ? <ChevronSmallRightWhiteIcon /> : <ChevronSmallRightIcon />}
</button>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import googleLogo from "@repo/timo-design-system/assets/images/google-logo.svg";
import { cn } from "@repo/timo-design-system/utils";
import Image from "next/image";

type OnboardingGoogleButtonVariant = "login" | "connectCalendar";

interface OnboardingGoogleButtonProps {
variant: OnboardingGoogleButtonVariant;
label: string;
isSelected?: boolean;

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.

사소한거긴 한데 PR 설명에는 selected prop이라고 적혀있어서 pr 내용에 실제 코드랑 prop명 일치시키면 좋을 것 같아요-!

onClick?: () => void;
}

export const OnboardingGoogleButton = ({
label,
isSelected = false,
onClick,
}: OnboardingGoogleButtonProps) => {
return (
<button
type="button"
onClick={onClick}
className={cn(
"flex w-full items-center justify-center rounded-[4px] border py-2.5",
isSelected
? "border-timo-blue-300 bg-timo-blue-50"
: "border-timo-gray-500 bg-white",
)}
>
<div className="flex items-center gap-2.5 px-2">
<div className="flex size-[22px] items-center justify-center">
<Image src={googleLogo} alt="Google" width={18} height={18} />
</div>
<span className="typo-headline-m-16 text-timo-blue-300">{label}</span>
</div>
</button>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { cn } from "@repo/timo-design-system/utils";

interface OnboardingStepButtonProps {
step: 1 | 2 | 3 | 4;
}

const STEPS = [1, 2, 3, 4] as const;

export const OnboardingStepButton = ({ step }: OnboardingStepButtonProps) => {
return (
<ol className="flex items-center gap-2">
{STEPS.map((s) => (
<li
key={s}
className={cn(
"flex size-[19px] items-center justify-center rounded-full",
s === step ? "bg-timo-blue-300" : "bg-timo-blue-65",
)}
>
<span
className={cn(
"typo-caption-r-10",
s === step ? "text-white" : "text-timo-blue-100",
)}
>
{s}
</span>
</li>
))}
</ol>
);
Comment thread
ehye1 marked this conversation as resolved.
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import {
ChevronDownGrayIcon,
ChevronUpGrayIcon,
} from "@repo/timo-design-system/icons";
import { Dropdown } from "@repo/timo-design-system/ui";
import { cn } from "@repo/timo-design-system/utils";

import { getAmPm } from "@/utils/get-am-pm";

const TIME_OPTIONS = Array.from(
{ length: 25 },
(_, i) => `${String(i).padStart(2, "0")}:00`,
);
Comment thread
coderabbitai[bot] marked this conversation as resolved.

interface OnboardingTimeDropdownProps {
value: string;
placeholder?: string;
onChange: (time: string) => void;
}

export const OnboardingTimeDropdown = ({
value,
placeholder,
onChange,
}: OnboardingTimeDropdownProps) => {
return (
<Dropdown className="w-[150px]">
<Dropdown.Trigger className="group border-timo-gray-500 flex w-full items-center justify-between rounded-[4px] border bg-white px-4 py-3 text-left">
{value ? (
<div className="flex items-center gap-1">
<span className="typo-headline-b-16 text-timo-black">{value}</span>
<span className="typo-headline-b-16 text-timo-black">
{getAmPm(value)}
</span>
</div>
) : (
<span className="typo-headline-b-16 text-timo-gray-700">
{placeholder}
</span>
)}
<ChevronDownGrayIcon className="group-aria-expanded:hidden" />
<ChevronUpGrayIcon className="hidden group-aria-expanded:block" />
</Dropdown.Trigger>

<Dropdown.Panel className="border-timo-gray-500 mt-1 h-[220px] w-full rounded-[4px] border py-3 pr-2.5 pl-4">
<div className="flex h-full w-full flex-col gap-2.5 overflow-y-auto p-1 pr-3.5">
{TIME_OPTIONS.map((time) => (
<Dropdown.Item
key={time}
onClick={() => onChange(time)}
className={cn(
"typo-headline-b-16 flex w-full justify-between rounded-none",
value === time ? "text-timo-blue-300" : "text-timo-gray-700",
)}
>
<span>{time}</span>
<span>{getAmPm(time)}</span>
</Dropdown.Item>
))}
</div>
</Dropdown.Panel>
</Dropdown>
);
};
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"use client";

import { useTranslations } from "next-intl";

import { OnboardingButton } from "@/app/[locale]/onboarding/_components/OnboardingButton";

interface OnboardingButtonContainerProps {
variant: "next" | "prev" | "start";
isActive?: boolean;
disabled?: boolean;
onClick?: () => void;
}

export const OnboardingButtonContainer = ({
variant,
isActive,
disabled,
onClick,
}: OnboardingButtonContainerProps) => {
const t = useTranslations("Onboarding");

return (
<OnboardingButton
variant={variant}
label={t(`button.${variant}`)}
isActive={isActive}
disabled={disabled}
onClick={onClick}
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"use client";

import { useTranslations } from "next-intl";

import { OnboardingGoogleButton } from "@/app/[locale]/onboarding/_components/OnboardingGoogleButton";

interface OnboardingGoogleButtonContainerProps {
variant: "login" | "connectCalendar";
isSelected?: boolean;
onClick?: () => void;
}

export const OnboardingGoogleButtonContainer = ({
variant,
isSelected,
onClick,
}: OnboardingGoogleButtonContainerProps) => {
const t = useTranslations("Onboarding");

return (
<OnboardingGoogleButton
variant={variant}
label={t(`onboardingGoogleButton.${variant}`)}
isSelected={isSelected}
onClick={onClick}
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"use client";

import { useState } from "react";

import { OnboardingTimeDropdown } from "@/app/[locale]/onboarding/_components/OnboardingTimeDropdown";

export const OnboardingTimeContainer = () => {
const [time, setTime] = useState("");

return (
<OnboardingTimeDropdown
value={time}
placeholder="01:00"
onChange={setTime}
/>
);
};
Comment thread
coderabbitai[bot] marked this conversation as resolved.
11 changes: 11 additions & 0 deletions apps/timo-web/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,16 @@
"tagLimit": "You can create up to <blue>8 tags</blue>, and to add more, please delete existing tags.",
"todoLimitLine1": "You can add up to <blue>20 incomplete to-dos</blue>.",
"todoLimitLine2": "To add a new to-do, please complete an existing one."
},
"Onboarding": {
"button": {
"next": "Next",
"prev": "Previous",
"start": "Get Started"
},
"onboardingGoogleButton": {
"login": "Continue with your Google account",
"connectCalendar": "Connect Google Calendar"
}
}
}
11 changes: 11 additions & 0 deletions apps/timo-web/messages/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,16 @@
"tagLimit": "태그는 <blue>최대 8개</blue>까지 만들 수 있으며, 추가하려면 기존 태그를 삭제해 주세요.",
"todoLimitLine1": "완료되지 않은 투두는 <blue>최대 20개</blue>까지 추가할 수 있어요.",
"todoLimitLine2": "새로운 투두를 추가하려면 기존 투두를 완료해주세요."
},
"Onboarding": {
"button": {
"next": "다음",
"prev": "이전",
"start": "시작하기"
},
"onboardingGoogleButton": {
"login": "Google 계정으로 계속하기",
"connectCalendar": "Google 캘린더 연결하기"
}
}
}
13 changes: 13 additions & 0 deletions apps/timo-web/utils/get-am-pm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const HOURS_IN_DAY = 24;

/**
* "HH:MM" 형식의 시간 문자열에서 오전/오후를 판별합니다.
* 24시("24:00")는 다음날 자정(오전)으로 취급합니다.
*
* @param time - "HH:MM" 형식의 시간 문자열
* @returns "AM" 또는 "PM"
*/
export const getAmPm = (time: string): "AM" | "PM" => {
const hour = parseInt(time.split(":")[0] ?? "0", 10);
return hour >= 12 && hour < HOURS_IN_DAY ? "PM" : "AM";
};
4 changes: 2 additions & 2 deletions packages/tailwind-config/scrollbar.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
}

*::-webkit-scrollbar {
width: 6px;
height: 6px;
width: 8px;
height: 8px;
}

*::-webkit-scrollbar-track {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading