Skip to content

Commit

Permalink
[#521] TextArea 컴포넌트 (#522)
Browse files Browse the repository at this point in the history
* feat: textarea 컴포넌트 구현

* wip: textarea 개선 작업 중

* refactor: Textarea 고도화

- InputLength, ErrorMessage 구조 수정
- 컴포넌트 파일명 대소문자 수정을 위한 더미커밋 포함

* chore: TextArea 파일명 변경
  • Loading branch information
gxxrxn committed Jun 17, 2024
1 parent 2bd859a commit a1673f6
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 10 deletions.
54 changes: 54 additions & 0 deletions src/stories/base/TextArea.stories.tsx
Original file line number Diff line number Diff line change
@@ -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<typeof TextArea> = {
title: 'Base/TextArea',
component: TextArea,
};

export default meta;

type Story = StoryObj<typeof TextArea>;

export const Default: Story = {
args: { placeholder: '어떤 이야기를 모임에서 나누면 좋을까요?' },
};

type FormValue = {
content: string;
};

const TextAreaWithForm = () => {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<FormValue>({ mode: 'all' });

const handleTextAreaSubmit: SubmitHandler<FormValue> = value => {
alert(value.content);
};

return (
<form onSubmit={handleSubmit(handleTextAreaSubmit)}>
<TextArea
{...register('content', {
maxLength: { value: 3, message: '최대 3자' },
})}
count={true}
error={!!errors.content}
>
<TextArea.Error>{errors.content?.message}</TextArea.Error>
</TextArea>
<Button type="submit" size="full">
submit
</Button>
</form>
);
};

export const UseWithForm: Story = {
render: () => <TextAreaWithForm />,
};
20 changes: 12 additions & 8 deletions src/v1/base/ErrorMessage.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="flex items-center gap-[0.4rem] text-xs text-warning-800">
<div className="h-[1.2rem] w-[1.2rem]">
<IconWarningCircle />
</div>
{children}
</div>
<>
{children && (
<div className="flex items-center gap-[0.4rem] text-xs text-warning-800">
<div className="h-[1.2rem] w-[1.2rem]">
<IconWarningCircle />
</div>
{children}
</div>
)}
</>
);
};

Expand Down
4 changes: 2 additions & 2 deletions src/v1/base/InputLength.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ const InputLength = ({
const textColor = isError ? 'text-warning-800 ' : 'text-main-900';

return (
<div>
<p>
<span className={textColor}>{currentLength ? currentLength : 0}</span>/
{maxLength}
</div>
</p>
);
};

Expand Down
100 changes: 100 additions & 0 deletions src/v1/base/TextArea.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLTextAreaElement> {
error?: boolean;
}
interface TextAreaProps extends BaseTextAreaProps {
count?: boolean;
}

const _TextArea = (
{
maxLength = 500,
count = false,
error = false,
onChange,
children,
...props
}: PropsWithChildren<TextAreaProps>,
ref: ForwardedRef<HTMLTextAreaElement>
) => {
const [value, setValue] = useState('');

const handleChange: ChangeEventHandler<HTMLTextAreaElement> = e => {
setValue(e.target.value);
onChange && onChange(e);
};

return (
<div>
<BaseTextArea
onChange={handleChange}
maxLength={maxLength}
error={error}
ref={ref}
{...props}
/>
<div className="flex justify-between gap-[0.4rem]">
{/** 에러 메세지 */}
<div>{getErrorChildren(children)}</div>
{/** 글자수 카운트 */}
{count && (
<InputLength
currentLength={value.length}
isError={error}
maxLength={maxLength}
/>
)}
</div>
</div>
);
};

const TextArea = Object.assign(forwardRef(_TextArea), {
Error: ErrorMessage,
});

const ErrorMeesageType = (<ErrorMessage />).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<HTMLTextAreaElement, BaseTextAreaProps>(
({ placeholder, error, ...props }, ref) => {
const borderColor = error
? 'border-warning-800 focus:border-warning-800'
: 'border-black-400 focus:border-main-900';

return (
<textarea
className={`min-h-[8rem] w-full resize-none rounded-[0.5rem] border-[0.05rem] p-[1rem] text-sm outline-none placeholder:text-placeholder ${borderColor}`}
placeholder={placeholder || '내용을 입력해주세요'}
ref={ref}
{...props}
/>
);
}
);

BaseTextArea.displayName = 'BaseTextArea';

0 comments on commit a1673f6

Please sign in to comment.