-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 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
1 parent
eb10275
commit 6c6e479
Showing
7 changed files
with
457 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 /> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}; | ||
`; |
Oops, something went wrong.