/* ============================================================================ * 서명전에 — TDS ProgressBar 컴포넌트 * · 작업의 진행 상태를 시각적으로 보여주는 수평 막대. * · 단일 컴포넌트 — 서브 없음. * * · props (ProgressBarProps): * progress * number 0.0 ~ 1.0 (필수, 범위 밖이면 자동 clamp) * size * "light" | "normal" | "bold" (기본 "normal") * color string fill 색 (CSS color) — 기본 var(--tds-blue-400) * animate boolean true 면 width 변경 시 부드러운 전환 (기본 false) * className string 패스스루 * style object 패스스루 (root div 의 inline style) * id, ...rest 패스스루 * * · 접근성: * - role="progressbar" * - aria-valuenow = round(progress * 100) * - aria-valuemin = 0, aria-valuemax = 100 * - 라벨이 필요한 맥락 (예: "분석 진행률") 에서는 호출자가 * aria-label 또는 aria-labelledby 를 같이 넘겨주세요. * ========================================================================== */ const __PB_VALID_SIZES = { light: 1, normal: 1, bold: 1 }; function ProgressBar({ progress, size = "normal", color, animate = false, className = "", style, id, ...rest }) { // size 검증 if (!__PB_VALID_SIZES[size]) { if (window.console && console.warn) { console.warn( `[ProgressBar] \`size\` 는 "light" | "normal" | "bold" 만 가능해요. 받은 값: ${size}` ); } } const safeSize = __PB_VALID_SIZES[size] ? size : "normal"; // progress 정규화 — 숫자가 아니거나 NaN/Infinity 면 0 으로 const raw = typeof progress === "number" && Number.isFinite(progress) ? progress : 0; const p = Math.max(0, Math.min(1, raw)); const pct = `${(p * 100).toFixed(2)}%`; // 경고 — 0~1 밖이면 사용자가 잘못 쓴 것 if (typeof progress === "number" && Number.isFinite(progress) && (progress < 0 || progress > 1)) { if (window.console && console.warn) { console.warn( `[ProgressBar] \`progress\` 는 0.0 ~ 1.0 사이여야 해요. 받은 값: ${progress} → ${p} 로 보정.` ); } } const cls = [ "tds-progress", `tds-progress--size-${safeSize}`, animate ? "tds-progress--animate" : "", className, ].filter(Boolean).join(" "); // fill 의 width 는 inline % — color 가 있으면 background 도 같이 const fillStyle = { width: pct }; if (color) fillStyle.background = color; return (