diff --git a/jobdri/assets/ic_Arrow_Down_M.svg b/jobdri/assets/ic_Arrow_Down_M.svg
new file mode 100644
index 0000000..aae2a6f
--- /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..6f57035
--- /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 @@
+
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/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 (
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/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";
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 (
+ -
+
+ {stepNumber}
+
+
+ {step.label}
+
+
+ );
+ })}
+
+
-
+
+
+
+
);
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,
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({
{/* 인풋 */}
-
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";
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/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..7636607
--- /dev/null
+++ b/jobdri/components/common/progress/index.ts
@@ -0,0 +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/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}
+
+
-
+
+
+
);
}
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}
+
+ )}
+
+ );
+}
diff --git a/jobdri/components/input/inputStyles.ts b/jobdri/components/input/inputStyles.ts
index 1f64660..a6f6742 100644
--- a/jobdri/components/input/inputStyles.ts
+++ b/jobdri/components/input/inputStyles.ts
@@ -1 +1,5 @@
-export * from "@/components/common/input/inputStyles";
+export {
+ getFieldClass,
+ getWrapperClass,
+ scrollbarClass,
+} from "@/components/common/input/inputStyles";