안녕하세요! 👏
지난 글에서는 기본 세팅을 완료했습니다.
이번에는 Atom 요소들을 만들어 보겠습니다.
최소한의 기본 지식이 있어야 이해하기 편하실 것 같습니다!
목차 📋
src 폴더 내부를 아래와 같이 세팅해주세요
ㄴsrc
│
└───assets
│
└───components
│ │
│ └───atoms
│ │
│ └───molecules
│ │
│ └───organisms
│ │
│ └───index.ts
│
└───pages
│
└───templates
│
└───untils
│
│ App.tsx
│ index.tsx
│ react-app-env.d.ts
│ reportWebVitals.ts
└── setupTests.ts
잠시 폴더 설명을 하고 가자면
assets 폴더: fonts, images, scss(css) 같은 파일 위치
components 폴더: 요소 파일 위치
pages 폴더: 화면에 보일 파일 위치
templates 폴더: 레이아웃 파일 위치
utils 폴더: hooks, dummy-data, 기타 파일 위치
버튼(Btn)
먼저 버튼부터 시작하겠습니다.
atoms 폴더 아래에 같이 만들어 줍니다.
ㄴatoms
│
└───Btn
│
│ index.tsx
│ index.stories.tsx
└── styles.ts
// index.tsx
import React from 'react';
import * as S from './style';
export interface BtnProps {
children?: React.ReactElement | string; // 버튼 내용 또는 엘리먼트
disabled?: boolean; //disabled 여부
btnType?: string; // button styling type (ex. priamry, disable)
btnOnClick?: () => void; // 클릭 이벤트
}
export interface LinkBtnProps extends BtnProps {
btnLink: string; // Link 경로
}
export function Btn({ children, btnOnClick, ...props }: BtnProps): React.ReactElement {
return (
<S.Btn onClick={btnOnClick} {...props}>
{children}
</S.Btn>
);
}
export function LinkBtn({ children, btnLink, btnOnClick, ...props }: LinkBtnProps): React.ReactElement {
return (
<S.LinkBtn onClick={btnOnClick} to={btnLink} {...props}>
{children}
</S.LinkBtn>
);
}
먼저 위 코드에서 봐야 할 점은 버튼을 2개로 나눠서 작성한 것입니다.
Link는 to가 필수 요소기 때문에 interface를 따로 분리하였습니다.
조건문을 이용하여 작업도 해보았는데 interface와 레이아웃에 크게 영향을 안 준다고 생각되면 굳이 나눌 필요는 없을 것 같습니다.
또한, Atom 요소들은 ...props가 꼭 들어가야 합니다.
원래는 disable 같은 속성을 사용하기 위해 넣었는데, 상위 Molecules, Organisms에서 컴포넌트를 불러 상속시켜 스타일을 적용시킬 때 이게 없으면 스타일이 안 먹기 때문에 필수라고 생각됩니다.
스타일 도구를 설치해줍니다.
npm i styled-components
npm i --save-dev @types/styled-components
스타일을 넣기 전에, 먼저 작동되는지 확인하고 하겠습니다.
S.LinkBtn은 주석 처리해주시고, S.Btn은 아래 코드처럼 변경 후, 실행해주시기 바랍니다.
// index.tsx
export function Btn({ children, btnOnClick, ...props }: BtnProps): React.ReactElement {
return (
// <S.Btn onClick={btnOnClick} {...props}>
// {children}
// </S.Btn>
<button>{children}</button>
);
}
// export function LinkBtn({ children, btnLink, btnOnClick, ...props }: LinkBtnProps): React.ReactElement {
// return (
// <S.LinkBtn onClick={btnOnClick} to={btnLink} {...props}>
// {children}
// </S.LinkBtn>
// );
// }
// index.stories.tsx
import React from 'react';
import { Story, Meta } from '@storybook/react';
import { Btn, BtnProps, LinkBtnProps } from './index';
export default {
title: 'Atoms/Btn'
} as Meta;
export const defaultBtn = (args: BtnProps) => <Btn {...args} btnOnClick={() => {}} />;
defaultBtn.args = {
children: '테스트',
disabled: false
};
index.stories.tsx 설명
title: SideBar 제목/소제목
const Template: 함수 호출
export const 소소제목: 사이드바 버튼 아래 보일 이름이다.
소소제목.args: props로 받는 것들을 여기에서 넣어서 테스트할 수 있다.
사진같이 나오시면 성공입니다!
앞으로 이런 식으로 컴포넌트를 만들고 Storybook으로 확인하며 작업하시면 됩니다.
이 프로젝트 스타일은 기본으로 reset.css스타일이 적용되어 있습니다.
reset 소스를 구하셔서 적용하시는 것을 추천드립니다. ex) Normalize css
또한, 해당 글은 크로스 브라우징을 고려하여 flex를 사용하지 않습니다.
styled-components는 SCSS를 이용합니다.
참고 글: SCSS 강의
// style.ts
import styled, { css } from 'styled-components';
import { Link } from 'react-router-dom';
interface BtnProps {
btnType?: string;
}
const BtnCSS = css<BtnProps>`
display: inline-block;
width: 100%;
margin: 0;
padding: 0;
border: 1px solid #cccccc;
font-size: 16px;
color: #000000;
text-align: center;
text-decoration: none;
background: #ffffff;
cursor: pointer;
&:disabled {
color: #ffffff;
background: #b9b9b9;
cursor: default;
}
${(props) => props.btnType === 'default' && css``}
${(props) =>
props.btnType === 'blue' &&
css`
border: 0;
color: #ffffff !important;
background: #1a5ae8;
`}
${(props) =>
props.btnType === 'blue_outline' &&
css`
border-color: #1a5ae8;
`}
${(props) =>
props.btnType === 'border_none' &&
css`
border: 0;
`}
`;
export const Btn = styled.button<BtnProps>`
${BtnCSS}
`;
export const LinkBtn = styled(Link)<BtnProps>`
${BtnCSS}
`;
스타일까지 작성되었으면 아까 주석처리를 풀고, 나머지 속성들도 넣어 보겠습니다.
// index.tsx
import React from 'react';
import * as S from './style';
export interface BtnProps {
children?: React.ReactElement | string; // 버튼 내용 또는 엘리먼트
disabled?: boolean; //disabled 여부
btnType?: string; // button styling type (ex. priamry, disable)
btnOnClick?: () => void; // 클릭 이벤트
}
export interface LinkBtnProps extends BtnProps {
btnLink: string; // Link 경로
}
export function Btn({ children, btnOnClick, ...props }: BtnProps): React.ReactElement {
return (
<S.Btn onClick={btnOnClick} {...props}>
{children}
</S.Btn>
);
}
export function LinkBtn({ children, btnLink, btnOnClick, ...props }: LinkBtnProps): React.ReactElement {
return (
<S.LinkBtn onClick={btnOnClick} to={btnLink} {...props}>
{children}
</S.LinkBtn>
);
}
// index.stories.tsx
import React from 'react';
import { Meta } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { Btn, LinkBtn, BtnProps, LinkBtnProps } from './index';
export default {
title: 'Atoms/Btn',
argTypes: {
btnType: {
control: {
type: 'select',
options: ['default', 'blue', 'blue_outline', 'border_none']
}
}
}
} as Meta;
export const defaultBtn = (args: BtnProps) => <Btn {...args} btnOnClick={action('Button is clicked!')} />;
defaultBtn.args = {
children: '테스트',
disabled: false
};
export const linktBtn = (args: LinkBtnProps) => <LinkBtn {...args} btnOnClick={action('Button is clicked!')} />;
linktBtn.args = {
children: '테스트',
disabled: false,
btnLink: ''
};
※ argTypes의 control을 이용하면 select를 사용하실 수 있습니다.
※ onClick 이벤트는 action('내용')을 이용하여 확인이 가능합니다.
문제가 발생하지 않는다면, 이제부터 위와 같은 형식으로 다른 Atom들도 만들어 보겠습니다.
Date(EventDate)
함수명을 Date로 하고 싶었지만 문제 될 수도 있어 Event를 붙였습니다.
다방에서 사용하는 날짜는 '21.1.6', '2021.1' 형태를 사용합니다. 그래서 따로 함수를 만들어줘야 합니다.
ㄴutils
│
│ DateFormat.ts
// DateFormat.ts
interface DateFormatProps {
dateAt: Date;
}
export function DateFormat1({ dateAt }: DateFormatProps): string {
if (dateAt) {
const Year = '' + dateAt.getFullYear().toString().substring(2);
const Month = '' + (dateAt.getMonth() + 1);
const Day = '' + dateAt.getDate();
const result = `${Year}.${Month}.${Day}`;
return result;
} else {
return '날짜가 없습니다.';
}
}
export function DateFormat2({ dateAt }: DateFormatProps): string {
if (dateAt) {
const Year = '' + dateAt.getFullYear().toString();
const Month = '' + (dateAt.getMonth() + 1);
const result = `${Year}.${Month}`;
return result;
} else {
return '날짜가 없습니다.';
}
}
함수에 어려운 것은 없으니 따로 설명은 안 하겠습니다.
ㄴatoms
│
└───EventDate
│
│ index.tsx
│ index.stories.tsx
index.tsx에서는 2개의 형태가 있지만 굳이 나눌 필요 없을 것 같아서 조건문을 이용하였습니다.
// index.tsx
import React from 'react';
import * as S from './style';
import { DateFormat1, DateFormat2 } from 'utils/DateFormat';
export interface EventDateProps {
eventDateType: string;
dateAt: Date;
}
export function EventDate({ eventDateType, dateAt }: EventDateProps): React.ReactElement {
return (
<S.Date>
{eventDateType === 'Date' && <>{dateAt && DateFormat1({ dateAt: dateAt })}</>}
{eventDateType === 'Month' && <>{dateAt && DateFormat2({ dateAt: dateAt })}</>}
</S.Date>
);
}
// index.stories.tsx
import React from 'react';
import { Meta } from '@storybook/react';
import { EventDate, EventDateProps } from './index';
export default {
title: 'Atoms/EventDate',
argTypes: {
dateAt: {
description: '날짜 데이터'
},
eventDateType: {
control: {
type: 'select',
options: ['Date', 'Month']
}
}
}
} as Meta;
const Now = new Date();
export const S_EventDate = (args: EventDateProps) => <EventDate {...args} />;
S_EventDate.args = {
eventDateType: 'Date',
dateAt: Now
};
import styled from 'styled-components';
export const Date = styled.span``;
EventDate에서는 이렇게 작성해주시면 됩니다.
나머지 Atom 요소들은 다음 글에 이어 작성하겠습니다.
'Frontend > Clone coding' 카테고리의 다른 글
React로 다방 클론코딩하기 - 4 (2) | 2021.02.16 |
---|---|
React로 다방 클론코딩하기 - 3 (0) | 2021.02.16 |
React로 다방 클론코딩하기 - 2.2 (0) | 2021.01.09 |
React로 다방 클론코딩하기 - 1 (0) | 2021.01.05 |