00. 리액트 컴포넌트 스타일링 방법
- 컴포넌트를 스타일링 할 때 자주 사용되는 기술
1) Sass
2) CSS Module
3) styled-components
01. Sass
- CSS pre-processor
- 복잡한 작업을 쉽게 할 수 있도록 해주고, 코드의 재활용성 및 가독성을 높여줌
- 두 가지 확장자 (.scss/.sass) 를 지원
01-1 시작하기
1) 새로운 리액트 프로젝트 만들기
$ npx create-react-app styling-with-sass
2) 해당 프로젝트 디렉터리에 node-sass(Sass를 CSS로 변환해주는 역할) 라이브러리 설치
$ cd styling-with-sass
$ yarn add node-sass
01-2 Button 컴포넌트 만들기
1) src 디렉터리에 components 디렉터리 생성 후 그 안에 Button.js 와 Button.scss 파일 만들기
//components/Button.js
import React from 'react';
import './Button.scss';
const Button = ({ children }) => {
return <button className="Button">{children}</button>;
}
export default Button;
//components/Button.scss
$blue: #228be6; // 주석 선언
.Button {
display: inline-flex;
color: white;
font-weight: bold;
outline: none;
border-radius: 4px;
border: none;
cursor: pointer;
height: 2.25rem;
padding-left: 1rem;
padding-right: 1rem;
font-size: 1rem;
background: $blue; // 주석 사용
&:hover {
background: lighten($blue, 10%); // 색상 10% 밝게
}
&:active {
background: darken($blue, 10%); // 색상 10% 어둡게
}
}
2) App 컴포넌트에서 사용
//App.js
import React from 'react';
import './App.scss';
import Button from './components/Button';
const App = () => {
return (
<div className="App">
<div className="buttons">
<Button>BUTTON</Button>
</div>
</div>
);
}
export default App;
3) App.css --> App.scss 로 파일 이름 수정, 내용 변경
.App {
width: 512px;
margin: 0 auto;
margin-top: 4rem;
border: 1px solid black;
padding: 1rem;
}
01-3 버튼 사이즈 조정하기
- large, medium, small 을 설정하도록 구현
1) defaultProps를 통하여 size의 기본값 medium으로 설정 후 button 의 className에 넣기
//Button.js
import React from 'react';
import './Button.scss';
const Button = ({ children, size }) => {
return <button className={['Button', size].join(' ')}>{children}</button>;
}
Button.defaultProps = {
size: 'medium' //defaultProps를 통하여 size의 기본값 medium으로 설정
};
export default Button;
※ classnames 라이브러리 : CSS 클래스를 조건부로 넣어주고 싶을때 사용
--> 문자열, 배열, 객체 등을 전달하여 문자열 쉽게 조합 가능
classNames('foo', 'bar'); // => 'foo bar'
classNames('foo', { bar: true }); // => 'foo bar'
classNames({ 'foo-bar': true }); // => 'foo-bar'
classNames({ 'foo-bar': false }); // => ''
classNames({ foo: true }, { bar: true }); // => 'foo bar'
classNames({ foo: true, bar: true }); // => 'foo bar'
classNames(['foo', 'bar']); // => 'foo bar'
// 동시에 여러개의 타입으로 받아올 수 도 있습니다.
classNames('foo', { bar: true, duck: false }, 'baz', { quux: true }); // => 'foo bar baz quux'
// false, null, 0, undefined 는 무시됩니다.
classNames(null, false, 'bar', undefined, 0, 1, { baz: null }, ''); // => 'bar 1'
프로젝트에 설치 후 사용
$ yarn add classnames
2) props 값이 button 태그의 className 으로 전달
//Button.js
import React from 'react';
import classNames from 'classnames';
import './Button.scss';
const Button = ({ children, size }) => {
return <button className={classNames('Button', size)}>{children}</button>;
}
Button.defaultProps = {
size: 'medium'
};
export default Button;
3) Button.scss에서 다른 크기 지정, 여백 추가
$blue: #228be6;
.Button {
display: inline-flex;
color: white;
font-weight: bold;
outline: none;
border-radius: 4px;
border: none;
cursor: pointer;
// 사이즈 관리
&.large {
height: 3rem;
padding-left: 1rem;
padding-right: 1rem;
font-size: 1.25rem;
}
&.medium {
height: 2.25rem;
padding-left: 1rem;
padding-right: 1rem;
font-size: 1rem;
}
&.small {
height: 1.75rem;
font-size: 0.875rem;
padding-left: 1rem;
padding-right: 1rem;
}
background: $blue;
&:hover {
background: lighten($blue, 10%);
}
&:active {
background: darken($blue, 10%);
}
& + & { /*Button + Button --> 만약 함께 있다면 우측에 있는 버튼에 여백 설정*/
margin-left: 1rem;
}
}
4) App.js 에서 버튼 2개 더 렌더링, size 값 설정
import React from 'react';
import './App.scss';
import Button from './components/Button';
const App = () => {
return (
<div className="App">
<div className="buttons">
<Button size="large">BUTTON</Button>
<Button>BUTTON</Button>
<Button size="small">BUTTON</Button>
</div>
</div>
);
}
export default App;
01-4 버튼 색상 설정하기
- blue, gray, pink 등 색을 설정 할 수 있도록 구현
1) Button에서 color라는 props 받아오기, 기본값 blue로 설정, color값 className에 포함
//components/Button.js
import React from 'react';
import classNames from 'classnames';
import './Button.scss';
const Button = ({ children, size, color }) => {
return (
<button className={classNames('Button', size, color)}>{children}</button>
);
}
Button.defaultProps = {
size: 'medium',
color: 'blue'
};
export default Button;
2) Button.scss 파일 수정
- mixin이라는 기능을 통해 반복되는 코드 재사용 가능 (함수와 같은 역할)
$blue: #228be6;
$gray: #495057;
$pink: #f06595;
@mixin button-color($color) {
background: $color;
&:hover {
background: lighten($color, 10%);
}
&:active {
background: darken($color, 10%);
}
}
.Button {
display: inline-flex;
color: white;
font-weight: bold;
outline: none;
border-radius: 4px;
border: none;
cursor: pointer;
// 사이즈 관리
&.large {
height: 3rem;
padding-left: 1rem;
padding-right: 1rem;
font-size: 1.25rem;
}
&.medium {
height: 2.25rem;
padding-left: 1rem;
padding-right: 1rem;
font-size: 1rem;
}
&.small {
height: 1.75rem;
font-size: 0.875rem;
padding-left: 1rem;
padding-right: 1rem;
}
// 색상 관리
&.blue {
@include button-color($blue);
}
&.gray {
@include button-color($gray);
}
&.pink {
@include button-color($pink);
}
& + & {
margin-left: 1rem;
}
}
3) App 컴포넌트에서 버튼 렌더링
//App.js
import React from 'react';
import './App.scss';
import Button from './components/Button';
const App = () => {
return (
<div className="App">
<div className="buttons">
<Button size="large">BUTTON</Button>
<Button>BUTTON</Button>
<Button size="small">BUTTON</Button>
</div>
<div className="buttons">
<Button size="large" color="gray">
BUTTON
</Button>
<Button color="gray">BUTTON</Button>
<Button size="small" color="gray">
BUTTON
</Button>
</div>
<div className="buttons">
<Button size="large" color="pink">
BUTTON
</Button>
<Button color="pink">BUTTON</Button>
<Button size="small" color="pink">
BUTTON
</Button>
</div>
</div>
);
}
export default App;
4) App.scss 수정 (margin-top을 사용해 위아래 버튼 간격 두기)
.App {
width: 512px;
margin: 0 auto;
margin-top: 4rem;
border: 1px solid black;
padding: 1rem;
.buttons + .buttons {
margin-top: 1rem;
}
}
01-5 outline 옵션 만들기
- 버튼에서 테두리만 보여지도록 설정
1) Button.js를 수정
- outline 값을 props로 받아와 객체 안에 넣고 classNames()에 포함
--> outline값이 true일 때에만 button에 outline CSS클래스가 적용
//Button.js
import React from 'react';
import classNames from 'classnames';
import './Button.scss';
const Button = ({ children, size, color, outline }) => {
return (
<button className={classNames('Button', size, color, { outline })}>
{children}
</button>
);
}
Button.defaultProps = {
size: 'medium',
color: 'blue'
};
export default Button;
2) 만약 outline CSS 클래스가 있다면, 테두리만 보여지도록 Button.scss 수정
//Button.scss
$blue: #228be6;
$gray: #495057;
$pink: #f06595;
@mixin button-color($color) {
background: $color;
&:hover {
background: lighten($color, 10%);
}
&:active {
background: darken($color, 10%);
}
&.outline {
color: $color;
background: none;
border: 1px solid $color;
&:hover {
background: $color;
color: white;
}
}
}
.Button {
display: inline-flex;
color: white;
font-weight: bold;
outline: none;
border-radius: 4px;
border: none;
cursor: pointer;
// 사이즈 관리
&.large {
height: 3rem;
padding-left: 1rem;
padding-right: 1rem;
font-size: 1.25rem;
}
&.medium {
height: 2.25rem;
padding-left: 1rem;
padding-right: 1rem;
font-size: 1rem;
}
&.small {
height: 1.75rem;
font-size: 0.875rem;
padding-left: 1rem;
padding-right: 1rem;
}
// 색상 관리
&.blue {
@include button-color($blue);
}
&.gray {
@include button-color($gray);
}
&.pink {
@include button-color($pink);
}
& + & {
margin-left: 1rem;
}
}
3) App.js 사용
import React from 'react';
import './App.scss';
import Button from './components/Button';
const App = () => {
return (
<div className="App">
<div className="buttons">
<Button size="large">BUTTON</Button>
<Button>BUTTON</Button>
<Button size="small">BUTTON</Button>
</div>
<div className="buttons">
<Button size="large" color="gray">
BUTTON
</Button>
<Button color="gray">BUTTON</Button>
<Button size="small" color="gray">
BUTTON
</Button>
</div>
<div className="buttons">
<Button size="large" color="pink">
BUTTON
</Button>
<Button color="pink">BUTTON</Button>
<Button size="small" color="pink">
BUTTON
</Button>
</div>
<div className="buttons">
<Button size="large" color="blue" outline>
BUTTON
</Button>
<Button color="gray" outline>
BUTTON
</Button>
<Button size="small" color="pink" outline>
BUTTON
</Button>
</div>
</div>
);
}
export default App;
01-6 전체 너비 차지하는 옵션
- fullWidth라는 옵션이 있으면 버튼이 전체 너비를 차지하도록 구현
1) Button.js 수정
//Button.js
import React from 'react';
import classNames from 'classnames';
import './Button.scss';
const Button = ({ children, size, color, outline, fullWidth }) => {
return (
<button
className={classNames('Button', size, color, { outline, fullWidth })}
>
{children}
</button>
);
}
Button.defaultProps = {
size: 'medium',
color: 'blue'
};
export default Button;
2) 스타일 수정
//Button.scss
$blue: #228be6;
$gray: #495057;
$pink: #f06595;
@mixin button-color($color) {
background: $color;
&:hover {
background: lighten($color, 10%);
}
&:active {
background: darken($color, 10%);
}
&.outline {
color: $color;
background: none;
border: 1px solid $color;
&:hover {
background: $color;
color: white;
}
}
}
.Button {
display: inline-flex;
color: white;
font-weight: bold;
outline: none;
border-radius: 4px;
border: none;
cursor: pointer;
// 사이즈 관리
&.large {
height: 3rem;
padding-left: 1rem;
padding-right: 1rem;
font-size: 1.25rem;
}
&.medium {
height: 2.25rem;
padding-left: 1rem;
padding-right: 1rem;
font-size: 1rem;
}
&.small {
height: 1.75rem;
font-size: 0.875rem;
padding-left: 1rem;
padding-right: 1rem;
}
// 색상 관리
&.blue {
@include button-color($blue);
}
&.gray {
@include button-color($gray);
}
&.pink {
@include button-color($pink);
}
& + & {
margin-left: 1rem;
}
&.fullWidth {
width: 100%;
justify-content: center;
& + & {
margin-left: 0;
margin-top: 1rem;
}
}
}
3) App에서 사용
App.js
import React from 'react';
import './App.scss';
import Button from './components/Button';
const App = () => {
return (
<div className="App">
<div className="buttons">
<Button size="large">BUTTON</Button>
<Button>BUTTON</Button>
<Button size="small">BUTTON</Button>
</div>
<div className="buttons">
<Button size="large" color="gray">
BUTTON
</Button>
<Button color="gray">BUTTON</Button>
<Button size="small" color="gray">
BUTTON
</Button>
</div>
<div className="buttons">
<Button size="large" color="pink">
BUTTON
</Button>
<Button color="pink">BUTTON</Button>
<Button size="small" color="pink">
BUTTON
</Button>
</div>
<div className="buttons">
<Button size="large" color="blue" outline>
BUTTON
</Button>
<Button color="gray" outline>
BUTTON
</Button>
<Button size="small" color="pink" outline>
BUTTON
</Button>
</div>
<div className="buttons">
<Button size="large" fullWidth>
BUTTON
</Button>
<Button size="large" fullWidth color="gray">
BUTTON
</Button>
<Button size="large" fullWidth color="pink">
BUTTON
</Button>
</div>
</div>
);
}
export default App;
01-7 ...rest props 전달하기
- 만들어놓은 버튼 컴포넌트에 onClick 설정
1) Button.js 파일 수정(spread와 rest를 사용해 이벤트 추가 간편화하기)
import React from 'react';
import classNames from 'classnames';
import './Button.scss';
const Button = ({ children, size, color, outline, fullWidth, ...rest }) => {
return (
<button
className={classNames('Button', size, color, { outline, fullWidth })}
{...rest}
>
{children}
</button>
);
}
Button.defaultProps = {
size: 'medium',
color: 'blue'
};
export default Button;
2) App.js 에서 가장 첫번째 버튼에 onClick 설정
import React from 'react';
import './App.scss';
import Button from './components/Button';
const App = () => {
return (
<div className="App">
<div className="buttons">
<Button size="large" onClick={() => console.log('클릭됐다!')}>
BUTTON
</Button>
<Button>BUTTON</Button>
<Button size="small">BUTTON</Button>
</div>
<div className="buttons">
<Button size="large" color="gray">
BUTTON
</Button>
<Button color="gray">BUTTON</Button>
<Button size="small" color="gray">
BUTTON
</Button>
</div>
<div className="buttons">
<Button size="large" color="pink">
BUTTON
</Button>
<Button color="pink">BUTTON</Button>
<Button size="small" color="pink">
BUTTON
</Button>
</div>
<div className="buttons">
<Button size="large" color="blue" outline>
BUTTON
</Button>
<Button color="gray" outline>
BUTTON
</Button>
<Button size="small" color="pink" outline>
BUTTON
</Button>
</div>
<div className="buttons">
<Button size="large" fullWidth>
BUTTON
</Button>
<Button size="large" color="gray" fullWidth>
BUTTON
</Button>
<Button size="large" color="pink" fullWidth>
BUTTON
</Button>
</div>
</div>
);
}
export default App;
※컴포넌트가 어떤 props 를 받을 지 확실치는 않지만 그대로 다른 컴포넌트 또는 HTML 태그에 전달을 해주어야 하는 상황에는 이렇게 ...rest 문법 활용
01-8 Sass 마무리
- Sass를 사용하면 스타일 파일에 다양한 유용한 문법을 사용해서 컴포넌트 스타일링 생산성을 높여줌
- 프로젝트를 개발할 때 매번 버튼을 만들때마다 새로운 컴포넌트를 만들게 아니라 위와 같이 다양한 옵션을 넣을 수 있게 해서 그때 그때 커스터마이징 해서 사용하는 것이 효율적임
02. CSS Module
- 클래스 이름에 대하여 고유한 이름들이 만들어짐으로써 CSS 클래스가 중첩되는 것을 방지
- 파일 확장자 : .module.css
02-1 CSS Module 알아보기
1) 예시 코드(Box.module.css)
-리액트 컴포넌트 파일에서 해당 CSS 파일을 불러올 때 CSS 파일에 선언한 클래스 이름들이 모두 고유해짐
/*Box.module.css*/
.Box {
background: black;
color: white;
padding: 2rem;
}
//Box.js
import React from "react";
import styles from "./Box.module.css";
const Box = () => {
return <div className={styles.Box}>{styles.Box}</div>;
}
export default Box;
className을 설정할 때 import로 불러온 styles 객체 안에 있는 값을 참조 (ex. style.Box)
2) CSS Module 사용하면 유용한 상황
- 레거시 프로젝트에 리액트를 도입할 때 (기존 프로젝트에 있던 CSS 클래스와 이름이 중복되어도 스타일이 꼬이지 않음)
- CSS 클래스를 중복되지 않게 작성하기 위하여 CSS 클래스 네이밍 규칙을 만들기 귀찮을 때
02-2 커스텀 체크박스 컴포넌트 만들기
1) 새로운 프로젝트 생성
$ npx create-react-app styling-with-css-module
2) src 디렉터리 > components 디렉터리 > CheckBox.js 파일 생성
- CheckBox 컴포넌트의 틀 준비
//CheckBox.js
import React from 'react';
const CheckBox = ({ children, checked, ...rest }) => {
return (
<div>
<label>
<input type="checkbox" checked={checked} {...rest} />
<div>{checked ? '체크됨' : '체크 안됨'}</div>
</label>
<span>{children}</span>
</div>
);
}
export default CheckBox;
3) App 컴포넌트에서 렌더링
//App.js
import React, { useState } from 'react';
import CheckBox from './components/CheckBox';
const App = () => {
const [check, setCheck] = useState(false);
const onChange = e => {
setCheck(e.target.checked);
};
return (
<div>
<CheckBox onChange={onChange} checked={check}>
다음 약관에 모두 동의
</CheckBox>
<p>
<b>check: </b>
{check ? 'true' : 'false'}
</p>
</div>
);
}
export default App;
4) react-icons 라이브러리 설치 (스타일링 시 필요)
$ yarn add react-icons
5) CheckBox 컴포넌트 수정
- 텍스트 대신 아이콘이 나타나도록
//CheckBox.js
import React from 'react';
import { MdCheckBox, MdCheckBoxOutlineBlank } from 'react-icons/md';
const CheckBox = ({ children, checked, ...rest }) => {
return (
<div>
<label>
<input type="checkbox" checked={checked} {...rest} />
<div>{checked ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}</div>
</label>
<span>{children}</span>
</div>
);
}
export default CheckBox;
6) components 디렉터리에 CheckBox.module.css 파일 생성
- CSS Module을 작성했으므로 .checkbox 와 같이 흔한 이름을 사용해도 무방 (중복 안됨)
.checkbox {
display: flex;
align-items: center;
}
.checkbox label {
cursor: pointer;
}
/* 실제 input 을 숨기기 위한 코드 */
.checkbox input {
width: 0;
height: 0;
position: absolute;
opacity: 0;
}
.checkbox span {
font-size: 1.125rem;
font-weight: bold;
}
.icon {
display: flex;
align-items: center;
/* 아이콘의 크기는 폰트 사이즈로 조정 가능 */
font-size: 2rem;
margin-right: 0.25rem;
color: #adb5bd;
}
.checked {
color: #339af0;
}
7) CheckBox.js에서 사용
import React from 'react';
import { MdCheckBox, MdCheckBoxOutlineBlank } from 'react-icons/md';
import styles from './CheckBox.module.css';
const CheckBox = ({ children, checked, ...rest }) => {
return (
<div className={styles.checkbox}>
<label>
<input type="checkbox" checked={checked} {...rest} />
<div className={styles.icon}>
{checked ? (
<MdCheckBox className={styles.checked} />
) : (
<MdCheckBoxOutlineBlank />
)}
</div>
</label>
<span>{children}</span>
</div>
);
}
export default CheckBox;
※ 클래스 이름에 - 가 들어가 있거나, 여래개가 있을 시 값을 조회하기 번거로움
--> classnames 라이브러리에 bind 기능 사용
1) classnames 라이브러리 설치
$ yarn add classnames
2) CheckBox.js 수정
- bind기능을 사용하면 cx('클래스이름')과 같은 형식으로 편하게 사용 가능
import React from 'react';
import { MdCheckBox, MdCheckBoxOutlineBlank } from 'react-icons/md';
import styles from './CheckBox.module.css';
import classNames from 'classnames/bind';
const cx = classNames.bind(styles);
const CheckBox = ({ children, checked, ...rest }) => {
return (
<div className={cx('checkbox')}>
<label>
<input type="checkbox" checked={checked} {...rest} />
<div className={cx('icon')}>
{checked ? (
<MdCheckBox className={cx('checked')} />
) : (
<MdCheckBoxOutlineBlank />
)}
</div>
</label>
<span>{children}</span>
</div>
);
}
export default CheckBox;
02-3 CSS Module 마무리
- 레거시 프로젝트에 리액트를 도입할 때 편리
- 클래스 이름 짓는 규칙을 정하기 힘들 때 편리
03. styled-components
- CSS in JS (JS안에 CSS를 작성하는 것을 의미)
03-1 Tagged Template Literal
- Template Literal : 문자열 조합을 쉽게 하도록 하는 ES6 문법 (객체/함수 도 가능)
const name = 'react';
const message = `hello ${name}`;
console.log(message);
// "hello react"
- Tagged Template Literal 문법 : 내부에 넣은 자바스크립트 값을 조회하고 싶을 때 사용
const red = '빨간색';
const blue = '파란색';
const favoriteColors = (texts, ...values) => {
console.log(texts);
console.log(values);
}
favoriteColors`제가 좋아하는 색은 ${red}과 ${blue}입니다.`
- ${} 을 통해 함수를 넣어줬다면, 해당 함수 사용 가능
const sample = (texts, ...fns) => {
const mockProps = {
title: '안녕하세요',
body: '내용은 내용내용 입니다.'
};
return texts.reduce((result, text, i) => `${result}${text}${fns[i] ? fns[i](mockProps) : ''}`, '');
}
sample`
제목: ${props => props.title}
내용: ${props => props.body}
`
/*
"
제목: 안녕하세요
내용: 내용은 내용내용 입니다.
"
*/
03-2 styled-components 사용하기
1) 새로운 프로젝트 생성, styled-components 설치
$ npx create-react-app styling-with-styled-components
$ cd styling-with-styled-components
$ yarn add styled-components
2) App컴포넌트에서 styled-components로 스타일링하기
- 스타일을 입력함과 동시에 해당 스타일을 가진 컴포넌트 생성 가능
- div를 스타일링 하고 싶다면 styled.div 이런식으로 사용
//App.js
import React from 'react';
import styled from 'styled-components';
const Circle = styled.div`
width: 5rem;
height: 5rem;
background: black;
border-radius: 50%;
`;
const App = () => {
return <Circle />;
}
export default App;
3) Circle 컴포넌트에 color, huge라는 props 설정
import React from 'react';
import styled, { css } from 'styled-components';
const Circle = styled.div`
width: 5rem;
height: 5rem;
background: ${props => props.color || 'black'};
border-radius: 50%;
${props =>
props.huge &&
css`
width: 10rem;
height: 10rem;
`}
`;
const App = () => {
return <Circle color="red" huge />;
}
export default App;
03-3 Button 만들기
1) src안에 components 디렉터리 생성 후 그 안에 Button.js 파일 생성
//Buttton.js
import React from 'react';
import styled from 'styled-components';
const StyledButton = styled.button`
/* 공통 스타일 */
display: inline-flex;
outline: none;
border: none;
border-radius: 4px;
color: white;
font-weight: bold;
cursor: pointer;
padding-left: 1rem;
padding-right: 1rem;
/* 크기 */
height: 2.25rem;
font-size: 1rem;
/* 색상 */
background: #228be6;
&:hover {
background: #339af0;
}
&:active {
background: #1c7ed6;
}
/* 기타 */
& + & {
margin-left: 1rem;
}
`;
const Button = ({ children, ...rest }) => {
return <StyledButton {...rest}>{children}</StyledButton>;
}
export default Button;
2) App.js 에서 Button 사용
//App.js
import React from 'react';
import styled from 'styled-components';
import Button from './components/Button';
const AppBlock = styled.div`
width: 512px;
margin: 0 auto;
margin-top: 4rem;
border: 1px solid black;
padding: 1rem;
`;
const App = () => {
return (
<AppBlock>
<Button>BUTTON</Button>
</AppBlock>
);
}
export default App;
03-4 polished의 스타일 관련 유틸 함수 사용하기
- Sass에서의 lighten(), darken()과 같은 유틸 함수를 사용하여 색상 변화 가능
- CSS in JS 에서도 polished 라는 라이브러리를 통해 유틸 함수 사용 가능
1) polished 라이브러리 설치
$ yarn add polished
2) 기존의 색상 부분 polished의 유틸 함수로 대체
- 버튼에 커서를 올렸을 때 색상 변경하도록 코드 작성
//Button.js
import React from 'react';
import styled from 'styled-components';
import { darken, lighten } from 'polished';
const StyledButton = styled.button`
/* 공통 스타일 */
display: inline-flex;
outline: none;
border: none;
border-radius: 4px;
color: white;
font-weight: bold;
cursor: pointer;
padding-left: 1rem;
padding-right: 1rem;
/* 크기 */
height: 2.25rem;
font-size: 1rem;
/* 색상 */
background: #228be6;
&:hover {
background: ${lighten(0.1, '#228be6')};
}
&:active {
background: ${darken(0.1, '#228be6')};
}
/* 기타 */
& + & {
margin-left: 1rem;
}
`;
const Button = ({ children, ...rest }) => {
return <StyledButton {...rest}>{children}</StyledButton>;
}
export default Button;
3) ThemeProvider 기능을 사용하여 styled-components로 만드는 모든 컴포넌트 조회, 사용할 수 있는 전역적인 값 설정
//App.js
import React from 'react';
import styled, { ThemeProvider } from 'styled-components';
import Button from './components/Button';
const AppBlock = styled.div`
width: 512px;
margin: 0 auto;
margin-top: 4rem;
border: 1px solid black;
padding: 1rem;
`;
const App = () => {
return (
<ThemeProvider
theme={{
palette: {
blue: '#228be6',
gray: '#495057',
pink: '#f06595'
}
}}
>
<AppBlock>
<Button>BUTTON</Button>
</AppBlock>
</ThemeProvider>
);
}
export default App;
△Theme 설정 시 ThemeProvider 내부에 렌더링 된 styled-components로 만든 컴포넌트에서 palette 조회하여 사용 가능
4) Button 컴포넌트에서 위에서 선언한 palette.blue 값 조회
- seleceted 값이 무조건 blue값을 가르키도록 설정
//Button.js
import React from 'react';
import styled, { css } from 'styled-components';
import { darken, lighten } from 'polished';
const StyledButton = styled.button`
/* 공통 스타일 */
display: inline-flex;
outline: none;
border: none;
border-radius: 4px;
color: white;
font-weight: bold;
cursor: pointer;
padding-left: 1rem;
padding-right: 1rem;
/* 크기 */
height: 2.25rem;
font-size: 1rem;
/* 색상 */
${props => {
const selected = props.theme.palette.blue;
return css`
background: ${selected};
&:hover {
background: ${lighten(0.1, selected)};
}
&:active {
background: ${darken(0.1, selected)};
}
`;
}}
/* 기타 */
& + & {
margin-left: 1rem;
}
`;
const Button = ({ children, ...rest }) => {
return <StyledButton {...rest}>{children}</StyledButton>;
}
export default Button;
△ ThemeProvider로 설정한 값은 props.theme 으로 조회 가능
5) Button 컴포넌트가 color라는 props를 통하여 받아오게 될 색상을 사용하도록 수정
- 기본 색상이 blue가 되도록 설정
//Button.js
import React from 'react';
import styled, { css } from 'styled-components';
import { darken, lighten } from 'polished';
const StyledButton = styled.button`
/* 공통 스타일 */
display: inline-flex;
outline: none;
border: none;
border-radius: 4px;
color: white;
font-weight: bold;
cursor: pointer;
padding-left: 1rem;
padding-right: 1rem;
/* 크기 */
height: 2.25rem;
font-size: 1rem;
/* 색상 */
${props => {
const selected = props.theme.palette[props.color];
return css`
background: ${selected};
&:hover {
background: ${lighten(0.1, selected)};
}
&:active {
background: ${darken(0.1, selected)};
}
`;
}}
/* 기타 */
& + & {
margin-left: 1rem;
}
`;
const Button = ({ children, ...rest }) => {
return <StyledButton {...rest}>{children}</StyledButton>;
}
Button.defaultProps = {
color: 'blue'
};
export default Button;
6) App컴포넌트에서 회색, 핑크색 버튼 렌더링
//App.js
import React from 'react';
import styled, { ThemeProvider } from 'styled-components';
import Button from './components/Button';
const AppBlock = styled.div`
width: 512px;
margin: 0 auto;
margin-top: 4rem;
border: 1px solid black;
padding: 1rem;
`;
const App = () => {
return (
<ThemeProvider
theme={{
palette: {
blue: '#228be6',
gray: '#495057',
pink: '#f06595'
}
}}
>
<AppBlock>
<Button>BUTTON</Button>
<Button color="gray">BUTTON</Button>
<Button color="pink">BUTTON</Button>
</AppBlock>
</ThemeProvider>
);
}
export default App;
7) Button 컴포넌트 코드 리팩토링
//Button.js
import React from 'react';
import styled, { css } from 'styled-components';
import { darken, lighten } from 'polished';
const StyledButton = styled.button`
/* 공통 스타일 */
display: inline-flex;
outline: none;
border: none;
border-radius: 4px;
color: white;
font-weight: bold;
cursor: pointer;
padding-left: 1rem;
padding-right: 1rem;
/* 크기 */
height: 2.25rem;
font-size: 1rem;
/* 색상 */
${({ theme, color }) => {
const selected = theme.palette[color];
return css`
background: ${selected};
&:hover {
background: ${lighten(0.1, selected)};
}
&:active {
background: ${darken(0.1, selected)};
}
`;
}}
/* 기타 */
& + & {
margin-left: 1rem;
}
`;
const Button = ({ children, color, ...rest }) => {
return <StyledButton color={color} {...rest}>{children}</StyledButton>;
}
Button.defaultProps = {
color: 'blue'
};
export default Button;
8) size props를 설정하여 버튼 크기 설정
//Button.js
import React from 'react';
import styled, { css } from 'styled-components';
import { darken, lighten } from 'polished';
const colorStyles = css`
${({ theme, color }) => {
const selected = theme.palette[color];
return css`
background: ${selected};
&:hover {
background: ${lighten(0.1, selected)};
}
&:active {
background: ${darken(0.1, selected)};
}
`;
}}
`;
const sizeStyles = css`
${props =>
props.size === 'large' &&
css`
height: 3rem;
font-size: 1.25rem;
`}
${props =>
props.size === 'medium' &&
css`
height: 2.25rem;
font-size: 1rem;
`}
${props =>
props.size === 'small' &&
css`
height: 1.75rem;
font-size: 0.875rem;
`}
`;
const StyledButton = styled.button`
/* 공통 스타일 */
display: inline-flex;
outline: none;
border: none;
border-radius: 4px;
color: white;
font-weight: bold;
cursor: pointer;
padding-left: 1rem;
padding-right: 1rem;
/* 크기 */
${sizeStyles}
/* 색상 */
${colorStyles}
/* 기타 */
& + & {
margin-left: 1rem;
}
`;
const Button = ({ children, color, size, ...rest }) => {
return (
<StyledButton color={color} size={size} {...rest}>
{children}
</StyledButton>
);
}
Button.defaultProps = {
color: 'blue'
};
export default Button;
9) App 컴포넌트에서 버튼 렌더링, 중복되는 코드 리팩토링
- 여백 1rem으로 설정
import React from 'react';
import styled, { css } from 'styled-components';
import { darken, lighten } from 'polished';
const colorStyles = css`
${({ theme, color }) => {
const selected = theme.palette[color];
return css`
background: ${selected};
&:hover {
background: ${lighten(0.1, selected)};
}
&:active {
background: ${darken(0.1, selected)};
}
`;
}}
`;
const sizes = {
large: {
height: '3rem',
fontSize: '1.25rem'
},
medium: {
height: '2.25rem',
fontSize: '1rem'
},
small: {
height: '1.75rem',
fontSize: '0.875rem'
}
};
const sizeStyles = css`
${({ size }) => css`
height: ${sizes[size].height};
font-size: ${sizes[size].fontSize};
`}
`;
const StyledButton = styled.button`
/* 공통 스타일 */
display: inline-flex;
outline: none;
border: none;
border-radius: 4px;
color: white;
font-weight: bold;
cursor: pointer;
padding-left: 1rem;
padding-right: 1rem;
/* 크기 */
${sizeStyles}
/* 색상 */
${colorStyles}
/* 기타 */
& + & {
margin-left: 1rem;
}
`;
const Button = ({ children, color, size, ...rest }) => {
return (
<StyledButton color={color} size={size} {...rest}>
{children}
</StyledButton>
);
}
Button.defaultProps = {
color: 'blue',
size: 'medium'
};
export default Button;
10) colorStyles 수정
- Button 컴포넌트에 outline 이라는 props 를 설정하여 이 값이 true 일 때에는 테두리만 지닌 버튼을 보여주도록 설정
//Button.js
import React from 'react';
import styled, { css } from 'styled-components';
import { darken, lighten } from 'polished';
const colorStyles = css`
${({ theme, color }) => {
const selected = theme.palette[color];
return css`
background: ${selected};
&:hover {
background: ${lighten(0.1, selected)};
}
&:active {
background: ${darken(0.1, selected)};
}
${props =>
props.outline &&
css`
color: ${selected};
background: none;
border: 1px solid ${selected};
&:hover {
background: ${selected};
color: white;
}
`}
`;
}}
`;
const sizes = {
large: {
height: '3rem',
fontSize: '1.25rem'
},
medium: {
height: '2.25rem',
fontSize: '1rem'
},
small: {
height: '1.75rem',
fontSize: '0.875rem'
}
};
const sizeStyles = css`
${({ size }) => css`
height: ${sizes[size].height};
font-size: ${sizes[size].fontSize};
`}
`;
const StyledButton = styled.button`
/* 공통 스타일 */
display: inline-flex;
outline: none;
border: none;
border-radius: 4px;
color: white;
font-weight: bold;
cursor: pointer;
padding-left: 1rem;
padding-right: 1rem;
/* 크기 */
${sizeStyles}
/* 색상 */
${colorStyles}
/* 기타 */
& + & {
margin-left: 1rem;
}
`;
const Button = ({ children, color, size, outline, ...rest }) => {
return (
<StyledButton color={color} size={size} outline={outline} {...rest}>
{children}
</StyledButton>
);
}
Button.defaultProps = {
color: 'blue',
size: 'medium'
};
export default Button;
11) outline 스타일을 가진 버튼 렌더링
//App.js
import React from 'react';
import styled, { ThemeProvider } from 'styled-components';
import Button from './components/Button';
const AppBlock = styled.div`
width: 512px;
margin: 0 auto;
margin-top: 4rem;
border: 1px solid black;
padding: 1rem;
`;
const ButtonGroup = styled.div`
& + & {
margin-top: 1rem;
}
`;
const App = () => {
return (
<ThemeProvider
theme={{
palette: {
blue: '#228be6',
gray: '#495057',
pink: '#f06595'
}
}}
>
<AppBlock>
<ButtonGroup>
<Button size="large">BUTTON</Button>
<Button>BUTTON</Button>
<Button size="small">BUTTON</Button>
</ButtonGroup>
<ButtonGroup>
<Button color="gray" size="large">
BUTTON
</Button>
<Button color="gray">BUTTON</Button>
<Button color="gray" size="small">
BUTTON
</Button>
</ButtonGroup>
<ButtonGroup>
<Button color="pink" size="large">
BUTTON
</Button>
<Button color="pink">BUTTON</Button>
<Button color="pink" size="small">
BUTTON
</Button>
</ButtonGroup>
<ButtonGroup>
<Button size="large" outline>
BUTTON
</Button>
<Button color="gray" outline>
BUTTON
</Button>
<Button color="pink" size="small" outline>
BUTTON
</Button>
</ButtonGroup>
</AppBlock>
</ThemeProvider>
);
}
export default App;
12) 버튼 크기 조정
- fullWidth 라는 props 가 주어졌다면 버튼의 크기가 100% 를 차지하도록 설정
//Button.js
import React from 'react';
import styled, { css } from 'styled-components';
import { darken, lighten } from 'polished';
const colorStyles = css`
${({ theme, color }) => {
const selected = theme.palette[color];
return css`
background: ${selected};
&:hover {
background: ${lighten(0.1, selected)};
}
&:active {
background: ${darken(0.1, selected)};
}
${props =>
props.outline &&
css`
color: ${selected};
background: none;
border: 1px solid ${selected};
&:hover {
background: ${selected};
color: white;
}
`}
`;
}}
`;
const sizes = {
large: {
height: '3rem',
fontSize: '1.25rem'
},
medium: {
height: '2.25rem',
fontSize: '1rem'
},
small: {
height: '1.75rem',
fontSize: '0.875rem'
}
};
const sizeStyles = css`
${({ size }) => css`
height: ${sizes[size].height};
font-size: ${sizes[size].fontSize};
`}
`;
const fullWidthStyle = css`
${props =>
props.fullWidth &&
css`
width: 100%;
justify-content: center;
& + & {
margin-left: 0;
margin-top: 1rem;
}
`}
`;
const StyledButton = styled.button`
/* 공통 스타일 */
display: inline-flex;
outline: none;
border: none;
border-radius: 4px;
color: white;
font-weight: bold;
cursor: pointer;
padding-left: 1rem;
padding-right: 1rem;
/* 크기 */
${sizeStyles}
/* 색상 */
${colorStyles}
/* 기타 */
& + & {
margin-left: 1rem;
}
${fullWidthStyle}
`;
const Button = ({ children, color, size, outline, fullWidth, ...rest }) => {
return (
<StyledButton
color={color}
size={size}
outline={outline}
fullWidth={fullWidth}
{...rest}
>
{children}
</StyledButton>
);
}
Button.defaultProps = {
color: 'blue',
size: 'medium'
};
export default Button;
13) fullWidth 스타일의 컴포넌트 렌더링
//App.js
import React from 'react';
import styled, { ThemeProvider } from 'styled-components';
import Button from './components/Button';
const AppBlock = styled.div`
width: 512px;
margin: 0 auto;
margin-top: 4rem;
border: 1px solid black;
padding: 1rem;
`;
const ButtonGroup = styled.div`
& + & {
margin-top: 1rem;
}
`;
function App() {
return (
<ThemeProvider
theme={{
palette: {
blue: '#228be6',
gray: '#495057',
pink: '#f06595'
}
}}
>
<AppBlock>
<ButtonGroup>
<Button size="large">BUTTON</Button>
<Button>BUTTON</Button>
<Button size="small">BUTTON</Button>
</ButtonGroup>
<ButtonGroup>
<Button color="gray" size="large">
BUTTON
</Button>
<Button color="gray">BUTTON</Button>
<Button color="gray" size="small">
BUTTON
</Button>
</ButtonGroup>
<ButtonGroup>
<Button color="pink" size="large">
BUTTON
</Button>
<Button color="pink">BUTTON</Button>
<Button color="pink" size="small">
BUTTON
</Button>
</ButtonGroup>
<ButtonGroup>
<Button size="large" outline>
BUTTON
</Button>
<Button color="gray" outline>
BUTTON
</Button>
<Button color="pink" size="small" outline>
BUTTON
</Button>
</ButtonGroup>
<ButtonGroup>
<Button size="large" fullWidth>
BUTTON
</Button>
<Button size="large" color="gray" fullWidth>
BUTTON
</Button>
<Button size="large" color="pink" fullWidth>
BUTTON
</Button>
</ButtonGroup>
</AppBlock>
</ThemeProvider>
);
}
export default App;
03-5 Dialog 만들기
- Dialog 컴포넌트 : 기존 화면을 가리면서 정보를 보여줌
1) components 디렉터리에 Dialog.js 파일 생성
//Dialog.js
import React from 'react';
import styled from 'styled-components';
import Button from './Button';
const DarkBackground = styled.div`
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.8);
`;
const DialogBlock = styled.div`
width: 320px;
padding: 1.5rem;
background: white;
border-radius: 2px;
h3 {
margin: 0;
font-size: 1.5rem;
}
p {
font-size: 1.125rem;
}
`;
const ButtonGroup = styled.div`
margin-top: 3rem;
display: flex;
justify-content: flex-end;
`;
const Dialog = ({ title, children, confirmText, cancelText }) => {
return (
<DarkBackground>
<DialogBlock>
<h3>{title}</h3>
<p>{children}</p>
<ButtonGroup>
<Button color="gray">{cancelText}</Button>
<Button color="pink">{confirmText}</Button>
</ButtonGroup>
</DialogBlock>
</DarkBackground>
);
}
Dialog.defaultProps = {
confirmText: '확인',
cancelText: '취소'
};
export default Dialog;
- h3와 p를 스타일링 할 때 따로 컴포넌트를 만들지 않고
const Title = styled.h3``;
const Description = styled.p``;
- Nested CSS 문법을 사용할 수 있으므로 DialogBlock 안에 있는 h3와 p에게 특정 스타일을 주고 싶다면 아래와 같이 작성 가능
const DialogBlock = styled.div`
h3 {}
p {}
`;
2) Dialog 컴포넌트를 App에 렌더링
- Dialog 컴포넌트를 AppBlock 하단에 넣음
- AppBlock 과 Dialog 를 <></> 으로 감싸줌 (ThemeProvider 내부는 하나의 리액트 엘리먼트로 감싸져있어야 하기 때문)
App.js
import React from 'react';
import styled, { ThemeProvider } from 'styled-components';
import Button from './components/Button';
import Dialog from './components/Dialog';
const AppBlock = styled.div`
width: 512px;
margin: 0 auto;
margin-top: 4rem;
border: 1px solid black;
padding: 1rem;
`;
const ButtonGroup = styled.div`
& + & {
margin-top: 1rem;
}
`;
const App = () => {
return (
<ThemeProvider
theme={{
palette: {
blue: '#228be6',
gray: '#495057',
pink: '#f06595'
}
}}
>
<>
<AppBlock>
<ButtonGroup>
<Button size="large">BUTTON</Button>
<Button>BUTTON</Button>
<Button size="small">BUTTON</Button>
</ButtonGroup>
<ButtonGroup>
<Button color="gray" size="large">
BUTTON
</Button>
<Button color="gray">BUTTON</Button>
<Button color="gray" size="small">
BUTTON
</Button>
</ButtonGroup>
<ButtonGroup>
<Button color="pink" size="large">
BUTTON
</Button>
<Button color="pink">BUTTON</Button>
<Button color="pink" size="small">
BUTTON
</Button>
</ButtonGroup>
<ButtonGroup>
<Button size="large" outline>
BUTTON
</Button>
<Button color="gray" outline>
BUTTON
</Button>
<Button color="pink" size="small" outline>
BUTTON
</Button>
</ButtonGroup>
<ButtonGroup>
<Button size="large" fullWidth>
BUTTON
</Button>
<Button size="large" color="gray" fullWidth>
BUTTON
</Button>
<Button size="large" color="pink" fullWidth>
BUTTON
</Button>
</ButtonGroup>
</AppBlock>
<Dialog
title="정말로 삭제하시겠습니까?"
confirmText="삭제"
cancelText="취소"
>
데이터를 정말로 삭제하시겠습니까?
</Dialog>
</>
</ThemeProvider>
);
}
export default App;
3) Dialog.js 에서 ShortMarginButton 생성, 기존 Button 대체
- 버튼 간 여백 줄어들도록 설정
import React from 'react';
import styled from 'styled-components';
import Button from './Button';
const DarkBackground = styled.div`
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.8);
`;
const DialogBlock = styled.div`
width: 320px;
padding: 1.5rem;
background: white;
border-radius: 2px;
h3 {
margin: 0;
font-size: 1.5rem;
}
p {
font-size: 1.125rem;
}
`;
const ButtonGroup = styled.div`
margin-top: 3rem;
display: flex;
justify-content: flex-end;
`;
const ShortMarginButton = styled(Button)`
& + & {
margin-left: 0.5rem;
}
`;
const Dialog = ({ title, children, confirmText, cancelText }) => {
return (
<DarkBackground>
<DialogBlock>
<h3>{title}</h3>
<p>{children}</p>
<ButtonGroup>
<ShortMarginButton color="gray">{cancelText}</ShortMarginButton>
<ShortMarginButton color="pink">{confirmText}</ShortMarginButton>
</ButtonGroup>
</DialogBlock>
</DarkBackground>
);
}
Dialog.defaultProps = {
confirmText: '확인',
cancelText: '취소'
};
export default Dialog;
4) 컴포넌트를 열고 닫을 수 있는 기능 구현
- Dialog에서 onConfirm 과 onCancel 을 props 로 받아오도록 하고 해당 함수들을 각 버튼들에게 onClick 으로 설정
- visible props 도 받아와서 이 값이 false 일 때 컴포넌트에서 null 을 반환하도록 설정
Dialog.js
import React from 'react';
import styled from 'styled-components';
import Button from './Button';
const DarkBackground = styled.div`
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.8);
`;
const DialogBlock = styled.div`
width: 320px;
padding: 1.5rem;
background: white;
border-radius: 2px;
h3 {
margin: 0;
font-size: 1.5rem;
}
p {
font-size: 1.125rem;
}
`;
const ButtonGroup = styled.div`
margin-top: 3rem;
display: flex;
justify-content: flex-end;
`;
const ShortMarginButton = styled(Button)`
& + & {
margin-left: 0.5rem;
}
`;
const Dialog = ({
title,
children,
confirmText,
cancelText,
onConfirm,
onCancel,
visible
}) => {
if (!visible) return null;
return (
<DarkBackground>
<DialogBlock>
<h3>{title}</h3>
<p>{children}</p>
<ButtonGroup>
<ShortMarginButton color="gray" onClick={onCancel}>
{cancelText}
</ShortMarginButton>
<ShortMarginButton color="pink" onClick={onConfirm}>
{confirmText}
</ShortMarginButton>
</ButtonGroup>
</DialogBlock>
</DarkBackground>
);
}
Dialog.defaultProps = {
confirmText: '확인',
cancelText: '취소'
};
export default Dialog;
5) App 컴포넌트에서 useState를 사용하여 Dialog 상태를 관리
- 핑크 버튼의 이름을 '삭제'로 변경 후 해당 버튼을 누르면 Dialog가 보여지도록 설정
- Dialog 에 onConfirm, onCancel, visible 값을 전달
//App.js
import React, { useState } from 'react';
import styled, { ThemeProvider } from 'styled-components';
import Button from './components/Button';
import Dialog from './components/Dialog';
const AppBlock = styled.div`
width: 512px;
margin: 0 auto;
margin-top: 4rem;
border: 1px solid black;
padding: 1rem;
`;
const ButtonGroup = styled.div`
& + & {
margin-top: 1rem;
}
`;
function App() {
const [dialog, setDialog] = useState(false);
const onClick = () => {
setDialog(true);
};
const onConfirm = () => {
console.log('확인');
setDialog(false);
};
const onCancel = () => {
console.log('취소');
setDialog(false);
};
return (
<ThemeProvider
theme={{
palette: {
blue: '#228be6',
gray: '#495057',
pink: '#f06595'
}
}}
>
<>
<AppBlock>
<ButtonGroup>
<Button size="large">BUTTON</Button>
<Button>BUTTON</Button>
<Button size="small">BUTTON</Button>
</ButtonGroup>
<ButtonGroup>
<Button color="gray" size="large">
BUTTON
</Button>
<Button color="gray">BUTTON</Button>
<Button color="gray" size="small">
BUTTON
</Button>
</ButtonGroup>
<ButtonGroup>
<Button color="pink" size="large">
BUTTON
</Button>
<Button color="pink">BUTTON</Button>
<Button color="pink" size="small">
BUTTON
</Button>
</ButtonGroup>
<ButtonGroup>
<Button size="large" outline>
BUTTON
</Button>
<Button color="gray" outline>
BUTTON
</Button>
<Button color="pink" size="small" outline>
BUTTON
</Button>
</ButtonGroup>
<ButtonGroup>
<Button size="large" fullWidth>
BUTTON
</Button>
<Button size="large" color="gray" fullWidth>
BUTTON
</Button>
<Button size="large" color="pink" fullWidth onClick={onClick}>
삭제
</Button>
</ButtonGroup>
</AppBlock>
<Dialog
title="정말로 삭제하시겠습니까?"
confirmText="삭제"
cancelText="취소"
onConfirm={onConfirm}
onCancel={onCancel}
visible={dialog}
>
데이터를 정말로 삭제하시겠습니까?
</Dialog>
</>
</ThemeProvider>
);
}
export default App;
03-6 트랜지션 구현하기
- Dialog가 나타나거나 사라질 때 트랜지션 효과 적용
- 트랜지션 효과 : CSS Keyframe 사용, styled-components 에서는 keyframes라는 유틸 사용
1) Dialog.js 작성
- Dialog가 나타날 때 DarkBackground 쪽에는 서서히 나타나는 fadeIn 효과 부여
- DialogBlock 에는 아래에서부터 위로 올라오는 효과를 보여주는 slideUp 효과 부여
(애니메이션 이름은 마음대로 지정 가능)
import React from 'react';
import styled, { keyframes } from 'styled-components';
import Button from './Button';
const fadeIn = keyframes`
from {
opacity: 0
}
to {
opacity: 1
}
`;
const slideUp = keyframes`
from {
transform: translateY(200px);
}
to {
transform: translateY(0px);
}
`;
const DarkBackground = styled.div`
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.8);
animation-duration: 0.25s;
animation-timing-function: ease-out;
animation-name: ${fadeIn};
animation-fill-mode: forwards;
`;
const DialogBlock = styled.div`
width: 320px;
padding: 1.5rem;
background: white;
border-radius: 2px;
h3 {
margin: 0;
font-size: 1.5rem;
}
p {
font-size: 1.125rem;
}
animation-duration: 0.25s;
animation-timing-function: ease-out;
animation-name: ${slideUp};
animation-fill-mode: forwards;
`;
const ButtonGroup = styled.div`
margin-top: 3rem;
display: flex;
justify-content: flex-end;
`;
const ShortMarginButton = styled(Button)`
& + & {
margin-left: 0.5rem;
}
`;
const Dialog = ({
title,
children,
confirmText,
cancelText,
onConfirm,
onCancel,
visible
}) => {
if (!visible) return null;
return (
<DarkBackground>
<DialogBlock>
<h3>{title}</h3>
<p>{children}</p>
<ButtonGroup>
<ShortMarginButton color="gray" onClick={onCancel}>
{cancelText}
</ShortMarginButton>
<ShortMarginButton color="pink" onClick={onConfirm}>
{confirmText}
</ShortMarginButton>
</ButtonGroup>
</DialogBlock>
</DarkBackground>
);
}
Dialog.defaultProps = {
confirmText: '확인',
cancelText: '취소'
};
export default Dialog;
2) 사라지는 효과 구현
- useEffect 작성 --> visible 값이 true 에서 false 로 바뀌는 시점을 감지하여 animate 값을 true 로 바꿔주고 setTimeout 함수를 사용하여 250ms 이후 false로 바꾸어줌
- !visible 조건에서 null 를 반환하는 대신에 !animate && !localVisible 조건에서 null 을 반환하도록 수정
- Dialog 컴포넌트에서 두 개의 로컬 상태(animate 값, localVisible 값)를 관리 해주어야 함
- animate 값 : 현재 트랜지션 효과를 보여주고 있는 중이라는 상태를 의미
- localVisible 값 : 실제로 컴포넌트가 사라지는 시점을 지연시키기 위한 값
- DarkBackground 와 DialogBlock 에 disappear 라는 props 를 주어서 사라지는 효과가 나타나도록 설정
- 각 컴포넌트의 disappear 값을 !visible로 설정
//Dialog.js
import React, { useState, useEffect } from 'react';
import styled, { keyframes, css } from 'styled-components';
import Button from './Button';
const fadeIn = keyframes`
from {
opacity: 0
}
to {
opacity: 1
}
`;
const fadeOut = keyframes`
from {
opacity: 1
}
to {
opacity: 0
}
`;
const slideUp = keyframes`
from {
transform: translateY(200px);
}
to {
transform: translateY(0px);
}
`;
const slideDown = keyframes`
from {
transform: translateY(0px);
}
to {
transform: translateY(200px);
}
`;
const DarkBackground = styled.div`
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.8);
animation-duration: 0.25s;
animation-timing-function: ease-out;
animation-name: ${fadeIn};
animation-fill-mode: forwards;
${props =>
props.disappear &&
css`
animation-name: ${fadeOut};
`}
`;
const DialogBlock = styled.div`
width: 320px;
padding: 1.5rem;
background: white;
border-radius: 2px;
h3 {
margin: 0;
font-size: 1.5rem;
}
p {
font-size: 1.125rem;
}
animation-duration: 0.25s;
animation-timing-function: ease-out;
animation-name: ${slideUp};
animation-fill-mode: forwards;
${props =>
props.disappear &&
css`
animation-name: ${slideDown};
`}
`;
const ButtonGroup = styled.div`
margin-top: 3rem;
display: flex;
justify-content: flex-end;
`;
const ShortMarginButton = styled(Button)`
& + & {
margin-left: 0.5rem;
}
`;
const Dialog = ({
title,
children,
confirmText,
cancelText,
onConfirm,
onCancel,
visible
}) => {
const [animate, setAnimate] = useState(false);
const [localVisible, setLocalVisible] = useState(visible);
useEffect(() => {
// visible 값이 true -> false 가 되는 것을 감지
if (localVisible && !visible) {
setAnimate(true);
setTimeout(() => setAnimate(false), 250);
}
setLocalVisible(visible);
}, [localVisible, visible]);
if (!animate && !localVisible) return null;
return (
<DarkBackground disappear={!visible}>
<DialogBlock disappear={!visible}>
<h3>{title}</h3>
<p>{children}</p>
<ButtonGroup>
<ShortMarginButton color="gray" onClick={onCancel}>
{cancelText}
</ShortMarginButton>
<ShortMarginButton color="pink" onClick={onConfirm}>
{confirmText}
</ShortMarginButton>
</ButtonGroup>
</DialogBlock>
</DarkBackground>
);
}
Dialog.defaultProps = {
confirmText: '확인',
cancelText: '취소'
};
export default Dialog;
03-7 styled-components 마무리
- CSS in JS는 기존 css파일을 분리하여 작성하는 방식과 개발흐름이 조금 다르므로 적응해야함
'JS' 카테고리의 다른 글
리액트 훅(React Hook) (0) | 2024.08.02 |
---|---|
[2024 여름방학 React.js 스터디] #4주차 "리액트 입문(2)" (0) | 2024.08.02 |
[2024 여름방학 React.js 스터디] #3주차 "리액트 입문" (0) | 2024.07.26 |
[JS] async & await (1) | 2024.07.23 |
[JS] 프로미스(Promise) 이해하기 (2) | 2024.07.23 |