Skip to content

Commit

Permalink
feat: Button 컴포넌트 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
bytrustu committed Mar 2, 2024
1 parent f107bbd commit e179ef5
Show file tree
Hide file tree
Showing 8 changed files with 288 additions and 2 deletions.
40 changes: 40 additions & 0 deletions docs/REQUIREMENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
### 필수 요구사항
- [x] 원시적인 형태의 Primitive UI 형태의 컴포넌트 작성
- [x] Box
- [x] HStack
- [x] VStack
- [x] TextField
- [x] Typography
- [x] Button
- [ ] Funnel 기반의 애플리케이션 설계
- [ ] Storybook 상호 작용 테스트
- [ ] Controlled & Uncontrolled Components를 명확하게 구분하거나 선택하여 구현

### 카드 추가
<(뒤로가기) 버튼 클릭 시, 카드 목록 페이지로 이동한다.

= [ ] 카드 번호를 입력 받을 수 있다.

- [ ] 카드 번호는 숫자만 입력가능하다.
- [ ] 카드 번호 4자리마다 -가 삽입된다.
- [ ] 카드 번호는 실시간으로 카드 UI에 반영된다.
- [ ] 카드 번호는 앞 8자리만 숫자로 보여지고, 나머지 숫자는 *로 보여진다.
- [ ] 만료일을 입력 받을 수 있다.

- [ ] MM / YY 로 placeholder를 적용한다.
- [ ] 월, 년 사이에 자동으로 /가 삽입된다.
- [ ] 만료일은 실시간으로 카드 UI에 반영된다.
- [ ] 월은 1이상 12이하 숫자여야 한다.
- [ ] 보안코드를 입력 받을 수 있다.

- [ ] 보안코드는 *으로 보여진다.
- [ ] 보안코드는 숫자만 입력가능하다.
- [ ] 카드 비밀번호의 앞 2자리를 입력 받을 수 있다.

- [ ] 카드 비밀번호는 각 폼마다 한자리 숫자만 입력가능하다.
- [ ] 카드 번호 입력 시, *으로 보여진다.
- [ ] 카드 소유자 이름을 입력 받을 수 있다.

- [ ] 이름은 30자리까지 입력할 수 있다.
- [ ] 이름 입력 폼 위에, 현재 입력 자릿수와 최대 입력 자릿수를 실시간으로 보여준다.
- [ ] 카드 추가 완료시 카드 등록 완료 페이지로 이동한다.
90 changes: 90 additions & 0 deletions src/shared/components/Button/Button.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { Meta, StoryObj } from '@storybook/react';
import { AppLayout } from '@/components';
import { Button } from '@/shared/components';
import { storybookControls } from '@/shared/styles';

const meta: Meta<typeof Button> = {
title: 'Primitive/Button',
component: Button,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
...storybookControls.argTypes,
type: {
options: ['button', 'submit', 'reset'],
},
variant: {
options: ['solid', 'outline', 'ghost'],
},
colorScheme: {
options: ['teal', 'gray'],
},
textAlign: {
options: ['left', 'center', 'right'],
},
},
args: {
type: 'button',
variant: 'solid',
colorScheme: 'teal',
children: 'Button',
},
decorators: [
(Story) => (
<AppLayout>
<Story />
</AppLayout>
),
],
};

export default meta;

type Story = StoryObj<typeof Button>;

export const Primary: Story = {
args: {
variant: 'solid',
},
};

export const WithVariantOutline: Story = {
args: {
variant: 'outline',
},
};

export const WithVariantGhost: Story = {
args: {
variant: 'ghost',
},
};

export const WithColorSchemeGray: Story = {
args: {
colorScheme: 'gray',
},
};

export const WithDisabled: Story = {
args: {
disabled: true,
},
};

export const WithCustomSize: Story = {
args: {
width: '200px',
height: '50px',
},
};

export const WithCustomTextAlign: Story = {
args: {
width: '200px',
height: '50px',
textAlign: 'right',
},
};
59 changes: 59 additions & 0 deletions src/shared/components/Button/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { HTMLProps, PropsWithChildren } from 'react';
import styled from '@emotion/styled';
import type { ButtonOptionalProps } from './Button.type';
import { getButtonColor } from './Button.util';
import { styleToken } from '@/shared/styles';
import type { AsProps, StyleProps } from '@/shared/types';

type ButtonProps = PropsWithChildren<StyleProps & AsProps & HTMLProps<HTMLButtonElement> & ButtonOptionalProps>;

export const Button = ({ children, type, variant, colorScheme, ...rest }: ButtonProps) => {
const buttonType = type || 'button';
const buttonVariant = variant || 'solid';
const buttonColorScheme = colorScheme || 'teal';
return (
<Root type={buttonType} variant={buttonVariant} colorScheme={buttonColorScheme} {...rest}>
{children}
</Root>
);
};

const Root = styled.button<ButtonProps & Required<ButtonOptionalProps>>`
width: ${({ width }) => width || 'auto'};
height: ${({ height }) => height || 'auto'};
text-align: ${({ textAlign }) => textAlign || 'center'};
padding: 10px 16px;
border-radius: 4px;
font-size: ${styleToken.fontSize.subtitle};
cursor: pointer;
transition:
background-color 0.2s,
color 0.2s,
border-color 0.2s;
${({ variant, colorScheme }) => {
const { backgroundColor, color, border } = getButtonColor(colorScheme, variant, 'default');
return `
background-color: ${backgroundColor};
color: ${color};
border: ${border};
`;
}}
&:hover {
${({ variant, colorScheme }) => {
const { backgroundColor, color, border } = getButtonColor(colorScheme, variant, 'hover');
return `
background-color: ${backgroundColor};
color: ${color};
border: ${border};
`;
}}
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
`;

Button.displayName = 'Button';
9 changes: 9 additions & 0 deletions src/shared/components/Button/Button.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export type ButtonType = 'button' | 'submit' | 'reset';
export type ButtonVariant = 'solid' | 'outline' | 'ghost';
export type ButtonVariantState = 'default' | 'hover';
export type ButtonColorScheme = 'teal' | 'gray';
export type ButtonOptionalProps = {
type?: ButtonType;
variant?: ButtonVariant;
colorScheme?: ButtonColorScheme;
};
86 changes: 86 additions & 0 deletions src/shared/components/Button/Button.util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import type { ButtonVariant, ButtonVariantState, ButtonColorScheme } from './Button.type';
import { styleToken } from '@/shared/styles';

export const buttonColorMap = {
teal: {
solid: {
default: {
backgroundColor: styleToken.color.teal200,
color: styleToken.color.white,
border: 'unset',
},
hover: {
backgroundColor: styleToken.color.teal100,
color: styleToken.color.white,
border: 'unset',
},
},
outline: {
default: {
backgroundColor: styleToken.color.white,
color: styleToken.color.teal200,
border: `1px solid ${styleToken.color.teal200}`,
},
hover: {
backgroundColor: styleToken.color.white,
color: styleToken.color.teal200,
border: `1px solid ${styleToken.color.teal200}`,
},
},
ghost: {
default: {
backgroundColor: styleToken.color.white,
color: styleToken.color.teal200,
border: 'unset',
},
hover: {
backgroundColor: styleToken.color.white,
color: styleToken.color.teal200,
border: 'unset',
},
},
},
gray: {
solid: {
default: {
backgroundColor: styleToken.color.gray400,
color: styleToken.color.white,
border: 'unset',
},
hover: {
backgroundColor: styleToken.color.gray300,
color: styleToken.color.white,
border: 'unset',
},
},
outline: {
default: {
backgroundColor: styleToken.color.white,
color: styleToken.color.gray400,
border: `1px solid ${styleToken.color.gray400}`,
},
hover: {
backgroundColor: styleToken.color.white,
color: styleToken.color.gray400,
border: `1px solid ${styleToken.color.gray400}`,
},
},
ghost: {
default: {
backgroundColor: styleToken.color.white,
color: styleToken.color.gray400,
border: 'unset',
},
hover: {
backgroundColor: styleToken.color.white,
color: styleToken.color.gray400,
border: 'unset',
},
},
},
} as const;

export const getButtonColor = (colorScheme: ButtonColorScheme, variant: ButtonVariant, state: ButtonVariantState) => {
const colorInfo = buttonColorMap[colorScheme][variant];
return colorInfo[state];
};
1 change: 1 addition & 0 deletions src/shared/components/Button/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './Button';
3 changes: 1 addition & 2 deletions src/shared/components/Label/Label.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Meta, StoryObj } from '@storybook/react';
import { AppLayout } from '@/components';
import { TextField, TypographyVariants, VStack } from '@/shared/components';
import { Label } from '@/shared/components/Label/Label.tsx';
import { TextField, TypographyVariants, VStack, Label } from '@/shared/components';
import { storybookControls } from '@/shared/styles';

const meta: Meta<typeof Label> = {
Expand Down
2 changes: 2 additions & 0 deletions src/shared/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ export * from './HStack';
export * from './VStack';
export * from './TextField';
export * from './Typography';
export * from './Label';
export * from './Button';

0 comments on commit e179ef5

Please sign in to comment.