Skip to content

Commit

Permalink
feat: Textarea 컴포넌트 구현 (#150)
Browse files Browse the repository at this point in the history
* feat: Textarea 컴포넌트 구현 (#146)

* Revert "feat: Textarea 컴포넌트 구현 (#146)"

This reverts commit afb1018.

* feat: Textarea 컴포넌트 구현 (#146)

* fix: 사라져버린 Pagination, useRadioGroup export 살리기

* refactor: currentLength 코드 삭제 -> value.length로 처리

* refactor: Textarea 타입 중복 정의 제거

(plaholder, disabled, maxLength)

* refactor: StyledContainer에서 사용 안하는 StyledTextareaProps 삭제

* refactor: e.target.value -> 선언한 newValue로 변경

* style: textStatusNegative -> lineStatusPositive

* style: border 관련 스타일 line 스타일로 적용

* style: error 상황일 때와 focus 상황일 때 border 코드 수정

* feat: value prop 삭제, width height 타입 변경

* feat: 스크롤바 커스텀 작업

* docs: onValueChange prop 관련 문서 작성

* feat: helperText 로직 수정

* style: 스크롤바와 텍스트 여백 padding: 6px 추가
  • Loading branch information
seocylucky authored Aug 30, 2024
1 parent eb10275 commit 6c6e479
Show file tree
Hide file tree
Showing 7 changed files with 457 additions and 2 deletions.
146 changes: 146 additions & 0 deletions src/components/Textarea/Textarea.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { Canvas, Meta, Controls } from '@storybook/blocks';
import * as TextareaStories from './Textarea.stories';
import { Textarea } from './Textarea';
import React from 'react';

<Meta of={TextareaStories} />

# Textarea

사용자가 텍스트를 입력하는 필드로, 여러 줄의 텍스트 입력이 필요한 경우 사용됩니다. 다양한 상태와 속성을 지원하여 사용자 경험을 향상시킬 수 있습니다.

<Canvas of={TextareaStories.Default} />
<Controls />

<br />
<br />

## 사용법

Textarea의 기본 사용법입니다.

필수 프로퍼티인 `width``height`를 사용하여 Textarea의 크기를 설정해주세요.

```tsx
import { Textarea } from '@yourssu/design-system-react';
```

```tsx
<Textarea width="343px" height="187px" />
```

이외의 프로퍼티들은 하단의 예시에서 확인할 수 있습니다.

<br />
<br />

## 예시

### placeholder

Textarea에 표시되는 짧은 안내 문구로, 사용자가 입력해야 할 내용의 예시를 보여줍니다.

```tsx
<Textarea width="343px" height="187px" placeholder="Enter text here..." />
```

<Canvas of={TextareaStories.Placeholder} withSource="none" />

<br />
<br />

### helperText

Textarea에 사용자가 올바르게 입력할 수 있도록 돕는 텍스트입니다.

```tsx
<Textarea
width="343px"
height="187px"
helperText="helperText입니다."
placeholder="Enter text here..."
/>
```

<Canvas of={TextareaStories.HelperText} withSource="none" />

<br />
<br />

### maxLength

Textarea에 사용자가 입력할 수 있는 최대 글자 수를 제한합니다.

```tsx
<Textarea width="343px" height="187px" maxLength={50} placeholder="Max 50 characters" />
```

<Canvas of={TextareaStories.MaxLength} withSource="none" />

<br />
<br />

### disabled

Textarea에 어떠한 입력을 할 수 없도록 막습니다.

```tsx
<Textarea
width="343px"
height="187px"
disabled={true}
helperText="Text Inputting"
placeholder="This field is disabled"
/>
```

<Canvas of={TextareaStories.Disabled} withSource="none" />

<br />
<br />

### error

Textarea에 잘못된 값이 입력되었을 때 사용자에게 오류 상태를 표시하는 데 사용됩니다.

```tsx
<Textarea
width="343px"
height="187px"
error={true}
helperText="Text Inputting"
placeholder="There is an error"
/>
```

<Canvas of={TextareaStories.Error} withSource="none" />

<br />
<br />

### onValueChange

Textarea에 텍스트를 입력하거나 변경할 때 호출되는 콜백 함수입니다. 이 함수는 현재 입력된 텍스트 값을 매개변수로 전달하여, 외부에서 상태를 관리하거나 추가적인 로직을 실행할 수 있게 합니다.

```tsx
const [text, setText] = useState('');

const handleValueChange = (newValue: string) => {
setText(newValue);
};

return (
<Textarea
{...args}
value={text}
onValueChange={handleValueChange}
maxLength={args.maxLength}
placeholder={args.placeholder}
/>
);
```

<Canvas of={TextareaStories.OnValueChange} withSource="none" />

<br />
<br />
146 changes: 146 additions & 0 deletions src/components/Textarea/Textarea.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { useState } from 'react';

import { Meta, StoryObj } from '@storybook/react';

import { Textarea } from './Textarea';
import { TextareaProps } from './Textarea.type';

const meta: Meta<typeof Textarea> = {
title: 'Components/Textarea',
component: Textarea,
argTypes: {
width: {
control: 'text',
description: 'Textarea의 가로 길이',
},
height: {
control: 'text',
description: 'Textarea의 세로 길이',
},
placeholder: {
control: 'text',
description: 'Textarea의 placeholder 텍스트',
},
maxLength: {
control: 'number',
description: 'Textarea의 최대 입력 가능 글자 수',
},
helperText: {
control: 'text',
description: 'Textarea에 올바르게 입력할 수 있도록 돕는 텍스트',
},
disabled: {
control: 'boolean',
description: 'Textarea의 비활성화 여부',
},
error: {
control: 'boolean',
description: 'Textarea의 에러 여부',
},
},
parameters: {
layout: 'centered',
},
};

export default meta;
type Story = StoryObj<typeof Textarea>;

const ControlledComponent = (args: TextareaProps) => {
return <Textarea {...args} maxLength={args.maxLength} placeholder={args.placeholder} />;
};

const OnChangeComponent = (args: TextareaProps) => {
const [text, setText] = useState('');

const handleValueChange = (newValue: string) => {
setText(newValue);
};

return (
<Textarea
{...args}
value={text}
onValueChange={handleValueChange}
maxLength={args.maxLength}
placeholder={args.placeholder}
/>
);
};

export const Default: Story = {
render: (args) => <ControlledComponent {...args} />,
args: {
width: '100%',
height: 'auto',
placeholder: 'Enter text here...',
helperText: 'Text Inputting',
},
};

export const Placeholder: Story = {
render: (args) => <ControlledComponent {...args} />,
args: {
width: '343px',
height: '187px',
placeholder: 'Enter text here...',
},
};

export const HelperText: Story = {
render: (args) => <ControlledComponent {...args} />,
args: {
width: '343px',
height: '187px',
placeholder: 'Enter text here...',
helperText: 'helperText입니다.',
},
};

export const Disabled: Story = {
render: (args) => <ControlledComponent {...args} />,
args: {
width: '343px',
height: '187px',
placeholder: 'This field is disabled',
helperText: 'Text Inputting',
disabled: true,
},
};

export const Error: Story = {
render: (args) => <ControlledComponent {...args} />,
args: {
width: '343px',
height: '187px',
error: true,
placeholder: 'There is an error',
helperText: 'Text Inputting',
},
};

export const MaxLength: Story = {
render: (args) => <ControlledComponent {...args} />,
args: {
width: '343px',
height: '187px',
maxLength: 50,
placeholder: 'Max 50 characters',
helperText: '',
disabled: false,
error: false,
},
};

export const OnValueChange: Story = {
render: (args) => <OnChangeComponent {...args} />,
args: {
width: '343px',
height: '187px',
maxLength: 50,
placeholder: 'onValueChange...',
helperText: '',
disabled: false,
error: false,
},
};
86 changes: 86 additions & 0 deletions src/components/Textarea/Textarea.style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { styled } from 'styled-components';

import { TextareaProps } from './Textarea.type';

interface StyledTextareaProps {
$width?: TextareaProps['width'];
$height?: TextareaProps['height'];
$error?: TextareaProps['error'];
$isFocused?: boolean;
}

export const StyledContainer = styled.div`
display: flex;
flex-direction: column;
gap: 4px;
`;

export const StyledTextareaWrapper = styled.div<StyledTextareaProps>`
width: ${({ $width }) => $width ?? '100%'};
height: ${({ $height }) => $height ?? 'auto'};
padding: 16px;
background-color: ${({ theme }) => theme.semantic.color.bgBasicLight};
border: ${({ theme, $error }) =>
$error ? `1px solid ${theme.semantic.color.lineStatusNegative}` : 'none'};
border-radius: ${({ theme }) => theme.semantic.radius.s}px;
box-sizing: border-box;
border: ${({ theme, $error, $isFocused }) =>
$error
? `1px solid ${theme.semantic.color.lineStatusNegative}`
: $isFocused
? `1px solid ${theme.semantic.color.lineStatusPositive}`
: 'none'};
`;

export const StyledTextarea = styled.textarea<StyledTextareaProps>`
width: 100%;
height: 100%;
padding-right: 6px;
resize: none;
${({ theme }) => theme.typo.B3_Rg_14}
box-sizing: border-box;
border: none;
background-color: transparent;
color: ${({ theme }) => theme.semantic.color.textBasicPrimary};
caret-color: ${({ theme, $error }) =>
$error ? theme.semantic.color.lineStatusNegative : theme.semantic.color.lineStatusPositive};
&:focus {
outline: none;
}
&::placeholder {
color: ${({ theme }) => theme.semantic.color.textBasicTertiary};
}
&:disabled {
background-color: transparent;
cursor: not-allowed;
&::placeholder {
color: ${({ theme }) => theme.semantic.color.textBasicDisabled};
}
}
&::-webkit-scrollbar {
width: 2px;
border-radius: 2px;
}
&::-webkit-scrollbar-track {
background: ${({ theme }) => theme.semantic.color.lineBasicMedium};
}
&::-webkit-scrollbar-thumb {
background-color: ${({ theme }) => theme.semantic.color.lineBasicStrong};
}
`;

export const StyledHelperText = styled.div<StyledTextareaProps>`
margin-left: 4px;
${({ theme }) => theme.typo.C2_Rg_12}
color: ${({ theme, $error }) =>
$error ? theme.semantic.color.textStatusNegative : theme.semantic.color.textBasicTertiary};
`;
Loading

0 comments on commit 6c6e479

Please sign in to comment.