diff --git a/components/DayPicker.tsx b/components/DayPicker.tsx
new file mode 100644
index 0000000..eb4d285
--- /dev/null
+++ b/components/DayPicker.tsx
@@ -0,0 +1,37 @@
+import { ko } from 'date-fns/locale';
+import { DayPicker } from 'react-day-picker';
+
+import 'react-day-picker/dist/style.css';
+
+interface CustomDayPickerProps {
+ selected?: Date;
+ onSelect?: (date: Date | undefined) => void;
+}
+
+function CustomDayPicker({ selected, onSelect }: CustomDayPickerProps) {
+ return (
+
+
+
+ );
+}
+
+export default CustomDayPicker;
diff --git a/components/Input.tsx b/components/Input.tsx
new file mode 100644
index 0000000..6c36ca8
--- /dev/null
+++ b/components/Input.tsx
@@ -0,0 +1,120 @@
+import { useValidation } from 'hooks/useValidation';
+import React, { useState } from 'react';
+
+import CustomDayPicker from './DayPicker';
+
+interface InputFieldProps {
+ value: string;
+ onChange: (e: React.ChangeEvent) => void;
+ type?: 'text' | 'email' | 'password' | 'name' | 'passwordConfirm';
+ placeholder?: string;
+ label?: string;
+ compareValue?: string;
+ layout?: 'vertical' | 'horizontal';
+}
+
+function InputField({
+ value,
+ onChange,
+ type = 'text',
+ placeholder,
+ label,
+ compareValue,
+ layout = 'vertical',
+}: InputFieldProps) {
+ const { errorMessage, validate } = useValidation({
+ type,
+ compareValue,
+ });
+
+ const [showDayPicker, setShowDayPicker] = useState(false);
+
+ const handleFocus = () => {
+ if (layout === 'horizontal' && label === '생일') {
+ setShowDayPicker(true);
+ }
+ };
+
+ const closeDayPicker = () => {
+ setShowDayPicker(false);
+ };
+
+ const handleBlur = () => {
+ if (layout === 'vertical')
+ // 가로모드 에러 확인 비활성화
+ validate(value);
+ };
+
+ const handleChange = (e: React.ChangeEvent) => {
+ onChange(e);
+ if (layout === 'vertical' && errorMessage) {
+ validate(e.target.value);
+ }
+ };
+ const getInputType = () => {
+ if (type === 'name') {
+ return 'text';
+ } else if (type === 'passwordConfirm') {
+ return 'password';
+ }
+ return type;
+ };
+ //스타일에 따른 클래스
+ const variantClass = {
+ containerVertical: 'mb-[24px] flex flex-col gap-[10px]',
+ containerHorizontal: 'mb-[24px] w-[239px] 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-[400px] rounded-md text-[14px] text-gray-500 placeholder:text-14 focus:outline-none mo:w-[355px]',
+ 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 =
+ layout === 'horizontal'
+ ? variantClass.labelHorizontal
+ : variantClass.labelVertical;
+
+ const inputClass = `${variantClass.base} ${
+ layout === 'vertical' && errorMessage
+ ? variantClass.error
+ : variantClass.normal
+ }`;
+
+ return (
+
+ {label &&
}
+
+ {layout === 'vertical' && errorMessage && (
+
{errorMessage}
+ )}
+ {showDayPicker && (
+
+ )}
+
+ );
+}
+
+export default InputField;
diff --git a/components/SearchInput.tsx b/components/SearchInput.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/hooks/useValidation.tsx b/hooks/useValidation.tsx
new file mode 100644
index 0000000..63709f9
--- /dev/null
+++ b/hooks/useValidation.tsx
@@ -0,0 +1,59 @@
+import { useState } from 'react';
+
+type ValidationType =
+ | 'text'
+ | 'email'
+ | 'password'
+ | 'name'
+ | 'passwordConfirm';
+
+interface UseValidationProps {
+ type: ValidationType;
+ compareValue?: string;
+}
+
+export function useValidation({ type, compareValue }: UseValidationProps) {
+ const [errorMessage, setErrorMessage] = useState(
+ undefined
+ );
+
+ const validateInput = (value: string) => {
+ if (!value) return undefined;
+
+ switch (type) {
+ case 'email':
+ if (!/\S+@\S+\.\S+/.test(value)) {
+ return '이메일 형식으로 작성해 주세요.';
+ }
+ break;
+ case 'password':
+ if (value.length < 8) {
+ return '8자 이상 입력해주세요.';
+ }
+ break;
+ case 'name':
+ if (value.length > 10) {
+ return '열 자 이하로 작성해주세요.';
+ }
+ break;
+ case 'passwordConfirm':
+ if (compareValue !== undefined && value !== compareValue) {
+ return '비밀번호가 일치하지 않습니다.';
+ }
+ break;
+ }
+ return undefined;
+ };
+
+ const validate = (value: string) => {
+ const error = validateInput(value);
+ setErrorMessage(error);
+ return error;
+ };
+
+ return {
+ errorMessage,
+ validate,
+ setErrorMessage,
+ };
+}
diff --git a/pages/signup/index.tsx b/pages/signup/index.tsx
new file mode 100644
index 0000000..0e3432d
--- /dev/null
+++ b/pages/signup/index.tsx
@@ -0,0 +1,72 @@
+import { useState } from 'react';
+
+import InputField from '@/components/Input';
+
+const SignUp: React.FC = () => {
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const [passwordConfirm, setPasswordConfirm] = useState('');
+ const [name, setName] = useState('');
+
+ const handleEmailChange = (e: React.ChangeEvent) => {
+ setEmail(e.target.value);
+ };
+
+ const handlePasswordChange = (e: React.ChangeEvent) => {
+ setPassword(e.target.value);
+ };
+
+ const handlePasswordConfirmChange = (
+ e: React.ChangeEvent
+ ) => {
+ setPasswordConfirm(e.target.value);
+ };
+
+ const handleNameChange = (e: React.ChangeEvent) => {
+ setName(e.target.value);
+ };
+
+ const handleSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+ alert('가입이 완료되었습니다');
+ };
+
+ return (
+
+ );
+};
+
+export default SignUp;
diff --git a/pages/test/index.tsx b/pages/test/index.tsx
index aac1243..29e84dc 100644
--- a/pages/test/index.tsx
+++ b/pages/test/index.tsx
@@ -1,5 +1,3 @@
-import { useState } from 'react';
-
import Button from '@/components/Button';
import LinkBar from '@/components/LinkBar';
import SnackBar from '@/components/SnackBar';
@@ -34,6 +32,12 @@ export default function Test() {
+
+ | inputfield |
+
+
+ |
+
| Button |
@@ -65,6 +69,11 @@ export default function Test() {
| LinkBar |
+
+ | inputfield |
+
+
+ |
diff --git a/tsconfig.json b/tsconfig.json
index 03d1b81..c32ac62 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,11 +1,7 @@
{
"compilerOptions": {
"target": "ES2017",
- "lib": [
- "dom",
- "dom.iterable",
- "esnext"
- ],
+ "lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
@@ -20,15 +16,9 @@
"incremental": true,
"baseUrl": ".",
"paths": {
- "@/components/*": [
- "components/*"
- ],
- "@/styles/*": [
- "styles/*"
- ],
- "@/pages/*": [
- "pages/*"
- ]
+ "@/components/*": ["components/*"],
+ "@/styles/*": ["styles/*"],
+ "@/pages/*": ["pages/*"]
}
},
"include": [
@@ -37,9 +27,5 @@
"components/**/*.ts",
"components/**/*.tsx"
],
- "exclude": [
- "node_modules",
- "dist",
- ".next"
- ]
+ "exclude": ["node_modules", "dist", ".next"]
}