From a1673f6446b3b2ffc0d2ae5b4390380dcfa8424d Mon Sep 17 00:00:00 2001 From: kyuran kim <57716832+gxxrxn@users.noreply.github.com> Date: Mon, 15 Apr 2024 23:28:47 +0900 Subject: [PATCH] =?UTF-8?q?[#521]=20TextArea=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20(#522)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: textarea 컴포넌트 구현 * wip: textarea 개선 작업 중 * refactor: Textarea 고도화 - InputLength, ErrorMessage 구조 수정 - 컴포넌트 파일명 대소문자 수정을 위한 더미커밋 포함 * chore: TextArea 파일명 변경 --- src/stories/base/TextArea.stories.tsx | 54 ++++++++++++++ src/v1/base/ErrorMessage.tsx | 20 +++--- src/v1/base/InputLength.tsx | 4 +- src/v1/base/TextArea.tsx | 100 ++++++++++++++++++++++++++ 4 files changed, 168 insertions(+), 10 deletions(-) create mode 100644 src/stories/base/TextArea.stories.tsx create mode 100644 src/v1/base/TextArea.tsx diff --git a/src/stories/base/TextArea.stories.tsx b/src/stories/base/TextArea.stories.tsx new file mode 100644 index 00000000..b89d7cff --- /dev/null +++ b/src/stories/base/TextArea.stories.tsx @@ -0,0 +1,54 @@ +import { Meta, StoryObj } from '@storybook/react'; +import { SubmitHandler, useForm } from 'react-hook-form'; +import TextArea from '@/v1/base/TextArea'; +import Button from '@/v1/base/Button'; + +const meta: Meta = { + title: 'Base/TextArea', + component: TextArea, +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { placeholder: '어떤 이야기를 모임에서 나누면 좋을까요?' }, +}; + +type FormValue = { + content: string; +}; + +const TextAreaWithForm = () => { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ mode: 'all' }); + + const handleTextAreaSubmit: SubmitHandler = value => { + alert(value.content); + }; + + return ( +
+ + +
+ ); +}; + +export const UseWithForm: Story = { + render: () => , +}; diff --git a/src/v1/base/ErrorMessage.tsx b/src/v1/base/ErrorMessage.tsx index 3a40d9c5..1bfc5490 100644 --- a/src/v1/base/ErrorMessage.tsx +++ b/src/v1/base/ErrorMessage.tsx @@ -1,14 +1,18 @@ -import { IconWarningCircle } from '@public/icons'; import { ReactNode } from 'react'; +import { IconWarningCircle } from '@public/icons'; -const ErrorMessage = ({ children }: { children: ReactNode }) => { +const ErrorMessage = ({ children }: { children?: ReactNode }) => { return ( -
-
- -
- {children} -
+ <> + {children && ( +
+
+ +
+ {children} +
+ )} + ); }; diff --git a/src/v1/base/InputLength.tsx b/src/v1/base/InputLength.tsx index b66db197..d0518a22 100644 --- a/src/v1/base/InputLength.tsx +++ b/src/v1/base/InputLength.tsx @@ -12,10 +12,10 @@ const InputLength = ({ const textColor = isError ? 'text-warning-800 ' : 'text-main-900'; return ( -
+

{currentLength ? currentLength : 0}/ {maxLength} -

+

); }; diff --git a/src/v1/base/TextArea.tsx b/src/v1/base/TextArea.tsx new file mode 100644 index 00000000..03ea3f69 --- /dev/null +++ b/src/v1/base/TextArea.tsx @@ -0,0 +1,100 @@ +import { + ChangeEventHandler, + ForwardedRef, + PropsWithChildren, + TextareaHTMLAttributes, + forwardRef, + useState, + ReactNode, + isValidElement, + Children, +} from 'react'; + +import ErrorMessage from './ErrorMessage'; +import InputLength from './InputLength'; + +interface BaseTextAreaProps + extends TextareaHTMLAttributes { + error?: boolean; +} +interface TextAreaProps extends BaseTextAreaProps { + count?: boolean; +} + +const _TextArea = ( + { + maxLength = 500, + count = false, + error = false, + onChange, + children, + ...props + }: PropsWithChildren, + ref: ForwardedRef +) => { + const [value, setValue] = useState(''); + + const handleChange: ChangeEventHandler = e => { + setValue(e.target.value); + onChange && onChange(e); + }; + + return ( +
+ +
+ {/** 에러 메세지 */} +
{getErrorChildren(children)}
+ {/** 글자수 카운트 */} + {count && ( + + )} +
+
+ ); +}; + +const TextArea = Object.assign(forwardRef(_TextArea), { + Error: ErrorMessage, +}); + +const ErrorMeesageType = ().type; + +const getErrorChildren = (children: ReactNode) => { + const childrenArray = Children.toArray(children); + + return childrenArray.find( + child => isValidElement(child) && child.type === ErrorMeesageType + ); +}; + +export default TextArea; + +const BaseTextArea = forwardRef( + ({ placeholder, error, ...props }, ref) => { + const borderColor = error + ? 'border-warning-800 focus:border-warning-800' + : 'border-black-400 focus:border-main-900'; + + return ( +