From 7214672c63fcd829126e1c8d5f1185e14ea04a55 Mon Sep 17 00:00:00 2001 From: mingo Date: Sat, 16 May 2026 01:50:18 +0900 Subject: [PATCH 01/10] =?UTF-8?q?Feat:=20#45=20=EA=B3=B5=ED=86=B5=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=ED=97=A4=EB=8D=94=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jobdri/components/common/header/Header.tsx | 108 +++++++++++++++++---- jobdri/components/icons/Icon.tsx | 2 + jobdri/components/input/InputMain.tsx | 1 + jobdri/components/input/inputStyles.ts | 5 + 4 files changed, 95 insertions(+), 21 deletions(-) create mode 100644 jobdri/components/icons/Icon.tsx create mode 100644 jobdri/components/input/InputMain.tsx create mode 100644 jobdri/components/input/inputStyles.ts diff --git a/jobdri/components/common/header/Header.tsx b/jobdri/components/common/header/Header.tsx index aa50e20..5972e31 100644 --- a/jobdri/components/common/header/Header.tsx +++ b/jobdri/components/common/header/Header.tsx @@ -1,7 +1,9 @@ "use client"; import type { ButtonHTMLAttributes } from "react"; -import Icon, { type IconType } from "@/components/common/icons/Icon"; +import clsx from "clsx"; +import { Button } from "@/components/common/buttons"; +import type { IconType } from "@/components/common/icons/Icon"; interface HeaderActionProps extends Omit< ButtonHTMLAttributes, @@ -11,50 +13,114 @@ interface HeaderActionProps extends Omit< label: string; } +interface ProgressStep { + label: string; +} + interface HeaderProps { title?: string; leftAction?: HeaderActionProps; rightAction?: HeaderActionProps; + currentStep?: number; + steps?: ProgressStep[]; } -const actionClassName = - "inline-flex items-center justify-end gap-1 rounded-cta-s bg-fill-quaternary-assistive py-1.5 pr-3 pl-2 text-[14px] font-semibold leading-[140%] tracking-[-0.28px] text-text-neutral-description [font-feature-settings:'liga'_off,'clig'_off]"; +const defaultSteps: ProgressStep[] = [ + { label: "유형 선택" }, + { label: "공고 생성" }, + { label: "공고 확인" }, + { label: "문항 선택" }, + { label: "자소서 입력" }, + { label: "결과 확인" }, +]; function HeaderAction({ - iconType = "HOME_S", + iconType, label, className = "", ...buttonProps }: HeaderActionProps) { return ( - + /> ); } export default function Header({ title = "모의 서류 지원", leftAction = { label: "돌아가기", iconType: "HOME_S" }, - rightAction = { label: "기업 선택하기", iconType: "HOME_S" }, + rightAction = { label: "저장하기" }, + currentStep = 1, + steps = defaultSteps, }: HeaderProps) { return ( -
-
- +
+
+
+
+

+ {title} +

+
+ +
+ +
+ +
    + {steps.map((step, index) => { + const stepNumber = index + 1; + const reached = stepNumber <= currentStep; -

    - {title} -

    + return ( +
  1. + + {stepNumber} + + + {step.label} + +
  2. + ); + })} +
+
- +
+ + +
); diff --git a/jobdri/components/icons/Icon.tsx b/jobdri/components/icons/Icon.tsx new file mode 100644 index 0000000..a12e86e --- /dev/null +++ b/jobdri/components/icons/Icon.tsx @@ -0,0 +1,2 @@ +export { default } from "@/components/common/icons/Icon"; +export type { IconType } from "@/components/common/icons/Icon"; diff --git a/jobdri/components/input/InputMain.tsx b/jobdri/components/input/InputMain.tsx new file mode 100644 index 0000000..1b87bd0 --- /dev/null +++ b/jobdri/components/input/InputMain.tsx @@ -0,0 +1 @@ +export { InputMain } from "@/components/common/input/InputMain"; diff --git a/jobdri/components/input/inputStyles.ts b/jobdri/components/input/inputStyles.ts new file mode 100644 index 0000000..a6f6742 --- /dev/null +++ b/jobdri/components/input/inputStyles.ts @@ -0,0 +1,5 @@ +export { + getFieldClass, + getWrapperClass, + scrollbarClass, +} from "@/components/common/input/inputStyles"; From 455d2df28194a4608efc0896eda505fcd17ed00f Mon Sep 17 00:00:00 2001 From: mingo Date: Sat, 16 May 2026 02:01:26 +0900 Subject: [PATCH 02/10] =?UTF-8?q?Feat:=20#45=20=EA=B3=B5=ED=86=B5=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=ED=91=B8=ED=84=B0=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jobdri/app/page.tsx | 6 +++ jobdri/components/common/footer/Footer.tsx | 58 ++++++++++++++++++++++ jobdri/components/common/footer/index.ts | 1 + 3 files changed, 65 insertions(+) create mode 100644 jobdri/components/common/footer/Footer.tsx create mode 100644 jobdri/components/common/footer/index.ts diff --git a/jobdri/app/page.tsx b/jobdri/app/page.tsx index 5713ace..42fb4c2 100644 --- a/jobdri/app/page.tsx +++ b/jobdri/app/page.tsx @@ -1,6 +1,7 @@ import IconBox from "@/components/common/icons/IconBox"; import CheckBox from "@/components/common/icons/CheckBox"; import Header from "@/components/common/header/Header"; +import { Footer } from "@/components/common/footer"; import { Lnb } from "@/components/common/lnb"; import { Button, @@ -51,6 +52,11 @@ export default function Home() {
+
+
+
+
+
diff --git a/jobdri/components/common/footer/Footer.tsx b/jobdri/components/common/footer/Footer.tsx new file mode 100644 index 0000000..75533b8 --- /dev/null +++ b/jobdri/components/common/footer/Footer.tsx @@ -0,0 +1,58 @@ +import type { ButtonHTMLAttributes } from "react"; +import clsx from "clsx"; +import { Button } from "@/components/common/buttons"; + +interface FooterActionProps + extends Omit, "children"> { + label?: string; +} + +interface FooterProps { + backAction?: FooterActionProps; + ctaAction?: FooterActionProps; + className?: string; +} + +export default function Footer({ + backAction = {}, + ctaAction = {}, + className, +}: FooterProps) { + const { + label: backLabel = "뒤로가기", + className: backClassName, + ...backButtonProps + } = backAction; + const { + label: ctaLabel = "CTA", + className: ctaClassName, + ...ctaButtonProps + } = ctaAction; + + return ( +
+
+
+
+ ); +} diff --git a/jobdri/components/common/footer/index.ts b/jobdri/components/common/footer/index.ts new file mode 100644 index 0000000..090a49a --- /dev/null +++ b/jobdri/components/common/footer/index.ts @@ -0,0 +1 @@ +export { default as Footer } from "./Footer"; From 07cb204105bb46000e94984b65ae13350c399a40 Mon Sep 17 00:00:00 2001 From: mingo Date: Sat, 16 May 2026 02:13:50 +0900 Subject: [PATCH 03/10] =?UTF-8?q?Feat:=20#45=20=EA=B3=B5=ED=86=B5=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=ED=86=A0=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jobdri/components/common/toast/ToastFrame.tsx | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/jobdri/components/common/toast/ToastFrame.tsx b/jobdri/components/common/toast/ToastFrame.tsx index a382056..f3b2f31 100644 --- a/jobdri/components/common/toast/ToastFrame.tsx +++ b/jobdri/components/common/toast/ToastFrame.tsx @@ -15,24 +15,30 @@ export default function ToastFrame({ }: ToastFrameProps) { return (
-
- - - {message} - -
+
+
+
+ + + {message} + +
- + +
+
); } From 215fa9ad368810d03886c2b14ef1db62b872e024 Mon Sep 17 00:00:00 2001 From: mingo Date: Sat, 16 May 2026 02:18:33 +0900 Subject: [PATCH 04/10] =?UTF-8?q?Feat:=20#45=20=EA=B3=B5=ED=86=B5=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=95=84=EC=9D=B4?= =?UTF-8?q?=EC=BD=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jobdri/assets/ic_Arrow_Down_M.svg | 3 +++ jobdri/assets/ic_Arrow_Left_M.svg | 3 +++ jobdri/assets/ic_Arrow_Up_M.svg | 3 +++ jobdri/assets/ic_Kabab.svg | 5 +++++ 4 files changed, 14 insertions(+) create mode 100644 jobdri/assets/ic_Arrow_Down_M.svg create mode 100644 jobdri/assets/ic_Arrow_Left_M.svg create mode 100644 jobdri/assets/ic_Arrow_Up_M.svg create mode 100644 jobdri/assets/ic_Kabab.svg diff --git a/jobdri/assets/ic_Arrow_Down_M.svg b/jobdri/assets/ic_Arrow_Down_M.svg new file mode 100644 index 0000000..dac850b --- /dev/null +++ b/jobdri/assets/ic_Arrow_Down_M.svg @@ -0,0 +1,3 @@ + + + diff --git a/jobdri/assets/ic_Arrow_Left_M.svg b/jobdri/assets/ic_Arrow_Left_M.svg new file mode 100644 index 0000000..36ed3cc --- /dev/null +++ b/jobdri/assets/ic_Arrow_Left_M.svg @@ -0,0 +1,3 @@ + + + diff --git a/jobdri/assets/ic_Arrow_Up_M.svg b/jobdri/assets/ic_Arrow_Up_M.svg new file mode 100644 index 0000000..58a2f85 --- /dev/null +++ b/jobdri/assets/ic_Arrow_Up_M.svg @@ -0,0 +1,3 @@ + + + diff --git a/jobdri/assets/ic_Kabab.svg b/jobdri/assets/ic_Kabab.svg new file mode 100644 index 0000000..bd8b793 --- /dev/null +++ b/jobdri/assets/ic_Kabab.svg @@ -0,0 +1,5 @@ + + + + + From cb256bf32899471a4e29a43dcca1ea214a79eca1 Mon Sep 17 00:00:00 2001 From: mingo Date: Sat, 16 May 2026 02:45:38 +0900 Subject: [PATCH 05/10] =?UTF-8?q?Feat:=20#45=20=EA=B3=B5=ED=86=B5=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=9D=B8=ED=92=8B=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jobdri/app/page.tsx | 4 + .../common/input/InputModalQuestion.tsx | 77 +++++++++++++++++++ jobdri/components/common/input/index.ts | 1 + jobdri/components/common/list/ListQCart.tsx | 25 +++--- jobdri/components/common/modal/ModalInput.tsx | 7 +- 5 files changed, 98 insertions(+), 16 deletions(-) create mode 100644 jobdri/components/common/input/InputModalQuestion.tsx diff --git a/jobdri/app/page.tsx b/jobdri/app/page.tsx index 42fb4c2..054baa2 100644 --- a/jobdri/app/page.tsx +++ b/jobdri/app/page.tsx @@ -24,6 +24,7 @@ import { InputAutoGrow, InputFile, InputMain, + InputModalQuestion, InputMultiLine, InputMultiLine1000, InputSingleLine, @@ -283,6 +284,9 @@ export default function Home() {
+
+ +
diff --git a/jobdri/components/common/input/InputModalQuestion.tsx b/jobdri/components/common/input/InputModalQuestion.tsx new file mode 100644 index 0000000..9bf38ed --- /dev/null +++ b/jobdri/components/common/input/InputModalQuestion.tsx @@ -0,0 +1,77 @@ +"use client"; + +import type { InputHTMLAttributes } from "react"; +import { useState } from "react"; +import clsx from "clsx"; + +interface InputModalQuestionProps + extends Omit, "onChange"> { + label?: string; + required?: boolean; + value?: string; + onChange?: (value: string) => void; + error?: string; +} + +export function InputModalQuestion({ + label = "아이디", + required = true, + placeholder = "메시기를 입력하세요", + value: externalValue, + onChange, + disabled = false, + error, + className, + ...inputProps +}: InputModalQuestionProps) { + const [internalValue, setInternalValue] = useState(""); + const value = externalValue ?? internalValue; + + const handleChange = (e: React.ChangeEvent) => { + setInternalValue(e.target.value); + onChange?.(e.target.value); + }; + + return ( +
+
+ + {label} + + {required && ( +
+ +
+
+ +
+
+ + {error && ( + {error} + )} +
+ ); +} diff --git a/jobdri/components/common/input/index.ts b/jobdri/components/common/input/index.ts index 07fbee3..bde182b 100644 --- a/jobdri/components/common/input/index.ts +++ b/jobdri/components/common/input/index.ts @@ -1,4 +1,5 @@ export { InputMain } from "./InputMain"; +export { InputModalQuestion } from "./InputModalQuestion"; export { InputSingleLine } from "./InputSingleLine"; export { InputMultiLine } from "./InputMultiLine"; export { InputMultiLine1000 } from "./InputMultiLine1000"; diff --git a/jobdri/components/common/list/ListQCart.tsx b/jobdri/components/common/list/ListQCart.tsx index b61a0b6..791c457 100644 --- a/jobdri/components/common/list/ListQCart.tsx +++ b/jobdri/components/common/list/ListQCart.tsx @@ -2,8 +2,7 @@ import clsx from "clsx"; import { useState } from "react"; -import CheckBox from "@/components/common/icons/CheckBox"; -import IconBox from "@/components/common/icons/IconBox"; +import Icon from "@/components/common/icons/Icon"; interface ListQCartProps { question: string; @@ -28,18 +27,22 @@ export function ListQCart({ ); } diff --git a/jobdri/components/common/modal/ModalInput.tsx b/jobdri/components/common/modal/ModalInput.tsx index fd02026..1827f77 100644 --- a/jobdri/components/common/modal/ModalInput.tsx +++ b/jobdri/components/common/modal/ModalInput.tsx @@ -2,8 +2,7 @@ import clsx from "clsx"; import Icon from "@/components/common/icons/Icon"; -import Button from "@/components/common/buttons/Button"; -import { InputMain, InputSingleLine } from "@/components/common/input"; +import { InputModalQuestion } from "@/components/common/input"; import LoadMotion from "@/components/common/LoadMotion"; import ButtonCta from "@/components/common/buttons/ButtonCta"; import { ButtonCtaModal } from "../buttons"; @@ -73,11 +72,9 @@ export default function ModaInput({
{/* 인풋 */} - From d6bb761fb73512f8e3e35cf3dfb8a6f6b647ee0d Mon Sep 17 00:00:00 2001 From: mingo Date: Sat, 16 May 2026 03:03:36 +0900 Subject: [PATCH 06/10] =?UTF-8?q?Feat:=20#45=20=EA=B3=B5=ED=86=B5=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=AA=A8=EB=8B=AC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jobdri/app/page.tsx | 7 ++ .../components/common/modal/ModalNotice.tsx | 95 +++++++++++++++++++ jobdri/components/common/modal/index.ts | 4 + 3 files changed, 106 insertions(+) create mode 100644 jobdri/components/common/modal/ModalNotice.tsx create mode 100644 jobdri/components/common/modal/index.ts diff --git a/jobdri/app/page.tsx b/jobdri/app/page.tsx index 054baa2..92b7a99 100644 --- a/jobdri/app/page.tsx +++ b/jobdri/app/page.tsx @@ -15,6 +15,7 @@ import { Toast, ToastFrame } from "@/components/common/toast"; import { ChipRound, ChipRoundSelected, ChipQnumber } from "@/components/common/chips"; import ChipMainDemo from "@/components/common/chips/ChipMainDemo"; import ModalLinkInputDemo from "@/components/common/modal/ModalLinkInputDemo"; +import { ModalNotice } from "@/components/common/modal"; import { CompleteBadge } from "@/components/common/badges"; import { Tooltip, TooltipModify } from "@/components/common/tooltip"; import { TabMenuThree, TabMenuTwo } from "@/components/common/tabs"; @@ -249,6 +250,12 @@ export default function Home() {
{" "} +
+
+ + +
+
diff --git a/jobdri/components/common/modal/ModalNotice.tsx b/jobdri/components/common/modal/ModalNotice.tsx new file mode 100644 index 0000000..b855660 --- /dev/null +++ b/jobdri/components/common/modal/ModalNotice.tsx @@ -0,0 +1,95 @@ +import type { ButtonHTMLAttributes } from "react"; +import clsx from "clsx"; +import { Button } from "@/components/common/buttons"; + +type ModalNoticeVariant = "single" | "double"; + +interface ModalNoticeActionProps + extends Omit, "children"> { + label?: string; +} + +interface ModalNoticeProps { + variant?: ModalNoticeVariant; + title?: string; + description?: string; + primaryAction?: ModalNoticeActionProps; + secondaryAction?: ModalNoticeActionProps; + className?: string; +} + +export default function ModalNotice({ + variant = "single", + title = "공고 링크를 입력해주세요.", + description = "링크 내용이 부적절한 경우 제대로 추출되지 않을 수 있습니다.", + primaryAction = {}, + secondaryAction = {}, + className, +}: ModalNoticeProps) { + const { + label: primaryLabel = variant === "single" ? "닫기" : "입력하기", + className: primaryClassName, + ...primaryButtonProps + } = primaryAction; + const { + label: secondaryLabel = "닫기", + className: secondaryClassName, + ...secondaryButtonProps + } = secondaryAction; + + return ( +
+
+
+

+ {title} +

+

+ {description} +

+
+
+ +
+ {variant === "single" ? ( +
+ )} +
+
+ ); +} diff --git a/jobdri/components/common/modal/index.ts b/jobdri/components/common/modal/index.ts new file mode 100644 index 0000000..e82aa4c --- /dev/null +++ b/jobdri/components/common/modal/index.ts @@ -0,0 +1,4 @@ +export { default as ModalInput } from "./ModalInput"; +export { default as ModalLinkInputDemo } from "./ModalLinkInputDemo"; +export { default as ModalNotice } from "./ModalNotice"; +export { default as ModalPurchase } from "./ModalPurchase"; From dc32e0c90787bf4d8a75c73200f8b70bf3b1ebcf Mon Sep 17 00:00:00 2001 From: mingo Date: Sat, 16 May 2026 03:21:28 +0900 Subject: [PATCH 07/10] =?UTF-8?q?Feat:=20#45=20=EA=B3=B5=ED=86=B5=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=93=9C=EB=A1=AD?= =?UTF-8?q?=EB=8B=A4=EC=9A=B4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jobdri/app/page.tsx | 6 ++ jobdri/assets/ic_Arrow_Down_M.svg | 2 +- jobdri/assets/ic_Arrow_Up_M.svg | 2 +- .../components/common/dropdown/DropDown.tsx | 101 ++++++++++++++++++ jobdri/components/common/dropdown/index.ts | 2 + jobdri/components/common/icons/Icon.tsx | 4 + 6 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 jobdri/components/common/dropdown/DropDown.tsx create mode 100644 jobdri/components/common/dropdown/index.ts diff --git a/jobdri/app/page.tsx b/jobdri/app/page.tsx index 92b7a99..97bc63e 100644 --- a/jobdri/app/page.tsx +++ b/jobdri/app/page.tsx @@ -2,6 +2,7 @@ import IconBox from "@/components/common/icons/IconBox"; import CheckBox from "@/components/common/icons/CheckBox"; import Header from "@/components/common/header/Header"; import { Footer } from "@/components/common/footer"; +import { DropDown } from "@/components/common/dropdown"; import { Lnb } from "@/components/common/lnb"; import { Button, @@ -59,6 +60,11 @@ export default function Home() {
+
+
+ +
+
diff --git a/jobdri/assets/ic_Arrow_Down_M.svg b/jobdri/assets/ic_Arrow_Down_M.svg index dac850b..aae2a6f 100644 --- a/jobdri/assets/ic_Arrow_Down_M.svg +++ b/jobdri/assets/ic_Arrow_Down_M.svg @@ -1,3 +1,3 @@ - + diff --git a/jobdri/assets/ic_Arrow_Up_M.svg b/jobdri/assets/ic_Arrow_Up_M.svg index 58a2f85..6f57035 100644 --- a/jobdri/assets/ic_Arrow_Up_M.svg +++ b/jobdri/assets/ic_Arrow_Up_M.svg @@ -1,3 +1,3 @@ - + diff --git a/jobdri/components/common/dropdown/DropDown.tsx b/jobdri/components/common/dropdown/DropDown.tsx new file mode 100644 index 0000000..afc9848 --- /dev/null +++ b/jobdri/components/common/dropdown/DropDown.tsx @@ -0,0 +1,101 @@ +"use client"; + +import { useId, useState } from "react"; +import clsx from "clsx"; +import Icon from "@/components/common/icons/Icon"; + +export interface DropDownOption { + label: string; + value: string; +} + +interface DropDownProps { + options?: DropDownOption[]; + value?: string; + defaultValue?: string; + onChange?: (value: string) => void; + className?: string; +} + +const defaultOptions: DropDownOption[] = [ + { label: "300자", value: "300" }, + { label: "500자", value: "500" }, + { label: "800자", value: "800" }, + { label: "1,000자", value: "1000" }, + { label: "1,500자", value: "1500" }, + { label: "2,000자", value: "2000" }, +]; + +export default function DropDown({ + options = defaultOptions, + value, + defaultValue = "1000", + onChange, + className, +}: DropDownProps) { + const listboxId = useId(); + const [open, setOpen] = useState(false); + const [internalValue, setInternalValue] = useState(defaultValue); + const selectedValue = value ?? internalValue; + const selectedOption = + options.find((option) => option.value === selectedValue) ?? options[0]; + + const handleSelect = (nextValue: string) => { + setInternalValue(nextValue); + onChange?.(nextValue); + setOpen(false); + }; + + return ( +
+ + + {open && ( +
+ {options.map((option, index) => { + const selected = option.value === selectedValue; + + return ( +
+ + {index < options.length - 1 && ( + + )} +
+ ); + })} +
+ )} +
+ ); +} diff --git a/jobdri/components/common/dropdown/index.ts b/jobdri/components/common/dropdown/index.ts new file mode 100644 index 0000000..ecac87b --- /dev/null +++ b/jobdri/components/common/dropdown/index.ts @@ -0,0 +1,2 @@ +export { default as DropDown } from "./DropDown"; +export type { DropDownOption } from "./DropDown"; diff --git a/jobdri/components/common/icons/Icon.tsx b/jobdri/components/common/icons/Icon.tsx index 9f0d6e5..b574289 100644 --- a/jobdri/components/common/icons/Icon.tsx +++ b/jobdri/components/common/icons/Icon.tsx @@ -5,6 +5,8 @@ import IC_UPLOAD_M from "@/assets/ic_Upload_M.svg"; import IC_HOME_M from "@/assets/ic_Home_M.svg"; import IC_ARROW_R from "@/assets/ic_Arrow_RIght.svg"; import IC_ARROW_L from "@/assets/ic_Arrow_left.svg"; +import IC_ARROW_DOWN_M from "@/assets/ic_Arrow_Down_M.svg"; +import IC_ARROW_UP_M from "@/assets/ic_Arrow_Up_M.svg"; import IC_GOOGLE from "@/assets/ic_GoogleAsset.svg"; import IC_GOOD from "@/assets/ic_Good.svg"; import IC_ADD from "@/assets/ic_Add.svg"; @@ -43,6 +45,8 @@ const iconMap = { HOME_M: IC_HOME_M, ARROW_R: IC_ARROW_R, ARROW_L: IC_ARROW_L, + ARROW_DOWN_M: IC_ARROW_DOWN_M, + ARROW_UP_M: IC_ARROW_UP_M, GOOGLE: IC_GOOGLE, GOOD: IC_GOOD, ADD: IC_ADD, From 1d91aa5a69abac42d07701b857bd67274f14aeba Mon Sep 17 00:00:00 2001 From: mingo Date: Sat, 16 May 2026 03:26:14 +0900 Subject: [PATCH 08/10] =?UTF-8?q?Feat:=20#45=20=EA=B3=B5=ED=86=B5=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=B9=A9=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jobdri/app/page.tsx | 10 +++++++++- jobdri/components/common/chips/ChipTag.tsx | 8 +++----- jobdri/components/common/chips/index.ts | 2 ++ jobdri/components/common/credit/CreditRow.tsx | 3 +-- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/jobdri/app/page.tsx b/jobdri/app/page.tsx index 97bc63e..c5bf88e 100644 --- a/jobdri/app/page.tsx +++ b/jobdri/app/page.tsx @@ -13,7 +13,12 @@ import { TextButton, } from "@/components/common/buttons"; import { Toast, ToastFrame } from "@/components/common/toast"; -import { ChipRound, ChipRoundSelected, ChipQnumber } from "@/components/common/chips"; +import { + ChipQnumber, + ChipRound, + ChipRoundSelected, + ChipTag, +} from "@/components/common/chips"; import ChipMainDemo from "@/components/common/chips/ChipMainDemo"; import ModalLinkInputDemo from "@/components/common/modal/ModalLinkInputDemo"; import { ModalNotice } from "@/components/common/modal"; @@ -247,6 +252,9 @@ export default function Home() {
+
+ +
diff --git a/jobdri/components/common/chips/ChipTag.tsx b/jobdri/components/common/chips/ChipTag.tsx index de94f19..0a53150 100644 --- a/jobdri/components/common/chips/ChipTag.tsx +++ b/jobdri/components/common/chips/ChipTag.tsx @@ -2,17 +2,15 @@ import clsx from "clsx"; -interface ChipTagProps { +export interface ChipTagProps { label: string; - // 나중에 "active"나 "disabled" 등이 추가될 것을 대비해 유니온 타입으로 유지합니다. state?: "default"; className?: string; } -// Record의 키 타입을 ChipTagProps의 state로 지정하여 타입 안정성을 높입니다. const styles: Record, string> = { default: - "bg-fill-quaternary-default border border-line-neutral-default text-text-neutral-title hover:shadow-chip", + "border border-line-neutral-default bg-fill-quaternary-default text-text-neutral-description", }; export default function ChipTag({ @@ -23,7 +21,7 @@ export default function ChipTag({ return (
Date: Sat, 16 May 2026 03:42:07 +0900 Subject: [PATCH 09/10] =?UTF-8?q?Feat:=20#45=20=EA=B3=B5=ED=86=B5=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=82=AC=EC=9D=B4?= =?UTF-8?q?=EB=93=9C=EB=B0=94=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jobdri/app/page.tsx | 4 + .../common/progress/ProgressSidebar.tsx | 121 ++++++++++++++++++ .../common/progress/ProgressSidebarItem.tsx | 46 +++++++ jobdri/components/common/progress/index.ts | 3 + 4 files changed, 174 insertions(+) create mode 100644 jobdri/components/common/progress/ProgressSidebar.tsx create mode 100644 jobdri/components/common/progress/ProgressSidebarItem.tsx create mode 100644 jobdri/components/common/progress/index.ts diff --git a/jobdri/app/page.tsx b/jobdri/app/page.tsx index c5bf88e..486513e 100644 --- a/jobdri/app/page.tsx +++ b/jobdri/app/page.tsx @@ -23,6 +23,7 @@ import ChipMainDemo from "@/components/common/chips/ChipMainDemo"; import ModalLinkInputDemo from "@/components/common/modal/ModalLinkInputDemo"; import { ModalNotice } from "@/components/common/modal"; import { CompleteBadge } from "@/components/common/badges"; +import { ProgressSidebar } from "@/components/common/progress"; import { Tooltip, TooltipModify } from "@/components/common/tooltip"; import { TabMenuThree, TabMenuTwo } from "@/components/common/tabs"; import { CreditHeader, CreditRow } from "@/components/common/credit"; @@ -70,6 +71,9 @@ export default function Home() {
+
+ +
diff --git a/jobdri/components/common/progress/ProgressSidebar.tsx b/jobdri/components/common/progress/ProgressSidebar.tsx new file mode 100644 index 0000000..0ceef2b --- /dev/null +++ b/jobdri/components/common/progress/ProgressSidebar.tsx @@ -0,0 +1,121 @@ +"use client"; + +import { useCallback, useEffect, useMemo, useState } from "react"; +import clsx from "clsx"; +import ProgressSidebarItem from "./ProgressSidebarItem"; + +export interface ProgressSidebarItemData { + id: string; + label: string; +} + +interface ProgressSidebarProps { + items?: ProgressSidebarItemData[]; + activeId?: string; + defaultActiveId?: string; + onActiveChange?: (id: string) => void; + className?: string; +} + +const defaultItems: ProgressSidebarItemData[] = [ + { id: "job", label: "직무" }, + { id: "main-task", label: "주요업무" }, + { id: "qualification", label: "자격요건" }, + { id: "preference-1", label: "우대사항" }, + { id: "preference-2", label: "우대사항" }, + { id: "preference-3", label: "우대사항" }, + { id: "preference-4", label: "우대사항" }, + { id: "preference-5", label: "우대사항" }, +]; + +export default function ProgressSidebar({ + items = defaultItems, + activeId, + defaultActiveId, + onActiveChange, + className, +}: ProgressSidebarProps) { + const initialActiveId = defaultActiveId ?? items[0]?.id ?? ""; + const [internalActiveId, setInternalActiveId] = useState(initialActiveId); + const resolvedActiveId = activeId ?? internalActiveId; + const itemIds = useMemo(() => items.map((item) => item.id), [items]); + + const setActive = useCallback( + (nextId: string) => { + if (!nextId || nextId === resolvedActiveId) return; + setInternalActiveId(nextId); + onActiveChange?.(nextId); + }, + [onActiveChange, resolvedActiveId], + ); + + useEffect(() => { + let animationFrame = 0; + + const updateActiveFromScroll = () => { + const visibleTitles = itemIds + .map((id) => { + const element = document.getElementById(id); + if (!element) return null; + + const rect = element.getBoundingClientRect(); + const isVisible = rect.bottom > 0 && rect.top < window.innerHeight; + + return isVisible ? { id, top: rect.top } : null; + }) + .filter((item): item is { id: string; top: number } => Boolean(item)); + + if (visibleTitles.length === 0) return; + + const topVisibleTitle = + visibleTitles + .filter((item) => item.top >= 0) + .sort((a, b) => a.top - b.top)[0] ?? + visibleTitles.sort((a, b) => b.top - a.top)[0]; + + setActive(topVisibleTitle.id); + }; + + const requestUpdate = () => { + window.cancelAnimationFrame(animationFrame); + animationFrame = window.requestAnimationFrame(updateActiveFromScroll); + }; + + requestUpdate(); + window.addEventListener("scroll", requestUpdate, { passive: true }); + window.addEventListener("resize", requestUpdate); + + return () => { + window.cancelAnimationFrame(animationFrame); + window.removeEventListener("scroll", requestUpdate); + window.removeEventListener("resize", requestUpdate); + }; + }, [itemIds, setActive]); + + const handleItemClick = (id: string) => { + setActive(id); + document.getElementById(id)?.scrollIntoView({ + behavior: "smooth", + block: "start", + }); + }; + + return ( + + ); +} diff --git a/jobdri/components/common/progress/ProgressSidebarItem.tsx b/jobdri/components/common/progress/ProgressSidebarItem.tsx new file mode 100644 index 0000000..1a34c64 --- /dev/null +++ b/jobdri/components/common/progress/ProgressSidebarItem.tsx @@ -0,0 +1,46 @@ +"use client"; + +import type { ButtonHTMLAttributes } from "react"; +import clsx from "clsx"; + +interface ProgressSidebarItemProps + extends Omit, "children"> { + label?: string; + selected?: boolean; +} + +export default function ProgressSidebarItem({ + label = "직무", + selected = false, + className, + type = "button", + ...buttonProps +}: ProgressSidebarItemProps) { + return ( + + ); +} diff --git a/jobdri/components/common/progress/index.ts b/jobdri/components/common/progress/index.ts new file mode 100644 index 0000000..8aba2fc --- /dev/null +++ b/jobdri/components/common/progress/index.ts @@ -0,0 +1,3 @@ +export { default as ProgressSidebar } from "./ProgressSidebar"; +export { default as ProgressSidebarItem } from "./ProgressSidebarItem"; +export type { ProgressSidebarItemData } from "./ProgressSidebar"; From b6a945ce2ec19441bff64b8bd754b8a43c63e1ad Mon Sep 17 00:00:00 2001 From: mingo Date: Sat, 16 May 2026 21:22:47 +0900 Subject: [PATCH 10/10] =?UTF-8?q?Feat:=20#45=20=EA=B3=B5=ED=86=B5=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EB=A0=88=EC=8A=A4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jobdri/app/page.tsx | 17 ++- jobdri/components/common/LoadMotion.tsx | 36 ++++-- .../common/progress/ProgressPanelRow.tsx | 60 ++++++++++ .../common/progress/ProgressPanelRowItem.tsx | 97 ++++++++++++++++ jobdri/components/common/progress/index.ts | 4 + jobdri/components/input/InputMain.tsx | 109 +++++++++++++++++- 6 files changed, 314 insertions(+), 9 deletions(-) create mode 100644 jobdri/components/common/progress/ProgressPanelRow.tsx create mode 100644 jobdri/components/common/progress/ProgressPanelRowItem.tsx diff --git a/jobdri/app/page.tsx b/jobdri/app/page.tsx index 486513e..248156d 100644 --- a/jobdri/app/page.tsx +++ b/jobdri/app/page.tsx @@ -23,7 +23,10 @@ import ChipMainDemo from "@/components/common/chips/ChipMainDemo"; import ModalLinkInputDemo from "@/components/common/modal/ModalLinkInputDemo"; import { ModalNotice } from "@/components/common/modal"; import { CompleteBadge } from "@/components/common/badges"; -import { ProgressSidebar } from "@/components/common/progress"; +import { + ProgressPanelRow, + ProgressSidebar, +} from "@/components/common/progress"; import { Tooltip, TooltipModify } from "@/components/common/tooltip"; import { TabMenuThree, TabMenuTwo } from "@/components/common/tabs"; import { CreditHeader, CreditRow } from "@/components/common/credit"; @@ -71,6 +74,18 @@ export default function Home() {
+
+
+
+ 3 + +
+
+ 4 + +
+
+
diff --git a/jobdri/components/common/LoadMotion.tsx b/jobdri/components/common/LoadMotion.tsx index 2e96e00..ed88097 100644 --- a/jobdri/components/common/LoadMotion.tsx +++ b/jobdri/components/common/LoadMotion.tsx @@ -5,9 +5,23 @@ import clsx from "clsx"; interface LoadMotionProps { className?: string; + dotFrameClassName?: string; + dotClassName?: string; + activeDotClassName?: string; + inactiveDotClassName?: string; + activeMotionClassName?: string; + inactiveMotionClassName?: string; } -export default function LoadMotion({ className }: LoadMotionProps) { +export default function LoadMotion({ + className, + dotFrameClassName, + dotClassName, + activeDotClassName = "bg-icon-neutral-heavy", + inactiveDotClassName = "bg-icon-neutral-assistive", + activeMotionClassName = "-translate-y-0.5", + inactiveMotionClassName = "translate-y-0", +}: LoadMotionProps) { const [active, setActive] = useState(0); useEffect(() => { @@ -17,17 +31,25 @@ export default function LoadMotion({ className }: LoadMotionProps) { return () => clearInterval(interval); }, []); return ( -
+
{[0, 1, 2].map((i) => (
+ > + +
))}
); diff --git a/jobdri/components/common/progress/ProgressPanelRow.tsx b/jobdri/components/common/progress/ProgressPanelRow.tsx new file mode 100644 index 0000000..fee50ad --- /dev/null +++ b/jobdri/components/common/progress/ProgressPanelRow.tsx @@ -0,0 +1,60 @@ +import clsx from "clsx"; +import ProgressPanelRowItem, { + type ProgressPanelRowItemStatus, +} from "./ProgressPanelRowItem"; + +export interface ProgressPanelRowItemData { + title?: string; + description?: string; + stepNumber?: number; + status?: ProgressPanelRowItemStatus; +} + +interface ProgressPanelRowProps { + itemCount?: 3 | 4; + currentStep?: number; + items?: ProgressPanelRowItemData[]; + className?: string; + itemClassName?: string; +} + +function getStepStatus( + index: number, + currentStep: number, +): ProgressPanelRowItemStatus { + const stepNumber = index + 1; + + if (stepNumber < currentStep) return "complete"; + if (stepNumber === currentStep) return "inProgress"; + return "idle"; +} + +export default function ProgressPanelRow({ + itemCount = 3, + currentStep = 2, + items, + className, + itemClassName, +}: ProgressPanelRowProps) { + const steps = + items ?? + Array.from({ length: itemCount }, (_, index) => ({ + stepNumber: index + 1, + })); + + return ( +
+ {steps.map((item, index) => ( + + ))} +
+ ); +} diff --git a/jobdri/components/common/progress/ProgressPanelRowItem.tsx b/jobdri/components/common/progress/ProgressPanelRowItem.tsx new file mode 100644 index 0000000..e44fb99 --- /dev/null +++ b/jobdri/components/common/progress/ProgressPanelRowItem.tsx @@ -0,0 +1,97 @@ +import clsx from "clsx"; +import Icon from "@/components/common/icons/Icon"; +import LoadMotion from "@/components/common/LoadMotion"; + +export type ProgressPanelRowItemStatus = "inProgress" | "complete" | "idle"; + +interface ProgressPanelRowItemProps { + status?: ProgressPanelRowItemStatus; + title?: string; + description?: string; + stepNumber?: number; + showConnector?: boolean; + className?: string; +} + +export default function ProgressPanelRowItem({ + status = "inProgress", + title = "기본 정보 입력", + description = "서브 텍스트 서브 텍스트서브 텍스트", + stepNumber = 1, + showConnector = true, + className, +}: ProgressPanelRowItemProps) { + const isInProgress = status === "inProgress"; + const isComplete = status === "complete"; + const isIdle = status === "idle"; + + return ( +
+ {isInProgress && ( +
+ )} + + {showConnector && ( + + )} + +
+ {isInProgress ? ( + + ) : isComplete ? ( + + + + ) : ( + + {stepNumber} + + )} +
+ +
+ + {title} + + + {description} + +
+
+ ); +} diff --git a/jobdri/components/common/progress/index.ts b/jobdri/components/common/progress/index.ts index 8aba2fc..7636607 100644 --- a/jobdri/components/common/progress/index.ts +++ b/jobdri/components/common/progress/index.ts @@ -1,3 +1,7 @@ +export { default as ProgressPanelRow } from "./ProgressPanelRow"; +export { default as ProgressPanelRowItem } from "./ProgressPanelRowItem"; export { default as ProgressSidebar } from "./ProgressSidebar"; export { default as ProgressSidebarItem } from "./ProgressSidebarItem"; +export type { ProgressPanelRowItemData } from "./ProgressPanelRow"; +export type { ProgressPanelRowItemStatus } from "./ProgressPanelRowItem"; export type { ProgressSidebarItemData } from "./ProgressSidebar"; diff --git a/jobdri/components/input/InputMain.tsx b/jobdri/components/input/InputMain.tsx index 1b87bd0..6fb6ab3 100644 --- a/jobdri/components/input/InputMain.tsx +++ b/jobdri/components/input/InputMain.tsx @@ -1 +1,108 @@ -export { InputMain } from "@/components/common/input/InputMain"; +"use client"; + +import { useState } from "react"; +import clsx from "clsx"; +import Icon from "@/components/icons/Icon"; +import { getWrapperClass, getFieldClass } from "./inputStyles"; + +interface InputMainProps { + label?: string; + required?: boolean; + placeholder?: string; + value?: string; + onChange?: (value: string) => void; + inputType?: React.HTMLInputTypeAttribute; + name?: string; + autoComplete?: string; + disabled?: boolean; + hasError?: boolean; + error?: string; + rightContent?: React.ReactNode; + className?: string; + type?: "ID" | "PASSWORD"; +} + +export function InputMain({ + label, + required, + placeholder, + value: externalValue, + onChange, + inputType, + name, + autoComplete, + disabled = false, + hasError = false, + error, + rightContent, + className, + type, +}: InputMainProps) { + const [internalValue, setInternalValue] = useState(""); + const [focused, setFocused] = useState(false); + + const value = externalValue ?? internalValue; + const iconType = type === "PASSWORD" ? "PASSWORD" : "PROFILE"; + const resolvedInputType = + inputType ?? (type === "PASSWORD" ? "password" : "text"); + const isError = hasError || !!error; + + const handleChange = (e: React.ChangeEvent) => { + setInternalValue(e.target.value); + onChange?.(e.target.value); + }; + + return ( +
+ {label && ( + + {label} + {required && } + + )} + +
+
+ {!focused && !value && ( + + )} + setFocused(true)} + onBlur={() => setFocused(false)} + disabled={disabled} + /> + {rightContent && ( +
+ {rightContent} +
+ )} +
+
+ + {error && ( + + {error} + + )} +
+ ); +}