This repository has been archived by the owner on Jul 29, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: theme 컬러 추가 * feat: `Input` 컴포넌트 구현 * test: `Input` 스토리 작성 * refactor: red 색상 팔레트 추가 * refactor: htmlFor 속성 추가 및 outline css 수정
- Loading branch information
1 parent
4234cf1
commit 98217b9
Showing
3 changed files
with
230 additions
and
0 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,56 @@ | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
import Input, { InputVariant } from './Input'; | ||
import { StoryContainer, StoryItemContainer, StoryItemTitle } from 'styles/storybook'; | ||
import { Size } from 'constants/components/common'; | ||
|
||
const meta: Meta<typeof Input> = { | ||
title: 'common/Input', | ||
component: Input, | ||
args: { | ||
variant: 'outline', | ||
size: 'medium', | ||
labelText: '라벨', | ||
supportingText: '안내 문구는 여기에 나타납니다', | ||
isError: false, | ||
required: true, | ||
placeholder: 'placeholder', | ||
}, | ||
argTypes: { | ||
variant: { | ||
description: '미리 정의해놓은 인풋의 스타일입니다.', | ||
options: Object.values(InputVariant), | ||
control: { type: 'radio' }, | ||
}, | ||
size: { | ||
description: '크기에 따라 padding과 font-size가 바뀝니다.', | ||
options: Object.values(Size), | ||
control: { type: 'radio' }, | ||
}, | ||
labelText: { | ||
description: '라벨 텍스트입니다.', | ||
control: { type: 'text' }, | ||
}, | ||
supportingText: { | ||
description: '인풋 아래에 나타나는 안내 문구 텍스트입니다.', | ||
control: { type: 'text' }, | ||
}, | ||
placeholder: { | ||
description: '인풋 안에 나타나는 placeholder 텍스트입니다.', | ||
control: { type: 'text' }, | ||
}, | ||
isError: { | ||
description: 'Error 상태를 나타냅니다.', | ||
control: { type: 'boolean' }, | ||
}, | ||
required: { | ||
description: 'input의 필수 입력 여부를 나타냅니다.', | ||
control: { type: 'boolean' }, | ||
}, | ||
}, | ||
}; | ||
|
||
export default meta; | ||
|
||
type Story = StoryObj<typeof meta>; | ||
|
||
export const Playground: Story = {}; |
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,163 @@ | ||
import type { ComponentPropsWithRef, ForwardedRef, ReactElement } from 'react'; | ||
import { forwardRef, useId } from 'react'; | ||
import { RuleSet, css, styled } from 'styled-components'; | ||
import { Size } from 'types/components/common'; | ||
|
||
export const InputVariant = ['outline', 'filled', 'unstyled', 'underlined'] as const; | ||
export type InputVariant = (typeof InputVariant)[number]; | ||
|
||
type Props = { | ||
size: Size; | ||
labelText: string; | ||
supportingText: string; | ||
variant: InputVariant; | ||
isError: boolean; | ||
} & Omit<ComponentPropsWithRef<'input'>, 'size'>; | ||
|
||
const Input = ( | ||
{ | ||
size = 'large', | ||
labelText, | ||
supportingText, | ||
variant = 'outline', | ||
isError = false, | ||
...rest | ||
}: Partial<Props>, | ||
ref: ForwardedRef<HTMLInputElement>, | ||
) => { | ||
const inputId = useId(); | ||
return ( | ||
<S.InputContainer> | ||
{labelText && ( | ||
<S.Label htmlFor={inputId} $required={rest.required} $variant={variant}> | ||
{labelText} | ||
</S.Label> | ||
)} | ||
<S.Input | ||
id={inputId} | ||
ref={ref} | ||
$size={size} | ||
$variant={variant} | ||
$isError={isError} | ||
{...rest} | ||
/> | ||
{supportingText && <S.SupportingText $isError={isError}>{supportingText}</S.SupportingText>} | ||
</S.InputContainer> | ||
); | ||
}; | ||
|
||
export default forwardRef(Input); | ||
|
||
const genVariantStyle = ( | ||
variant: Required<Props>['variant'], | ||
isError: Required<Props>['isError'], | ||
): RuleSet<object> => { | ||
const styles: Record<typeof variant, ReturnType<typeof genVariantStyle>> = { | ||
outline: css` | ||
${({ theme }) => css` | ||
border: 1px solid ${isError ? theme.color.red6 : theme.color.gray6}; | ||
outline: 1px solid ${theme.color.gray1}; | ||
&:focus { | ||
border: 1px solid ${isError ? theme.color.red6 : theme.color.gray6}; | ||
outline: 1px solid ${isError ? theme.color.red6 : theme.color.gray8}; | ||
} | ||
`} | ||
`, | ||
filled: css` | ||
${({ theme }) => css` | ||
background-color: ${isError ? theme.color.red1 : theme.color.gray4}; | ||
border: 1px solid ${theme.color.gray1}; | ||
outline: 1px solid ${theme.color.gray1}; | ||
&:focus { | ||
background-color: ${theme.color.gray1}; | ||
outline: 1px solid ${isError ? theme.color.red6 : theme.color.gray8}; | ||
} | ||
`} | ||
`, | ||
unstyled: css` | ||
${({ theme }) => css` | ||
border: 1px solid ${theme.color.gray1}; | ||
outline: 1px solid ${isError ? theme.color.red6 : theme.color.gray1}; | ||
&:focus { | ||
outline: 1px solid ${isError ? theme.color.red6 : theme.color.gray8}; | ||
} | ||
`} | ||
`, | ||
underlined: css` | ||
${({ theme }) => css` | ||
border: 1px solid ${theme.color.gray1}; | ||
border-bottom: 1px solid ${isError ? theme.color.red6 : theme.color.gray6}; | ||
border-radius: 0; | ||
outline: 1px solid ${theme.color.gray1}; | ||
`} | ||
`, | ||
}; | ||
return styles[variant]; | ||
}; | ||
|
||
const genSizeStyle = (size: Required<Props>['size']): RuleSet<object> => { | ||
const styles: Record<typeof size, ReturnType<typeof genSizeStyle>> = { | ||
small: css` | ||
padding: 0.6rem 0.6rem; | ||
font-size: 1.3rem; | ||
`, | ||
medium: css` | ||
padding: 0.8rem 1rem; | ||
font-size: 1.4rem; | ||
`, | ||
large: css` | ||
padding: 1rem 1.2rem; | ||
font-size: 1.5rem; | ||
`, | ||
}; | ||
return styles[size]; | ||
}; | ||
|
||
const S = { | ||
InputContainer: styled.div` | ||
display: flex; | ||
flex-direction: column; | ||
gap: 0.6rem; | ||
font-size: 1.3rem; | ||
`, | ||
|
||
Label: styled.label<{ $required: boolean | undefined; $variant: InputVariant }>` | ||
font-weight: 500; | ||
${({ $required, theme }) => | ||
$required && | ||
css` | ||
&::after { | ||
content: '*'; | ||
margin-left: 0.2rem; | ||
color: ${theme.color.red6}; | ||
} | ||
`}; | ||
`, | ||
Input: styled.input<{ | ||
$size: Size; | ||
$variant: InputVariant; | ||
$isError: boolean; | ||
}>` | ||
border: none; | ||
border-radius: 4px; | ||
${({ $size }) => genSizeStyle($size)}; | ||
${({ $variant, $isError }) => genVariantStyle($variant, $isError)}; | ||
`, | ||
SupportingText: styled.p<{ $isError: boolean | undefined }>` | ||
color: ${({ $isError, theme }) => ($isError ? theme.color.red6 : theme.color.gray7)}; | ||
`, | ||
Underline: styled.div` | ||
position: absolute; | ||
bottom: 0; | ||
left: 0; | ||
height: 2px; | ||
width: 100%; | ||
background-color: ${({ theme }) => theme.color.primary}; | ||
transform: scaleX(0); | ||
transition: all 0.3s ease; | ||
`, | ||
}; |
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