"use client"; import * as React from "react"; import { cn } from "@/lib/utils"; import { formatCardNumber, parseCardNumber } from "@/lib/formatters"; export interface CardNumberInputProps extends Omit, "onChange" | "value" | "type"> { value: string; onChange: (value: string) => void; error?: boolean; } /** * CardNumberInput - 카드번호 자동 포맷팅 입력 컴포넌트 * * 특징: * - 입력 시 자동 하이픈 삽입 (0000-0000-0000-0000) * - 숫자만 입력 허용 (최대 16자리) * - onChange에는 숫자만 전달 (DB 저장용) * - 복사/붙여넣기 시 숫자만 추출 * * @example * */ const CardNumberInput = React.forwardRef( ({ className, value, onChange, error, ...props }, ref) => { // 마스킹된 값(****-****-****-1234) 여부 판별 const isMasked = value?.includes('*') ?? false; // 표시용 포맷된 값: 마스킹이면 그대로, 숫자만이면 포맷팅 const displayValue = React.useMemo(() => { if (!value) return ""; if (isMasked) return value; return formatCardNumber(value); }, [value, isMasked]); const handleChange = (e: React.ChangeEvent) => { const inputValue = e.target.value; // 숫자만 추출하여 전달 (마스킹 문자 자동 제거) const numbersOnly = parseCardNumber(inputValue); onChange(numbersOnly); }; const handleKeyDown = (e: React.KeyboardEvent) => { // 숫자, 백스페이스, 탭, 화살표, 복사/붙여넣기 허용 const allowedKeys = [ "Backspace", "Delete", "Tab", "Escape", "Enter", "ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End" ]; if (allowedKeys.includes(e.key)) return; // Ctrl/Cmd + A, C, V, X 허용 if ((e.ctrlKey || e.metaKey) && ["a", "c", "v", "x"].includes(e.key.toLowerCase())) { return; } // 숫자가 아니면 차단 if (!/^\d$/.test(e.key)) { e.preventDefault(); } }; const handlePaste = (e: React.ClipboardEvent) => { e.preventDefault(); const pastedText = e.clipboardData.getData("text"); const numbersOnly = parseCardNumber(pastedText); onChange(numbersOnly); }; return ( ); } ); CardNumberInput.displayName = "CardNumberInput"; export { CardNumberInput };