/* ============================================================================
* 서명전에 — TDS Stepper 컴포넌트
* · 여러 단계를 시각적으로 보여주는 단계 리스트.
* · ProgressStepper(#142) 와 역할 구분:
* ProgressStepper — "1/3, 2/3, 3/3" 처럼 진행 위치 강조.
* Stepper — 절차 안내·체크리스트·가이드 카드 형태로 단계별
* 제목·설명·우측 액션을 함께 보여 주기.
*
* · 2 컨테이너 + 5 서브:
* Stepper — 루트, children(StepperRow…) stagger 등장 모션
* StepperRow — 한 행, left / center / right 슬롯 + hideLine
* StepperRow.Texts — center 슬롯 텍스트 (type A/B/C 제목·설명)
* StepperRow.NumberIcon — left 숫자 아이콘 (1~9)
* StepperRow.AssetFrame — left 에셋 프레임 (이미지/이모지)
* StepperRow.RightArrow — right 화살표 (>)
* StepperRow.RightButton — right 버튼 (TDS Button 위임)
*
* · Stepper props:
* play (boolean, 기본 true) — 등장 애니메이션 재생 여부.
* false 면 모션 없이 그대로 렌더.
* delay (number, 기본 0) — 첫 행이 등장하기 전 지연 (초).
* staggerDelay (number, 기본 0.1) — 행 간격 (초).
*
* · 등장 모션:
* 각 StepperRow 에 CSS keyframe `tds-stepper-row-in`
* (translateY(8px) → 0, opacity 0 → 1, 360ms cubic-bezier(0.22,1,0.36,1) both).
* Stepper 가 React.cloneElement 로 자식 row 에 인덱스 + delay 변수
* (--tds-stepper-row-delay) 를 inline style 로 주입.
* play=false 면 inline style 로 animation: none + opacity 1 강제.
*
* · 연결선(hideLine=false 기본):
* 좌측 NumberIcon / AssetFrame 컬럼의 가운데에서 시작해 row bottom 까지
* grey-200 1px 세로선이 떨어져요. 마지막 행은 hideLine=true 로 끔.
*
* · 접근성:
* Stepper 루트 role="list" + aria-label="진행 단계" (기본).
* StepperRow role="listitem".
* 장식적 SVG 는 모두 aria-hidden="true".
* ========================================================================== */
const __STEPPER_TEXT_TYPES = { A: 1, B: 1, C: 1 };
// 에셋 프레임 모양 — Asset.frameShape preset 의 우리 측 매핑
const __STEPPER_FRAME_SHAPES = {
CircleSmall: { size: 24, radius: "50%" },
CircleMedium: { size: 36, radius: "50%" },
CircleLarge: { size: 48, radius: "50%" },
CleanW24: { size: 24, radius: 0 },
CleanW32: { size: 32, radius: 0 },
RectSmall: { size: 24, radius: 6 },
RectMedium: { size: 36, radius: 8 },
RectLarge: { size: 48, radius: 10 },
};
// =============================================================================
// Stepper — 컨테이너 (children 에 stagger 등장 모션 주입)
// =============================================================================
function Stepper({
children,
play = true,
delay = 0,
staggerDelay = 0.1,
className = "",
style,
id,
"aria-label": ariaLabel,
...rest
}) {
// ----- props 검증 -----
const safePlay = play !== false; // truthy 만 true 로 (단, false 명시는 false)
const safeDelay = typeof delay === "number" && isFinite(delay) ? Math.max(0, delay) : 0;
const safeStagger = typeof staggerDelay === "number" && isFinite(staggerDelay)
? Math.max(0, staggerDelay)
: 0.1;
// ----- 자식 → 인덱스 + delay 주입 -----
const childArray = React.Children.toArray(children).filter(function (c) {
return React.isValidElement(c);
});
const total = childArray.length;
const enhanced = childArray.map(function (child, idx) {
const rowDelay = safeDelay + idx * safeStagger;
// 자식이 이미 받은 style 을 보존하면서 inline 변수 / animation 합성
const childStyle = (child.props && child.props.style) || {};
const animStyle = safePlay
? {
...childStyle,
animation: `tds-stepper-row-in 360ms cubic-bezier(0.22,1,0.36,1) both`,
animationDelay: `${rowDelay}s`,
}
: {
...childStyle,
animation: "none",
opacity: 1,
transform: "none",
};
// hideLine 이 명시되지 않은 마지막 자식은 자동으로 hideLine=true
const childProps = child.props || {};
const autoHideLine = idx === total - 1 ? true : !!childProps.hideLine;
return React.cloneElement(child, {
style: animStyle,
hideLine: childProps.hideLine !== undefined ? childProps.hideLine : autoHideLine,
__tdsStepperIsLast: idx === total - 1,
});
});
const cls = ["tds-stepper", className].filter(Boolean).join(" ");
return (
{enhanced}
);
}
// =============================================================================
// StepperRow — 한 행 (left + center + right + 연결선)
// =============================================================================
function StepperRow({
left,
center,
right,
hideLine = false,
className = "",
style,
id,
__tdsStepperIsLast, // Stepper 가 주입 — 외부 노출하지 않음
...rest
}) {
// 사용자 친화 경고
if (left == null && window.console && console.warn) {
console.warn("[StepperRow] left prop 이 비어있어요. 보통 NumberIcon 또는 AssetFrame 을 넣어요.");
}
if (center == null && window.console && console.warn) {
console.warn("[StepperRow] center prop 이 비어있어요. 보통 StepperRow.Texts 를 넣어요.");
}
const cls = [
"tds-stepper-row",
hideLine ? "tds-stepper-row--no-line" : "",
className,
]
.filter(Boolean)
.join(" ");
// 외부에 새는 건 안 좋으니 __tdsStepperIsLast 는 비표준 prop 으로 DOM 에 안 흘러가게
void __tdsStepperIsLast;
return (