/* ============================================================================ * 서명전에 — TDS TableRow 컴포넌트 * · 한 행 안에 [왼쪽 슬롯][오른쪽 슬롯] 으로 데이터를 좌우로 배치하는 단일 컴포넌트. * "정보 제목 ─ 내용" 같은 대비를 가장 적은 마크업으로 만들 때 써요. * * · 공통: * 좌우 슬롯은 React.ReactNode (문자열/숫자/컴포넌트 모두 가능). * 높이는 콘텐츠 + 12px 위아래 패딩으로 자동 계산. * 텍스트는 sub-ty-5 (16px) regular / 라벨 측은 grey-600, 값 측은 grey-900 이 기본. * 두 슬롯 모두 word-break: keep-all 로 한국어 가독성 유지. * * · align (필수): * "space-between" — 왼쪽 시작 / 오른쪽 끝. 라벨-값을 양 끝으로 분명히 분리. * right 는 text-align: right (긴 값도 우측 정렬). * "left" — 둘 다 왼쪽으로 모아 배치. 좁은 카드/필드 안에서 라벨-값을 * 밀집시킬 때. leftRatio 와 함께 쓰면 라벨 너비가 균일해져 * 여러 행을 시각적으로 정렬할 수 있어요. * * · leftRatio (옵션, number): * 0~100 사이의 % 값. left 슬롯의 너비를 전체의 N% 로 고정. * "left" 모드에서 라벨 폭을 줄로 맞추는 용도가 가장 일반적. * "space-between" 에서도 동작하지만, 양 끝 정렬과 비율 고정의 의미가 * 충돌할 수 있어 주로 left 에서 권장. 범위 밖 값은 [0,100] 로 클램프. * * · 접근성: * 구조적으로는 단순 행이라 role 을 강제하지 않아요. 호출자가 의미를 가진 * 행으로 묶고 싶으면 부모에 role="list" + 자식에 role="listitem" 등을 부여. * 텍스트 색 대비는 라벨 grey-600 / 값 grey-900 으로 WCAG AA 통과. * * · BoardRow(#121) 와의 차이: * BoardRow — 펼침/접힘이 있는 Q&A · FAQ 행 (aria-expanded, 콘텐츠 영역). * TableRow — 펼침 없이 한 줄에 좌/우 두 슬롯만. 영수증, 메타 정보, 라벨-값 표. * ========================================================================== */ function TableRow({ left, right, align, leftRatio, className = "", style, ...rest }) { // ----- props 검증 ----- if (align !== "left" && align !== "space-between") { if (window.console && console.warn) { console.warn( `[TableRow] align 은 "left" | "space-between" 만 가능해요. 받은 값: ${align}. "space-between" 로 폴백.` ); } align = "space-between"; } if (left === undefined || left === null) { if (window.console && console.warn) { console.warn("[TableRow] left 는 필수예요. 왼쪽 슬롯이 비어 있어요."); } } if (right === undefined || right === null) { if (window.console && console.warn) { console.warn("[TableRow] right 는 필수예요. 오른쪽 슬롯이 비어 있어요."); } } // ----- leftRatio 정규화 ----- let resolvedRatio = null; if (leftRatio !== undefined && leftRatio !== null) { if (typeof leftRatio !== "number" || !isFinite(leftRatio)) { if (window.console && console.warn) { console.warn( `[TableRow] leftRatio 는 number 여야 해요. 받은 값: ${leftRatio} (${typeof leftRatio}). 무시함.` ); } } else { const clamped = Math.max(0, Math.min(100, leftRatio)); if (clamped !== leftRatio) { if (window.console && console.warn) { console.warn( `[TableRow] leftRatio 는 0~100 사이여야 해요. 받은 값: ${leftRatio}. ${clamped} 로 클램프.` ); } } resolvedRatio = clamped; } } // ----- 클래스 ----- const cls = [ "tds-table-row", `tds-table-row--${align}`, resolvedRatio !== null ? "tds-table-row--has-ratio" : "", className, ] .filter(Boolean) .join(" "); // ----- left 인라인 폭 ----- // leftRatio 가 지정되면 left 슬롯 너비를 % 로 고정 (flex 도 동시 지정해서 // 컨테이너 안에서 비율이 깨지지 않게) const leftStyle = resolvedRatio !== null ? { flex: `0 0 ${resolvedRatio}%`, width: `${resolvedRatio}%`, maxWidth: `${resolvedRatio}%` } : undefined; return (