/* ============================================================================ * 서명전에 — TDS ProgressStepper 컴포넌트 * · ProgressBar 와 Stepper 가 결합된 형태 — 단계별 진행 상태를 * 가로로 한 눈에 보여줘요. * · 2 서브: * ProgressStepper — 컨테이너 (variant / paddingTop / activeStepIndex / checkForFinish) * ProgressStep — 자식 (title / icon) * * · ProgressStepperProps: * variant * "compact" | "icon" (필수) * paddingTop "default" | "wide" (기본 "default" → 16px / "wide" → 24px) * activeStepIndex number (기본 0) * checkForFinish boolean (icon variant 한정) (기본 false) * className, style, id 패스스루 * * · ProgressStepProps: * title string (옵셔널) — 없으면 마커만 표시 * icon ReactNode (icon variant 한정) — 없으면 1-based 숫자 표시 * className, style, id 패스스루 * * · 단계 상태 (활성 인덱스 기준): * idx < active → "past" (완료, blue-400) * idx === active → "current" (현재, blue-400 + ring 강조) * idx > active → "future" (미완료, grey) * * · checkForFinish + variant=icon + state=past → 체크 SVG 아이콘 자동. * * · 구현 메모: ProgressStep 은 React.cloneElement 로 부모에서 내부 * prop (__idx / __total / __state / __variant / __checkForFinish) 을 * 주입받아요. 사용자는 신경 쓸 필요 없음. * ========================================================================== */ const __STEPPER_VARIANTS = { compact: 1, icon: 1 }; const __STEPPER_PT = { default: 1, wide: 1 }; // ----- 체크 아이콘 (SVG 인라인) ----- function __StepperCheckSvg() { return ( ); } // ============================================================================= // ProgressStepper — 컨테이너 // ============================================================================= function ProgressStepper({ variant, paddingTop = "default", activeStepIndex = 0, checkForFinish = false, className = "", style, id, children, ...rest }) { // variant 검증 — 필수 if (!__STEPPER_VARIANTS[variant]) { if (window.console && console.warn) { console.warn( `[ProgressStepper] \`variant\` 는 필수예요 — "compact" | "icon". 받은 값: ${variant}` ); } } const safeVariant = __STEPPER_VARIANTS[variant] ? variant : "compact"; // paddingTop 검증 if (!__STEPPER_PT[paddingTop]) { if (window.console && console.warn) { console.warn( `[ProgressStepper] \`paddingTop\` 은 "default" | "wide" 만 가능해요. 받은 값: ${paddingTop}` ); } } const safePt = __STEPPER_PT[paddingTop] ? paddingTop : "default"; // checkForFinish — variant=icon 일 때만 의미 있음 if (checkForFinish && safeVariant !== "icon") { if (window.console && console.warn) { console.warn( `[ProgressStepper] \`checkForFinish\` 는 \`variant="icon"\` 일 때만 사용할 수 있어요.` ); } } const safeCheckForFinish = !!checkForFinish && safeVariant === "icon"; // 자식만 ProgressStep 으로 — null/false 같은 빈 자식 제외 const steps = React.Children.toArray(children).filter(Boolean); const total = steps.length; // activeStepIndex 정규화 — 음수 / total 초과 보정 const safeActive = typeof activeStepIndex === "number" && Number.isFinite(activeStepIndex) ? Math.max(0, Math.min(total - 1, Math.round(activeStepIndex))) : 0; const cls = [ "tds-stepper", `tds-stepper--variant-${safeVariant}`, `tds-stepper--pt-${safePt}`, className, ].filter(Boolean).join(" "); return (
{steps.map(function (step, idx) { let state; if (idx < safeActive) state = "past"; else if (idx === safeActive) state = "current"; else state = "future"; // 자식이 ProgressStep 이 아닌 다른 컴포넌트면 그대로 두지 않고 경고 // (cloneElement 가 가능해야 하므로 React element 인지만 체크) if (!React.isValidElement(step)) return step; return React.cloneElement(step, { key: step.key != null ? step.key : `step-${idx}`, __idx: idx, __total: total, __state: state, __variant: safeVariant, __checkForFinish: safeCheckForFinish, }); })}
); } // ============================================================================= // ProgressStep — 각 단계 // ============================================================================= function ProgressStep({ title, icon, className = "", style, id, // 부모에서 cloneElement 로 주입 — 사용자 입장에서는 안 보이는 internal __idx, __total, __state, __variant, __checkForFinish, ...rest }) { // 단독 사용 방어 — Stepper 바깥에서 직접 쓰면 internal prop 이 없어요 if (typeof __idx !== "number") { if (window.console && console.warn) { console.warn( "[ProgressStep] `ProgressStepper` 안에서만 사용해 주세요. 바깥에서 단독 렌더는 의도된 사용이 아니에요." ); } } const idx = typeof __idx === "number" ? __idx : 0; const state = __state || "future"; const variant = __variant || "compact"; const showCheck = !!__checkForFinish && variant === "icon" && state === "past"; // marker 내부 — variant 별로 다른 콘텐츠 let markerContent = null; if (variant === "icon") { if (showCheck) { markerContent = <__StepperCheckSvg />; } else if (icon != null && icon !== false) { markerContent = icon; } else { // 1-based 숫자 markerContent = {idx + 1}; } } // compact 일 땐 marker 가 작은 도트 — 내부 콘텐츠 없음 // icon prop 이 compact 에서 무시됨을 한 번 알려줌 if (variant === "compact" && icon != null && window.console && console.warn) { console.warn( "[ProgressStep] `icon` 은 `variant=\"icon\"` 일 때만 표시돼요. compact 에서는 무시." ); } const stepCls = [ "tds-stepper__step", `tds-stepper__step--state-${state}`, className, ].filter(Boolean).join(" "); return (
{title ? ( {title} ) : null}
); } // ----- subcomponent 부착 + 글로벌 등록 ----- ProgressStepper.Step = ProgressStep; window.ProgressStepper = ProgressStepper; window.ProgressStep = ProgressStep; Object.assign(window, { ProgressStepper, ProgressStep });