안녕하세요!
오늘은 리액트를 이용한 컴포넌트 스타일링에 대해 배워보도록 할게요~~
기본적으로 리액트는 css 파일을 만들고, 이것을 컴포넌트로 import 하는 것입니다.
이것도 인라인보다는 편리하지만, 더 나은 방법들을 살펴볼게요.
1. SASS
SASS는?
- css의 pre-processor입니다.
- 복잡한 작업을 쉽게 할 수 있게 해주고, 코드의 재활용성을 높여줄 뿐 만 아니라, 코드의 가독성을 높여주어 유지보수를 쉽게해줍니다.
- 문법에는 sass, scss 두 종류가 존재하나, 더 대중적으로 사용되는 scss를 배워보도록 합니다.
먼저 새로운 리액트 프로젝트를 만드세요.
$ npx create-react-app styling-with-sass
그 다음에는, 해당 프로젝트 디렉터리에 node-sass 라는 라이브러리를 설치하세요.
$ cd styling-with-sass
$ yarn add node-sass
*이 라이브러리는 Sass 를 CSS 로 변환해주는 역할을 합니다.
components 내부에 button.js와 button.scss를 작성해줍니다.
components/Button.js
import React from 'react';
import './Button.scss';
function 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% 어둡게
}
}
App.js 렌더링
import React from 'react';
import './App.scss';
import Button from './components/Button';
function App() {
return (
<div className="App">
<div className="buttons">
<Button>BUTTON</Button>
</div>
</div>
);
}
export default App;
App.css -> App.scss
.App {
width: 512px;
margin: 0 auto;
margin-top: 4rem;
border: 1px solid black;
padding: 1rem;
}
다음과 같이 버튼이 나타나게 됩니다.
이 버튼을 조작해 봅시다.
우선 버튼 크기에 large, medium, small 를 설정해줄 수 있도록 구현을 해봅시다.
Button.js 에서 다음과 같이 defaultProps 를 통하여 size 의 기본값을 medium 으로 설정하고,
이 값을 button 의 className 에 넣어줍니다.
Button.js
import React from 'react';
import './Button.scss';
function Button({ children, size }) {
return <button className={['Button', size].join(' ')}>{children}</button>;
}
Button.defaultProps = {
size: 'medium'
};
export default Button;
className 에 CSS 클래스 이름을 동적으로 넣어주고 싶으면 이렇게 설정을 해주어야 합니다.
className={`Button ${size}`}
*classNames 라이브러리를 사용하면 함수의 인자에 문자열, 배열, 객체 등을 전달하여 손쉽게 문자열을 조합 할 수 있습니다.
install 후, Button.js에 적용해봅시다.
Button.js
import React from 'react';
import classNames from 'classnames';
import './Button.scss';
function Button({ children, size }) {
return <button className={classNames('Button', size)}>{children}</button>;
}
Button.defaultProps = {
size: 'medium'
};
export default Button;
6행에 변화가 있네요.
이제 props 값이 button 태그의 className 으로 전달이 될 것 입니다. 이제 이에 따라 Button.scss 에서 다른 크기를 지정해줘봅시다
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%);
}
}
L, M, S 를 각각 설정해주었습니다.
App에서 렌더링하고, size값도 설정해 보겠습니다.
App.js
import React from 'react';
import './App.scss';
import Button from './components/Button';
function 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;
Large, Medium(defalut), Small button.
버튼에 간격을 주려면,
& + & {
margin-left: 1rem;
}
을 .scss 파일에 추가해줍니다.
& + &는 .Button + .Button을 의미합니다. 우측의 Button의 left에, 즉 중앙에 마진을 설정해준 것입니다.
버튼에 색상을 불러와보겠습니다.
- Button 에서 color 라는 props 를 받아옵니다.
- defalut는 blue. 그리고, size 와 마찬가지로 color 값을 className 에 포함시켜주세요.
components/Button.js
import React from 'react';
import classNames from 'classnames';
import './Button.scss';
function Button({ children, size, color }) {
return (
<button className={classNames('Button', size, color)}>{children}</button>
);
}
Button.defaultProps = {
size: 'medium',
color: 'blue'
};
export default Button;
Button.scss 파일도 수정해봅시다.
components/Button.scss
$blue: #228be6;
$gray: #495057;
$pink: #f06595;
.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 {
background: $blue;
&:hover {
background: lighten($blue, 10%);
}
&:active {
background: darken($blue, 10%);
}
}
&.gray {
background: $gray;
&:hover {
background: lighten($gray, 10%);
}
&:active {
background: darken($gray, 10%);
}
}
&.pink {
background: $pink;
&:hover {
background: lighten($pink, 10%);
}
&:active {
background: darken($pink, 10%);
}
}
& + & {
margin-left: 1rem;
}
}
이 코드를 보면, 반복되는 부분이 보입니다.
&.blue {
background: $blue;
&:hover {
background: lighten($blue, 10%);
}
&:active {
background: darken($blue, 10%);
}
}
&.gray {
background: $gray;
&:hover {
background: lighten($gray, 10%);
}
&:active {
background: darken($gray, 10%);
}
}
&.pink {
background: $pink;
&:hover {
background: lighten($pink, 10%);
}
&:active {
background: darken($pink, 10%);
}
}
blue, gray, pink에서 총 3번 색상 관리의 동일한 형태가 반복되는데요,
이렇게 반복이 되는 코드는 Sass 의 mixin 이라는 기능을 사용하여 쉽게 재사용 할 수 있습니다.
한번, button-color라는 mixin 을 만들어서 사용해보겠습니다.
components/Button.scss
$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;
}
}
blue, gray, pink가 파라미터처럼 들어가며, 하나하나 지정할 필요가 없어집니다.
더욱 간결해진 색상 관리입니다.
Appjs를 통해서 이를 확인해봅시다.
정말 멋진 9개의 버튼들을 볼 수 있습니다.
근데 맨 왼쪽의 버튼들이 너무 붙어있네요. margin-top을 주어 틈을 벌려볼게요.
App.scss
.App {
width: 512px;
margin: 0 auto;
margin-top: 4rem;
border: 1px solid black;
padding: 1rem;
.buttons + .buttons {
margin-top: 1rem;
}
}
- 테두리를 나타내게 outline 옵션 사용해보기.
components/Button.js
import React from 'react';
import classNames from 'classnames';
import './Button.scss';
function Button({ children, size, color, outline }) {
return (
<button className={classNames('Button', size, color, { outline })}>
{children}
</button>
);
}
Button.defaultProps = {
size: 'medium',
color: 'blue'
};
export default Button;
- outline 값을 props 로 받아와서 객체 안에 집어 넣은 다음에 classNames() 에 포함시켜줬는데요, 이렇게 하면 outline 값이 true 일 때에만 button 에 outline CSS 클래스가 적용됩니다.
- 만약 outline CSS 클래스가 있다면, 테두리만 보여지도록 스타일쪽 코드도 수정해보세요.
components/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;
}
}
App.js 렌더
import React from 'react';
import './App.scss';
import Button from './components/Button';
function 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;
이러면 4째 줄에 버튼들이 추가되며, 여기서는 테두리만 표시됩니다.
*버튼이 전체 넓이를 차지하도록 하는 옵션: fullWidth를 통해
components/Button.js 수정
import React from 'react';
import classNames from 'classnames';
import './Button.scss';
function 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;
components/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;
}
}
}
App.js 렌더링
import React from 'react';
import './App.scss';
import Button from './components/Button';
function 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;
결과는 다음과 같습니다.
이로서 버튼 components를 완성했는데요,
여기에 onClick 이벤트를 넣어주거나 onMouseMove 이벤트를 넣어주려고 하면, 다음과 같이 매번 코드에 넣어줘야 합니다.
function Button({ children, size, color, outline, fullWidth, onClick, onMouseMove }) {
return (
<button
className={classNames('Button', size, color, { outline, fullWidth })}
onClick={onClick}
onMouseMove={onMouseMove}
spread와 rest로 이를 편리하게 사용해봐요.
components/Button.js
import React from 'react';
import classNames from 'classnames';
import './Button.scss';
function 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;
/*
...rest를 사용해서 우리가 지정한 props 를 제외한 값들을 rest 라는 객체에 모아주고,
<button> 태그에 {...rest} 를 해주면, rest 안에 있는 객체안에 있는 값들을 모두 <button>
태그에 설정을 해준답니다. */
<div className="buttons">
<Button size="large" onClick={() => console.log('클릭됐다!')}>
BUTTON
</Button>
<Button>BUTTON</Button>
<Button size="small">BUTTON</Button>
</div>
App.js의 첫번째 버튼에 onCLick을 설정해줍니다.
그러면, 시행 시 onLCick이 발생함을 알 수 있습니다.
어떤 이벤트를 받을지 정해두지 않는 경우, ...rest를 통해 편한 관리가 가능합니다.
이번 튜토리얼에서는 Sass 를 활용하는 방법에 대해서 알아보았습니다. Sass 를 사용하면 스타일 파일에 다양한 유용한 문법을 사용해서 컴포넌트 스타일링 생산성을 높여줄 수 있습니다!
02. CSS Module
- 리액트 프로젝트에서 컴포넌트를 스타일링 할 때 CSS Module 이라는 기술을 사용하면, CSS 클래스가 중첩되는 것을 완벽히 방지할 수 있습니다.
- CRA 프로젝트에서 CSS Module 를 사용 할 때에는, CSS 파일의 확장자를 .module.css 로 하면 되는데요,
다음과 같이 Box.module.css를 작성해줍니다.
Box.module.css
.Box {
background: black;
color: white;
padding: 2rem;
}
Box.js
import React from "react";
import styles from "./Box.module.css";
function Box() {
return <div className={styles.Box}>{styles.Box}</div>;
}
export default Box;
className 을 설정 할 때에는 styles.Box 처럼 styles 객체 안에 있는 값을 참조해야 합니다.
결국, CSS 클래스 네이밍 규칙을 만들고 따르기 싫다면, CSS Module 을 사용하면 되는 것입니다.
components 내에 뼈대부터 추가해줍시다.
components/CheckBox.js
import React from 'react';
function CheckBox({ children, checked, ...rest }) {
return (
<div>
<label>
<input type="checkbox" checked={checked} {...rest} />
<div>{checked ? '체크됨' : '체크 안됨'}</div>
</label>
<span>{children}</span>
</div>
);
}
export default CheckBox;
App.js
import React, { useState } from 'react';
import CheckBox from './components/CheckBox';
function 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;
그러면 다음과 같은 화면이 나옵니다.
yarn add react-icons
로 아이콘을 다운받고 넣어줍니다.
components/CheckBox.js 수정
import React from 'react';
import { MdCheckBox, MdCheckBoxOutlineBlank } from 'react-icons/md';
function 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;
components/CheckBox.module.css 수정
.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;
}
/*
CSS Module 을 작성 할 때에는 CSS 클래스 이름이 다른 곳에서 사용되는 CSS 클래스 이름과 중복될 일이
없기 때문에 .icon, .checkbox 같은 짧고 흔한 이름을 사용해도 상관이 없습니다.
*/
components/CheckBox.js 수정
import React from 'react';
import { MdCheckBox, MdCheckBoxOutlineBlank } from 'react-icons/md';
import styles from './CheckBox.module.css';
function 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;
- Sass 를 배울 때 썼었던 classnames 라이브러리에는 bind 기능이 있는데요, 이 기능을 사용하면 CSS Module 을 조금 더 편하게 사용 할 수 있습니다.
install 해줍니다.
그리고,
components/CheckBox.js 수정
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);
function 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;
- CSS Module 은 Sass 에서도 사용 할 수 있습니다. 그냥 확장자를 .module.scss 로 바꿔주면 됩니다. 물론, 그 전에 node-sass 를 설치해야합니다.
- 클래스 이름을 고유하게 사용하지 않고 전역으로 사용하고 싶다면, 이렇게 작성해줍니다.
:global .my-global-name {
}
SASS를 사용하면, 아래와 같습니다.
:global {
.my-global-name {
}
}
03. styled-components
CSS in JS는 웹개발에서 가장 많이 사용되는 기술입니다.
즐겁게 배워봅시다.
우선, styled-components를 설치해줍니다.
App.js 작성
import React from 'react';
import styled from 'styled-components';
const Circle = styled.div`
width: 5rem;
height: 5rem;
background: black;
border-radius: 50%;
`;
function App() {
return <Circle />;
}
export default App;
- styled-components 를 사용하면 이렇게 스타일을 입력함과 동시에 해당 스타일을 가진 컴포넌트를 만들 수 있습니다.
- 만약에 div 를 스타일링 하고 싶으면 styled.div, input 을 스타일링 하고 싶으면 styled.input 처럼 사용합니다.
props를 먹여서 크기를 변하는 원을 구현해보겠습니다.
App.js
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;
`}
`;
function App() {
return <Circle color="red" huge />;
}
export default App;
원이 참 크네요
이전의 Saas 버튼 컴포넌트를 새로운 방식으로 다시 구현해봐요.
components/Button.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;
}
`;
function Button({ children, ...rest }) {
return <StyledButton {...rest}>{children}</StyledButton>;
}
export default 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;
`;
function App() {
return (
<AppBlock>
<Button>BUTTON</Button>
</AppBlock>
);
}
export default App;
Saas 그대로 구현해봅시다!
components/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}
`;
function 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;
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;
*Dialog 추가
대화창인 Dialog에 트랜지션 기능을 넣어 여기에 추가해 주겠습니다.
components/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;
}
`;
function 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;
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;
렌더링을 마치고 실행해 보겠습니다.
이번주차 블로깅 마치겠습니다.
감사합니다.
'WINK-(Web & App) > React.js 스터디' 카테고리의 다른 글
[2024 React.js 스터디] 류상우 #6주차 (0) | 2024.05.28 |
---|---|
[2024 React.js 스터디] 박건민 #6주차 (0) | 2024.05.28 |
[2024 React.js 스터디] 김지나 #6주차 (0) | 2024.05.28 |
[2024 React.js 스터디] 정호용 #6주차 "React.JS 꾸미기" (0) | 2024.05.27 |
[2024 React.js 스터디] 박지민 #6주차 "리액트 컴포넌트 스타일링하기" (0) | 2024.05.27 |