diff --git a/components/DayPicker.tsx b/components/DayPicker.tsx deleted file mode 100644 index 7da2056..0000000 --- a/components/DayPicker.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { ko } from 'date-fns/locale'; -import { DayPicker } from 'react-day-picker'; - -import 'react-day-picker/dist/style.css'; - -interface CustomDayPickerProps { - selected: Date | undefined; - onSelect?: (date: Date | undefined) => void; -} - -function CustomDayPicker({ - selected, - onSelect = () => {}, -}: CustomDayPickerProps) { - return ( -
- -
- ); -} - -export default CustomDayPicker; diff --git a/components/Input.tsx b/components/Input.tsx index 31a4d7f..32a75dd 100644 --- a/components/Input.tsx +++ b/components/Input.tsx @@ -1,9 +1,6 @@ import { useValidation } from 'hooks/useValidation'; import Image from 'next/image'; -import React, { useState } from 'react'; -import { format } from 'date-fns'; - -import CustomDayPicker from './DayPicker'; +import React, { useState, forwardRef } from 'react'; interface InputFieldProps { value: string; @@ -15,173 +12,137 @@ interface InputFieldProps { layout?: 'vertical' | 'horizontal'; width?: string; onValidation?: (isValid: boolean) => void; - ref?: React.Ref; disabled?: boolean; } -function InputField({ - value, - onChange, - type = 'text', - placeholder, - label, - compareValue, - layout = 'vertical', - width, - onValidation, - ref, - disabled, - ...props -}: InputFieldProps) { - const { errorMessage, validate } = useValidation({ - type, - compareValue: compareValue ?? '', - }); - - const [showDayPicker, setShowDayPicker] = useState(false); - const [showPassword, setShowPassword] = useState(false); - - const handleFocus = () => { - if ( - typeof layout === 'string' && - layout === 'horizontal' && - typeof label === 'string' && - label === '생일' - ) { - setShowDayPicker(true); - } - }; - - const closeDayPicker = () => { - setShowDayPicker(false); - }; +const InputField = forwardRef( + ( + { + value, + onChange, + type = 'text', + placeholder, + label, + compareValue, + layout = 'vertical', + width, + onValidation, + disabled, + }, + ref + ) => { + const { errorMessage, validate } = useValidation({ + type, + compareValue: compareValue ?? '', + }); - const handleBlur = () => { - if (value) { - const error = validate(value); - const isValid = !error && value.length > (type === 'name' ? 1 : 0); - onValidation?.(isValid); - } - }; + const [showPassword, setShowPassword] = useState(false); - const handleChange = (e: React.ChangeEvent) => { - const newValue = e.target.value.trim(); - onChange(e); - const error = validate(newValue); - const isValid = !error && newValue.length > (type === 'name' ? 1 : 0); - onValidation?.(isValid); - }; + const handleChange = (e: React.ChangeEvent) => { + const newValue = e.target.value.trim(); + onChange(e); + if (label !== '생일') { + const error = validate(newValue); + const isValid = !error && newValue.length > (type === 'name' ? 1 : 0); + onValidation?.(isValid); + } + }; - const getInputType = () => { - if (type === 'name') { - return 'text'; - } else if (type === 'passwordConfirm' || type === 'password') { - return showPassword ? 'text' : 'password'; - } - return type; - }; + const getInputType = () => { + if (type === 'name') { + return 'text'; + } else if (type === 'passwordConfirm' || type === 'password') { + return showPassword ? 'text' : 'password'; + } else if (label === '생일') { + return 'date'; + } + return type; + }; - const togglePasswordVisibility = () => { - setShowPassword(!showPassword); - }; + const togglePasswordVisibility = () => { + setShowPassword(!showPassword); + }; - //스타일에 따른 클래스 - const variantClass = { - containerVertical: 'flex flex-col gap-[10px]', - containerHorizontal: 'flex items-center gap-[10px]', - labelVertical: 'text-14 text-gray-500', - labelHorizontal: 'text-14 text-gray-400 w-[60px] flex-shrink-0', - base: 'px-[20px] py-[10px] h-[45px] w-full rounded-md text-[14px] text-gray-500 placeholder:text-14 focus:outline-none', - error: 'border border-red-100 bg-red-50', - normal: - 'bg-gray-100 focus:border-green-200 focus:ring-1 focus:ring-green-200', - errorText: 'text-12 text-red-100', - }; + //스타일에 따른 클래스 + const variantClass = { + containerVertical: 'flex flex-col gap-[10px]', + containerHorizontal: 'flex items-center gap-[10px]', + labelVertical: 'text-14 text-gray-500', + labelHorizontal: 'text-14 text-gray-400 w-[60px] flex-shrink-0', + base: 'px-[20px] py-[10px] h-[45px] w-full rounded-md text-[14px] text-gray-500 placeholder:text-14 focus:outline-none', + error: 'border border-red-100 bg-red-50', + normal: + 'bg-gray-100 focus:border-green-200 focus:ring-1 focus:ring-green-200', + errorText: 'text-12 text-red-100', + }; - const labelClass = - typeof layout === 'string' && layout === 'horizontal' - ? variantClass.labelHorizontal - : variantClass.labelVertical; + const labelClass = + typeof layout === 'string' && layout === 'horizontal' + ? variantClass.labelHorizontal + : variantClass.labelVertical; - const inputClass = `${variantClass.base} ${ - typeof layout === 'string' && - layout === 'vertical' && - typeof errorMessage === 'string' && - errorMessage.length > 0 - ? variantClass.error - : variantClass.normal - } ${disabled ? 'opacity-50 pointer-events-none' : ''}`; + const inputClass = `${variantClass.base} ${ + typeof layout === 'string' && + layout === 'vertical' && + typeof errorMessage === 'string' && + errorMessage.length > 0 + ? variantClass.error + : variantClass.normal + } ${disabled ? 'opacity-50 pointer-events-none' : ''}`; - return ( -
0 ? { width } : undefined - } - > - {typeof label === 'string' && label.length > 0 && ( - - )} -
- - {(type === 'password' || type === 'passwordConfirm') && ( - + return ( +
0 ? { width } : undefined + } + > + {typeof label === 'string' && label.length > 0 && ( + )} -
- {errorMessage && ( - {errorMessage} - )} - {typeof showDayPicker === 'boolean' && showDayPicker && ( -
-
- { - if (date) { - const formattedDate = format(date, 'yyyy-MM-dd'); - onChange({ - target: { value: formattedDate }, - } as React.ChangeEvent); - setShowDayPicker(false); +
+ + {(type === 'password' || type === 'passwordConfirm') && ( +
- + alt={showPassword ? '비밀번호 숨기기' : '비밀번호 보기'} + width={20} + height={20} + /> + + )}
- )} -
- ); -} + {errorMessage && ( + {errorMessage} + )} +
+ ); + } +); + +InputField.displayName = 'InputField'; export default InputField; diff --git a/components/SearchInput.tsx b/components/SearchInput.tsx index 86f7ebf..5406fc4 100644 --- a/components/SearchInput.tsx +++ b/components/SearchInput.tsx @@ -36,18 +36,15 @@ function SearchInput({ const currentSize = sizeStyles[size]; - const inputStyles = [ - currentSize.container, - currentSize.padding, + const formStyles = [ + 'flex', + 'items-center', 'rounded-lg', - 'border-none', 'bg-gray-100', - 'text-gray-500', - 'text-16', - 'placeholder:text-gray-400', - isFocused - ? 'outline outline-2 outline-green-100' - : 'focus:outline-green-100', + 'px-[20px]', + currentSize.container, + currentSize.padding, + isFocused ? 'outline outline-2 outline-green-100' : '', ].join(' '); function handleInputChange(e: React.ChangeEvent) { @@ -62,21 +59,26 @@ function SearchInput({ return (
{ + if (!e.currentTarget.contains(e.relatedTarget as Node)) { + setIsFocused(false); + } + }} > -