From 0a9f2ced24d6c8ff718f687854f7287aaba58713 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=A0=95=EC=9B=90?= Date: Sat, 31 Jan 2026 23:40:42 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20=EC=95=84=ED=86=A0=EB=AF=B9=20?= =?UTF-8?q?=EC=9D=B8=ED=92=8B,=20=EB=B9=84=EB=B0=80=EB=B2=88=ED=98=B8?= =?UTF-8?q?=EB=A5=BC=20=EC=A1=B0=ED=95=A9=ED=95=9C=20=EC=9D=B8=ED=92=8B=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/test/page.tsx | 23 ++++++++ src/components/input/Input.module.css | 18 +++++++ src/components/input/Input.tsx | 23 ++++++++ src/components/input/PasswordInput.module.css | 21 ++++++++ src/components/input/PasswordInput.tsx | 52 +++++++++++++++++++ src/components/input/index.ts | 2 + 6 files changed, 139 insertions(+) create mode 100644 src/app/test/page.tsx create mode 100644 src/components/input/Input.module.css create mode 100644 src/components/input/Input.tsx create mode 100644 src/components/input/PasswordInput.module.css create mode 100644 src/components/input/PasswordInput.tsx create mode 100644 src/components/input/index.ts diff --git a/src/app/test/page.tsx b/src/app/test/page.tsx new file mode 100644 index 0000000..089ab76 --- /dev/null +++ b/src/app/test/page.tsx @@ -0,0 +1,23 @@ +import { Input, PasswordInput } from '@/components/input'; + +export default function TestPage() { + return ( +
+

Input 컴포넌트 테스트

+ +

Atom - Input

+
+ +
+ +
+ +
+ +

Molecule - PasswordInput

+
+ +
+
+ ); +} diff --git a/src/components/input/Input.module.css b/src/components/input/Input.module.css new file mode 100644 index 0000000..4cc39c3 --- /dev/null +++ b/src/components/input/Input.module.css @@ -0,0 +1,18 @@ +.input { + width: 100%; + height: 48px; + border-radius: 12px; + border: 1px solid #e2e8f0; + padding: 16px; + background: #ffffff; + font-family: var(--font-pretendard), 'Pretendard', sans-serif; + font-weight: 400; + font-size: 16px; + line-height: 19px; + color: #0f172a; + outline: none; +} + +.input::placeholder { + color: #64748b; +} diff --git a/src/components/input/Input.tsx b/src/components/input/Input.tsx new file mode 100644 index 0000000..3197589 --- /dev/null +++ b/src/components/input/Input.tsx @@ -0,0 +1,23 @@ +import { ComponentPropsWithRef } from 'react'; +import clsx from 'clsx'; +import styles from './Input.module.css'; + +type InputProps = ComponentPropsWithRef<'input'>; + +/** + * 공통 Input 컴포넌트 (Atom) + * + * - 네이티브 의 모든 속성(placeholder, type, onChange 등)을 그대로 사용할 수 있습니다. + * - width는 부모 요소에 맞춰 100%로 채워지므로, 부모에서 width를 지정해 주세요. + * - 비밀번호 입력에는 PasswordInput을 사용하세요. + * + * @example + * // 기본 사용 + * + * + * // react-hook-form 연동 + * + */ +export default function Input({ className, ref, ...props }: InputProps) { + return ; +} diff --git a/src/components/input/PasswordInput.module.css b/src/components/input/PasswordInput.module.css new file mode 100644 index 0000000..9481610 --- /dev/null +++ b/src/components/input/PasswordInput.module.css @@ -0,0 +1,21 @@ +.wrapper { + position: relative; + width: 100%; +} + +.input { + padding-right: 48px; +} + +.toggleButton { + position: absolute; + right: 16px; + top: 50%; + transform: translateY(-50%); + background: none; + border: none; + padding: 0; + cursor: pointer; + display: flex; + align-items: center; +} diff --git a/src/components/input/PasswordInput.tsx b/src/components/input/PasswordInput.tsx new file mode 100644 index 0000000..54b1b30 --- /dev/null +++ b/src/components/input/PasswordInput.tsx @@ -0,0 +1,52 @@ +'use client'; + +import { ComponentPropsWithRef, useState } from 'react'; +import Image from 'next/image'; +import clsx from 'clsx'; +import Input from './Input'; +import visibilityTrue from '@/assets/icons/visibility/visibillityTrue.svg'; +import visibilityFalse from '@/assets/icons/visibility/visibillityFalse.svg'; +import styles from './PasswordInput.module.css'; + +type PasswordInputProps = Omit, 'type'>; + +/** + * 비밀번호 Input 컴포넌트 (Molecule) + * + * - Input(Atom) + 눈 아이콘 토글 조합 + * - 눈 아이콘 클릭으로 비밀번호 표시/숨김 전환 + * - type은 내부에서 관리하므로 별도로 넘기지 않습니다. + * + * @example + * + * + * // react-hook-form 연동 + * + */ +export default function PasswordInput({ className, ref, ...props }: PasswordInputProps) { + const [showPassword, setShowPassword] = useState(false); + + return ( +
+ + +
+ ); +} diff --git a/src/components/input/index.ts b/src/components/input/index.ts new file mode 100644 index 0000000..65ebafa --- /dev/null +++ b/src/components/input/index.ts @@ -0,0 +1,2 @@ +export { default as Input } from './Input'; +export { default as PasswordInput } from './PasswordInput'; From 73168b7abb063048f81aba7bb06c77da886b5430 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=A0=95=EC=9B=90?= Date: Sun, 1 Feb 2026 03:06:01 +0900 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20=EC=95=84=ED=86=A0=EB=AF=B9=20?= =?UTF-8?q?=EB=B0=95=EC=8A=A4=20=EC=9D=B8=ED=92=8B=20=EA=B5=AC=ED=98=84,?= =?UTF-8?q?=20=EC=95=84=ED=86=A0=EB=AF=B9=20=EC=9D=B8=ED=92=8B=EC=9D=98=20?= =?UTF-8?q?=EC=A1=B0=ED=95=A9=EC=9C=BC=EB=A1=9C=20=EB=8C=93=EA=B8=80=20?= =?UTF-8?q?=EC=9D=B8=ED=92=8B=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/input/AccountInput.tsx | 25 ++++++++ src/components/input/ActionTextArea.tsx | 61 +++++++++++++++++++ src/components/input/ChangePassword.tsx | 41 +++++++++++++ src/components/input/CommentInput.tsx | 20 ++++++ src/components/input/Input.tsx | 22 ++----- src/components/input/PasswordInput.tsx | 22 +++---- src/components/input/TextArea.tsx | 13 ++++ src/components/input/index.ts | 5 ++ .../input/styles/AccountInput.module.css | 29 +++++++++ .../input/styles/ActionTextArea.module.css | 26 ++++++++ .../input/styles/ChangePassword.module.css | 24 ++++++++ .../input/styles/CommentInput.module.css | 9 +++ .../input/{ => styles}/Input.module.css | 0 .../{ => styles}/PasswordInput.module.css | 0 .../input/styles/TextArea.module.css | 20 ++++++ src/components/input/types/types.ts | 28 +++++++++ 16 files changed, 314 insertions(+), 31 deletions(-) create mode 100644 src/components/input/AccountInput.tsx create mode 100644 src/components/input/ActionTextArea.tsx create mode 100644 src/components/input/ChangePassword.tsx create mode 100644 src/components/input/CommentInput.tsx create mode 100644 src/components/input/TextArea.tsx create mode 100644 src/components/input/styles/AccountInput.module.css create mode 100644 src/components/input/styles/ActionTextArea.module.css create mode 100644 src/components/input/styles/ChangePassword.module.css create mode 100644 src/components/input/styles/CommentInput.module.css rename src/components/input/{ => styles}/Input.module.css (100%) rename src/components/input/{ => styles}/PasswordInput.module.css (100%) create mode 100644 src/components/input/styles/TextArea.module.css create mode 100644 src/components/input/types/types.ts diff --git a/src/components/input/AccountInput.tsx b/src/components/input/AccountInput.tsx new file mode 100644 index 0000000..cf749e0 --- /dev/null +++ b/src/components/input/AccountInput.tsx @@ -0,0 +1,25 @@ +import Input from './Input'; +import { AccountInputProps } from './types/types'; +import styles from './styles/AccountInput.module.css'; + +/** + * 프로필 페이지용 계정 정보 표시 컴포넌트. + * 이메일과 비밀번호 모두 읽기 전용으로 표시한다. + * @param email 등록된 이메일 주소 + * @param children 하단 영역에 렌더링할 요소 (수정하기 버튼 등) + */ +export default function AccountInput({ email, children }: AccountInputProps) { + return ( +
+
+ + +
+
+ + +
+ {children &&
{children}
} +
+ ); +} diff --git a/src/components/input/ActionTextArea.tsx b/src/components/input/ActionTextArea.tsx new file mode 100644 index 0000000..a77ed8f --- /dev/null +++ b/src/components/input/ActionTextArea.tsx @@ -0,0 +1,61 @@ +'use client'; + +import { useCallback, useRef, useState } from 'react'; +import Image from 'next/image'; +import clsx from 'clsx'; +import TextArea from './TextArea'; +import { ActionTextAreaProps } from './types/types'; +import arrowActive from '@/assets/buttons/arrow/arrowUpActivedButton.svg'; +import arrowInactive from '@/assets/buttons/arrow/arrowUpNonActivedButton.svg'; +import styles from './styles/ActionTextArea.module.css'; + +/** + * 전송 버튼이 포함된 텍스트 입력 기본 컴포넌트. + * 입력값이 있으면 전송 버튼이 활성화된다. + * @param onSubmit 전송 버튼 클릭 시 호출되는 콜백 + * @param wrapperClassName wrapper div에 적용할 추가 CSS 클래스 + * @param className TextArea에 적용할 추가 CSS 클래스 + * @param props 네이티브 textarea의 모든 속성 + */ +export default function ActionTextArea({ + onSubmit, + wrapperClassName, + className, + onChange, + ...props +}: ActionTextAreaProps) { + const [hasValue, setHasValue] = useState(false); + const textareaRef = useRef(null); + + const autoResize = useCallback(() => { + const el = textareaRef.current; + if (!el) return; + el.style.height = 'auto'; + el.style.height = `${el.scrollHeight}px`; + }, []); + + return ( +
+