"use client"; import { useEffect, useRef, type ChangeEvent, type ComponentPropsWithRef, type ReactNode, } from "react"; import { cx } from "../../utils"; import { FoxCheckIcon } from "../fox-check-icon"; export type FoxCheckboxSize = "lg" | "md"; /** * 네이티브 ``가 받는 것을 그대로 넘겨받는다 — name·value·checked· * defaultChecked·disabled·required·form 등. 의미가 겹치는 것만 걷어내고 아래에서 다시 정의한다. */ type NativeCheckboxProps = Omit< ComponentPropsWithRef<"input">, "type" | "size" | "className" | "children" | "hidden" | "onChange" >; export interface FoxCheckboxProps extends NativeCheckboxProps { size?: FoxCheckboxSize; label: ReactNode; /** 라벨 아래 보조 설명. 넘기지 않으면 영역을 렌더링하지 않는다. */ description?: ReactNode; /** * 부분선택. "전체 동의"처럼 자식 일부만 켜진 상태를 나타낸다. * * HTML 속성이 아니라 DOM 프로퍼티라 마크업만으로는 표현할 수 없다 — 이 컴포넌트가 대신 * 넣어 준다. 켜짐 여부와는 별개라, DOM에서 `checked`와 함께 참일 수 있고 그때는 부분선택 * 모양이 이긴다. */ indeterminate?: boolean; /** * 켜지거나 꺼질 때 부른다. 켜짐 여부가 첫 인자다 — 네이티브 `onChange`를 `Omit`으로 * 걷어내고 그 자리를 대신한다(`FoxInput`과 같은 방식). */ onChange?: (checked: boolean, event: ChangeEvent) => void; /** * 글자를 눈에서만 감춘다 — 표의 선택 열처럼 네모(동그라미)만 필요한 자리에 쓴다. * DOM에는 남으므로 스크린리더는 그대로 읽는다. `description`도 함께 감춰진다. */ hideLabel?: boolean; /** 참이면 렌더하지 않는다(DOM에 남지 않는다). */ hidden?: boolean; /** 배치 조정용. */ className?: string; } const SIZE_CLASS: Record = { lg: "fox-checkbox--lg", md: "fox-checkbox--md", }; /** * @fox 체크박스 한 칸. 켜짐 여부는 네이티브 ``가 소유한다. * * 라디오와 달리 `name`이 같아도 서로를 끄지 않는다. 여러 개를 동시에 고를 수 있고, 폼 전송 * 시 고른 것들이 함께 나간다. * * 루트가 `