import React, { forwardRef } from 'react' // ─── Input ──────────────────────────────────────────────────────────────────── export interface InputProps { type?: 'text' | 'email' | 'password' | 'number' | 'tel' | 'url' label?: string | React.ReactNode placeholder?: string error?: string helpText?: string disabled?: boolean required?: boolean leftIcon?: string rightIcon?: string leftSlot?: React.ReactNode rightSlot?: React.ReactNode className?: string labelClassName?: string inputClassName?: string value?: string defaultValue?: string onChange?: (e: React.ChangeEvent) => void id?: string name?: string pattern?: string minLength?: number maxLength?: number step?: number } export const Input = forwardRef( ( { type = 'text', label, placeholder, error, helpText, disabled = false, required = false, leftIcon, rightIcon, leftSlot, rightSlot, className = '', labelClassName = '', inputClassName = '', value, defaultValue, onChange, id, name, pattern, minLength, maxLength, step, }, ref ) => { const hasLeft = leftIcon || leftSlot const hasRight = rightIcon || rightSlot return (
{label && ( )}
{leftIcon && ( {leftIcon} )} {leftSlot && (
{leftSlot}
)} {rightIcon && ( {rightIcon} )} {rightSlot && (
{rightSlot}
)}
{error && (

{error}

)} {helpText && !error && (

{helpText}

)}
) } ) Input.displayName = 'Input' // ─── SearchInput ────────────────────────────────────────────────────────────── export const SearchInput = forwardRef) => void id?: string name?: string }>( ( { placeholder = '검색...', error, helpText, disabled = false, className = '', value, defaultValue, onChange, id, name, }, ref ) => (
search
{error &&

{error}

} {helpText && !error &&

{helpText}

}
) ) SearchInput.displayName = 'SearchInput' // ─── Select ─────────────────────────────────────────────────────────────────── export const Select = forwardRef) => void id?: string name?: string }>( ( { label, children, error, helpText, disabled = false, required = false, className = '', selectClassName = '', defaultValue, value, onChange, id, name, }, ref ) => (
{label && ( )}
expand_more
{error &&

{error}

} {helpText && !error &&

{helpText}

}
) ) Select.displayName = 'Select' // ─── Toggle ─────────────────────────────────────────────────────────────────── export const Toggle: React.FC<{ checked: boolean onChange?: (checked: boolean) => void disabled?: boolean label?: string | React.ReactNode className?: string labelClassName?: string }> = ({ checked, onChange, disabled = false, label, className = '', labelClassName = '' }) => (
{label && ( {label} )}
) // ─── Checkbox ───────────────────────────────────────────────────────────────── export const Checkbox: React.FC<{ checked: boolean onChange?: (checked: boolean) => void disabled?: boolean label?: string | React.ReactNode className?: string labelClassName?: string }> = ({ checked, onChange, disabled = false, label, className = '', labelClassName = '' }) => ( ) // ─── Radio ──────────────────────────────────────────────────────────────────── export const Radio: React.FC<{ checked: boolean onChange?: (checked: boolean) => void disabled?: boolean label?: string | React.ReactNode labelClassName?: string name?: string value?: string }> = ({ checked, onChange, disabled = false, label, labelClassName = '', name, value }) => ( ) // ─── NumberInput ────────────────────────────────────────────────────────────── export const NumberInput = forwardRef void min?: number max?: number step?: number unit?: string error?: string helpText?: string disabled?: boolean required?: boolean className?: string id?: string name?: string }>( ( { label, value, defaultValue, onChange, min, max, step = 1, unit, error, helpText, disabled = false, required = false, className = '', id, name, }, ref ) => { const handleStep = (dir: 1 | -1) => { if (disabled || onChange == null) return const current = value ?? defaultValue ?? 0 const next = current + dir * step if (min != null && next < min) return if (max != null && next > max) return onChange(next) } return (
{label && ( )}
onChange?.(parseFloat(e.target.value))} disabled={disabled} required={required} id={id} name={name} className={` w-full bg-slate-50 rounded-[10px] pl-3.5 ${unit ? 'pr-10' : 'pr-8'} py-2.5 text-body-sm text-slate-800 outline-none [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none disabled:bg-slate-100 disabled:text-slate-400 disabled:cursor-not-allowed ${error ? 'bg-red-50 border-[1.5px] border-red-300' : 'border-[1.5px] border-slate-200 focus:ring-1 focus:ring-teal-500 focus:border-teal-300'} `} /> {unit ? ( {unit} ) : (
)}
{error &&

{error}

} {helpText && !error &&

{helpText}

}
) } ) NumberInput.displayName = 'NumberInput' // ─── Slider ─────────────────────────────────────────────────────────────────── export const Slider: React.FC<{ value: number onChange?: (value: number) => void min?: number max?: number step?: number disabled?: boolean showValue?: boolean label?: string className?: string }> = ({ value, onChange, min = 0, max = 100, step = 1, disabled = false, showValue = true, label, className = '', }) => (
{(label || showValue) && (
{label && {label}} {showValue && ( {value} )}
)} onChange?.(parseFloat(e.target.value))} disabled={disabled} className=" w-full h-1.5 bg-slate-200 rounded-full appearance-none cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:h-4 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-teal-500 [&::-webkit-slider-thumb]:shadow-soft-sm [&::-webkit-slider-thumb]:cursor-pointer [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-white " />
{min} {max}
) // ─── Textarea ───────────────────────────────────────────────────────────────── export const Textarea = forwardRef) => void id?: string name?: string minLength?: number maxLength?: number }>( ( { label, placeholder, error, helpText, disabled = false, required = false, rows = 3, className = '', textareaClassName = '', value, defaultValue, onChange, id, name, minLength, maxLength, }, ref ) => (
{label && ( )}