/* ============================================================================ * 서명전에 — TDS Checkbox 컴포넌트 * · 서브: Checkbox.Circle (원형 박스 + 체크) / Checkbox.Line (체크 아이콘만) * · Controlled (checked + onCheckedChange) 또는 * Uncontrolled (defaultChecked 내부 state) 모드 지원. * · inputType="radio" 로 주면 같은 시각으로 라디오 버튼처럼 사용 가능. * 라디오 모드에서는 onChange 의 e.target.value 로 그룹 상태를 관리해요. * · disabled 인 상태에서 클릭하면 좌우로 살짝 흔들리는 애니메이션이 실행돼 * "여기선 선택할 수 없어요" 라는 힌트를 줘요. * · aria-label 은 꼭 제공하세요 — 커스텀 시각과 텍스트 레이블이 분리되는 * 구조라 스크린 리더가 용도를 설명해 줄 라벨이 필요해요. * * · props (CheckboxProps): * inputType "checkbox" (기본) | "radio" * size number — px. 기본 24. * checked boolean — controlled 모드에서 쓰는 현재 선택 상태. * onCheckedChange (checked: boolean) => void * defaultChecked boolean — uncontrolled 초기 상태. * disabled boolean — true 면 클릭 불가 + shake. * value string — 주로 radio 에서 e.target.value 로 판별. * onChange 원본 input onChange 이벤트 패스스루 (radio 패턴에서 활용). * className, style, aria-*, name ... HTMLInputElement 속성 전부 패스스루. * ========================================================================== */ const { useState: __cbxUseState, useRef: __cbxUseRef } = React; function __CheckboxBase({ variant, // "circle" | "line" (내부 전용) inputType = "checkbox", size = 24, checked, defaultChecked, onCheckedChange, onChange, disabled = false, value, className = "", style, ...rest }) { const isControlled = checked !== undefined; const [internalChecked, setInternalChecked] = __cbxUseState(!!defaultChecked); const currentChecked = isControlled ? !!checked : internalChecked; const [shake, setShake] = __cbxUseState(false); const handleInputChange = (e) => { // disabled 는 브라우저가 이미 막지만, radio 네이티브 동작을 우회하는 경우를 // 대비해 한 번 더 차단. if (disabled) return; onChange?.(e); // 라디오는 항상 "선택됨" 으로만 이동 (같은 라디오를 또 눌러도 true). const next = inputType === "radio" ? true : !currentChecked; if (!isControlled) setInternalChecked(next); onCheckedChange?.(next); }; const handleLabelClick = (e) => { // 라벨 클릭 → 네이티브는 input 으로 click 을 전달하지만 disabled 면 차단됨. // 우리는 여기서 shake 애니메이션만 트리거하고 기본 동작을 막는다. if (disabled) { e.preventDefault(); setShake(true); } }; const cls = [ "tds-checkbox", `tds-checkbox--${variant}`, currentChecked && "is-checked", disabled && "is-disabled", shake && "is-shake", className, ] .filter(Boolean) .join(" "); const mergedStyle = { ...(style || {}), "--checkbox-size": `${size}px`, }; // controlled / uncontrolled 에 따라 input 에 주는 prop 을 분기. const inputControlProps = isControlled ? { checked: !!checked } : { defaultChecked: !!defaultChecked }; return ( ); } const Checkbox = {}; Checkbox.Circle = function CheckboxCircle(props) { return <__CheckboxBase variant="circle" {...props} />; }; Checkbox.Line = function CheckboxLine(props) { return <__CheckboxBase variant="line" {...props} />; }; window.Checkbox = Checkbox; Object.assign(window, { Checkbox });