본문 바로가기

웹_프론트_백엔드/프론트엔드

2021.03.28

1. classnames 모듈 사용

** /component/button.jsx

import classNames from 'classnames';
import React from 'react';
import './button.scss';

/*
    HTML에서 CSS class 적용
    <div class="button size">2가지 class 적용</div>
*/
function Button({children, size, color, outline}) {
    // return <button className={["Button", size, color].join(' ')}>{children}</button>;
    // classnames 사용시
    return <button className={classNames("Button", size, color, {outline})}>{children}</button>;
}

Button.defaultProps = {
    size: 'medium',
    color: 'blue'
}

export default Button;

 

** /component/button.scss

$blue: #228be6;
$gray: #495057;
$pink: #f06595;

@mixin button-color($color) {
    background-color: $color;
    &:hover {
        background-color: lighten($color, 10%);
    }
    &:active {
        background-color: darken($color, 10%);
    }
    &.outline {
        color: $color;
        background: none;
        border: 1px solid $color;
        &:hover {
            background-color: $color;
            color: white;
        }
    }
}

.Button {
    display: inline-flex;
    color: white;
    font-weight: bold;
    outline: none;
    border-radius: 4px;
    border: none;
    cursor: pointer;

    // 1. 첫번째 size 실습
    // height: 2.3rem;
    // padding-top: 0.5rem;
    // padding-left: 1rem;
    // padding-right: 1rem;
    // font-size: 1rem;

    // 2. 두번째 size 실습
    &.large {
        height: 3rem;
        padding-top: 0.6rem;
        padding-left: 1rem;
        padding-right: 1rem;
        font-size: 1.25rem;
    }

    &.medium {
        height: 2.25rem;
        padding-top: 0.5rem;
        padding-left: 1rem;
        padding-right: 1rem;
        font-size: 1rem;
    }

    &.small {
        height: 1.7rem;
        padding-top: 0.3rem;
        padding-left: 1rem;
        padding-right: 1rem;
        font-size: 0.85rem;
    }

    // 1. 첫번째 color 실습
    // background-color: $blue;                    // $blue 변수값을 저장
    // &:hover {
    //     background-color: lighten($blue, 10%);  // 색상을 10% 밝게
    // }
    // &:active {
    //     background-color: darken($blue, 10%);   // 색상을 10% 어둡게
    // }

    // 2. 두번째 color 실습
    // &.blue {
    //     background-color: $blue;
    //     &:hover {
    //         background-color: lighten($blue, 10%);
    //     }
    //     &:active {
    //         background-color: darken($blue, 10%);
    //     }
    // }
    // &.gray {
    //     background-color: $gray;
    //     &:hover {
    //         background-color: lighten($gray, 10%);
    //     }
    //     &:active {
    //         background-color: darken($gray, 10%);
    //     }
    // }
    // &.pink {
    //     background-color: $pink;
    //     &:hover {
    //         background-color: lighten($pink, 10%);
    //     }
    //     &:active {
    //         background-color: darken($pink, 10%);
    //     }
    // }

    // 3. 세번째 color 실습
    &.blue {
        @include button-color($blue);
    }
    &.gray {
        @include button-color($gray);
    }
    &.pink {
        @include button-color($pink);
    }

    & + &  {
        margin-left: 1rem;
    }
}

 

** app.jsx

import React from 'react';
import './app.scss';
import Button from './component/button'

function App() {
  return (
    <div className="App">
      <div className="height">
        <Button size="large">버튼 1</Button>
        <Button>버튼 2</Button>
        <Button size="small">버튼 3</Button>
      </div>

      <div className="height">
        <Button size="large" color="gray">버튼 4</Button>
        <Button color="gray">버튼 5</Button>
        <Button size="small" color="gray">버튼 6</Button>
      </div>

      <div className="height">
        <Button size="large" color="pink">버튼 7</Button>
        <Button color="pink">버튼 8</Button>
        <Button size="small" color="pink">버튼 9</Button>
      </div>

      <div className="height">
        <Button size="large" color="blue" outline>버튼 10</Button>
        <Button color="gray" outline>버튼 11</Button>
        <Button size="small" color="pink" outline>버튼 12</Button>
      </div>
    </div>
  );
}

export default App;

 

 

** /component/button.jsx

import classNames from 'classnames';
import React from 'react';
import './button.scss';

/*
    HTML에서 CSS class 적용
    <div class="button size">2가지 class 적용</div>
*/
function Button({children, size, color, outline, fullWidth}) {
    // return <button className={["Button", size, color].join(' ')}>{children}</button>;
    // classnames 사용시
    return <button className={classNames("Button", size, color, {outline, fullWidth})}>{children}</button>;
}

Button.defaultProps = {
    size: 'medium',
    color: 'blue'
}

export default Button;

 

** /component/button.scss

$blue: #228be6;
$gray: #495057;
$pink: #f06595;

@mixin button-color($color) {
    background-color: $color;
    &:hover {
        background-color: lighten($color, 10%);
    }
    &:active {
        background-color: darken($color, 10%);
    }
    &.outline {
        color: $color;
        background: none;
        border: 1px solid $color;
        &:hover {
            background-color: $color;
            color: white;
        }
    }
}

.Button {
    display: inline-flex;
    color: white;
    font-weight: bold;
    outline: none;
    border-radius: 4px;
    border: none;
    cursor: pointer;

    // 1. 첫번째 size 실습
    // height: 2.3rem;
    // padding-top: 0.5rem;
    // padding-left: 1rem;
    // padding-right: 1rem;
    // font-size: 1rem;

    // 2. 두번째 size 실습
    &.large {
        height: 3rem;
        padding-top: 0.6rem;
        padding-left: 1rem;
        padding-right: 1rem;
        font-size: 1.25rem;
    }

    &.medium {
        height: 2.25rem;
        padding-top: 0.5rem;
        padding-left: 1rem;
        padding-right: 1rem;
        font-size: 1rem;
    }

    &.small {
        height: 1.7rem;
        padding-top: 0.3rem;
        padding-left: 1rem;
        padding-right: 1rem;
        font-size: 0.85rem;
    }

    // 1. 첫번째 color 실습
    // background-color: $blue;                    // $blue 변수값을 저장
    // &:hover {
    //     background-color: lighten($blue, 10%);  // 색상을 10% 밝게
    // }
    // &:active {
    //     background-color: darken($blue, 10%);   // 색상을 10% 어둡게
    // }

    // 2. 두번째 color 실습
    // &.blue {
    //     background-color: $blue;
    //     &:hover {
    //         background-color: lighten($blue, 10%);
    //     }
    //     &:active {
    //         background-color: darken($blue, 10%);
    //     }
    // }
    // &.gray {
    //     background-color: $gray;
    //     &:hover {
    //         background-color: lighten($gray, 10%);
    //     }
    //     &:active {
    //         background-color: darken($gray, 10%);
    //     }
    // }
    // &.pink {
    //     background-color: $pink;
    //     &:hover {
    //         background-color: lighten($pink, 10%);
    //     }
    //     &:active {
    //         background-color: darken($pink, 10%);
    //     }
    // }

    // 3. 세번째 color 실습
    &.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.jsx

import React from 'react';
import './app.scss';
import Button from './component/button'

function App() {
  return (
    <div className="App">
      <div className="height">
        <Button size="large">버튼 1</Button>
        <Button>버튼 2</Button>
        <Button size="small">버튼 3</Button>
      </div>

      <div className="height">
        <Button size="large" color="gray">버튼 4</Button>
        <Button color="gray">버튼 5</Button>
        <Button size="small" color="gray">버튼 6</Button>
      </div>

      <div className="height">
        <Button size="large" color="pink">버튼 7</Button>
        <Button color="pink">버튼 8</Button>
        <Button size="small" color="pink">버튼 9</Button>
      </div>

      <div className="height">
        <Button size="large" color="blue" outline>버튼 10</Button>
        <Button color="gray" outline>버튼 11</Button>
        <Button size="small" color="pink" outline>버튼 12</Button>
      </div>

      <div className="height">
        <Button size="large" color="blue" fullWidth>버튼 10</Button>
        <Button color="gray" fullWidth>버튼 11</Button>
        <Button size="small" color="pink" fullWidth>버튼 12</Button>
      </div>
    </div>
  );
}

export default App;

 

 

** /component/button.jsx

import classNames from 'classnames';
import React from 'react';
import './button.scss';

/*
    HTML에서 CSS class 적용
    <div class="button size">2가지 class 적용</div>
*/
function Button({children, size, color, outline, fullWidth, ...rest}) {
    // return <button className={["Button", size, color].join(' ')}>{children}</button>;
    // classnames 사용시
    return <button className={classNames("Button", size, color, {outline, fullWidth})} {...rest}>{children}</button>;
}

Button.defaultProps = {
    size: 'medium',
    color: 'blue'
}

export default Button;

 

** app.jsx

import React from 'react';
import './app.scss';
import Button from './component/button'

function App() {
  return (
    <div className="App">
      <div className="height">
        <Button size="large" onClick={() => console.log('클릭!')}>버튼 1</Button>
        <Button>버튼 2</Button>
        <Button size="small">버튼 3</Button>
      </div>

      <div className="height">
        <Button size="large" color="gray">버튼 4</Button>
        <Button color="gray">버튼 5</Button>
        <Button size="small" color="gray">버튼 6</Button>
      </div>

      <div className="height">
        <Button size="large" color="pink">버튼 7</Button>
        <Button color="pink">버튼 8</Button>
        <Button size="small" color="pink">버튼 9</Button>
      </div>

      <div className="height">
        <Button size="large" color="blue" outline>버튼 10</Button>
        <Button color="gray" outline>버튼 11</Button>
        <Button size="small" color="pink" outline>버튼 12</Button>
      </div>

      <div className="height">
        <Button size="large" color="blue" fullWidth>버튼 10</Button>
        <Button color="gray" fullWidth>버튼 11</Button>
        <Button size="small" color="pink" fullWidth>버튼 12</Button>
      </div>
    </div>
  );
}

export default App;

 

 

 

2. CSS Module

 : 리액트 프로젝트에서 컴포넌트를 스타일링할 때,
   CSS Module 기술을 사용하면 CSS 클래스가 중첩되는 것을 완벽히 방지할 수 있다.

 

   "CSS 파일 확장자를 .module.css로 해야 한다"

 

** box.module.css

.Box {
    background-color: black;
    color: white;
    padding: 2rem;
}

 

** box.jsx

import React from 'react';
import styles from './box.module.css';

function Box() {
    return <div className={styles.Box}>{styles.Box}</div>
}

export default Box;

 

** index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './app';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

 

** app.jsx

import React, { useState } from 'react';
import Box from './component/box'

function App() {
  return (
    <>
    <Box/>
    </>
  );
}

export default App;

 

 

** /component/checkbox.jsx

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.jsx

import React, { useState } from 'react';
// import Box from './component/box'
import Checkbox from './component/checkbox';

function App() {

  const [check, setCheck] = useState(false);
  const onChange = e => {
    setCheck(e.target.checked);
  };

  // return (
  //   <>
  //   <Box/>
  //   </>
  // );
  return ( 
    <div>
      <Checkbox onChange={onChange} checked={check}>
        약관에 모두 동의
      </Checkbox>
      <p>
        <b>check : </b>{check? 'true' : 'false'}
      </p>
    </div>
  );
}

export default App;

 

 

 

3. react-icons
   yarn add react-icons

   [참고사이트] https://react-icons.github.io/react-icons/#/

 

** checkbox.module.css

.checkbox {
    display: flex;
    align-items: center;
}

.checkbox label {
    cursor: pointer;
}

.checkbox input {
    width: 0;
    height: 0;
    position: absolute;
    opacity: 0;
}

.checkbox span {
    font-size: 1.2rem;
    font-weight: bold;
}

.icon {
    display: flex;
    align-items: center;
    font-size: 2rem;
    margin-right: 0.3rem;
    color: #adb3db;
}

.checked {
    color: #339af0;
}

 

** checkbox.jsx

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;

 

 

 

4. Template Literal, reduce()

** taggedTemplate.js

const name = '김사과';
const message1 = `안녕, ${name}`;

console.log(message1);


// Template Literal을 사용할 때, ${} 안에 객체를 넣는다면?
const obj = {num:1};
const message2 = `${obj}`

console.log(message2);


// Template Literal을 사용할 때, ${} 안에 함수를 넣는다면?
const fn = () => true
const message3 = `${fn}`

console.log(message3);


const red = '빨간색';
const blue = '파란색';

function favoriteColors(texts, ...values) {
    // console.log(texts);
    // console.log(values);
    return texts.reduce((result, text, i) => `${result}${text}${values[i] ? `<b>${values[i]}</b>` : ''}`, '');
}

console.log(favoriteColors`제가 좋아하는 색은 ${red}과 ${blue}입니다.` );

 

** reduce.js

const number = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

const val = 0;

const total = number.reduce((val1, val2, idx, arr) => {
    console.log(`val1 : ${val1}, val2 : ${val2}`);
    return val1 + val2;
}, val);

console.log(total);


/*
    reduce
    val1 : reduce() 함수의 두번째 파라미터 val 값이 넘어온다.
    val2 : number의 첫번째 데이터 1이 넘어온다.
    idx : reduce() 함수의 두번째 파라미터인 val를 사용했는지 안했는지에 따라 값이 달라진다.
          val을 사용했다면 0부터, 사용하지 않았다면 1부터 시작한다.
    arr : reduce() 함수가 호출된 배열, number가 리턴
*/

 

 

5. styled-components

 : JS 안에 CSS를 작성하는 라이브러리이다.

 

   "yarn add styled-components"

 

** app.jsx

import React, { useState } from 'react';
// import Box from './component/box'
// import Checkbox from './component/checkbox';
import styled from 'styled-components';

const Circle = styled.div`
  width: 5rem;
  height: 5rem;
  background: black;
  border-radius: 50%
`;

function App() {

  // const [check, setCheck] = useState(false);
  // const onChange = e => {
  //   setCheck(e.target.checked);
  // };

  // // return (
  // //   <>
  // //   <Box/>
  // //   </>
  // // );
  // return ( 
  //   <div>
  //     <Checkbox onChange={onChange} checked={check}>
  //       약관에 모두 동의
  //     </Checkbox>
  //     <p>
  //       <b>check : </b>{check? 'true' : 'false'}
  //     </p>
  //   </div>
  // );

  return <Circle/>
}

export default App;

 

 

** app.jsx

import React, { useState } from 'react';
// import Box from './component/box'
// import Checkbox from './component/checkbox';
import styled, {css} from 'styled-components';

const Circle = styled.div`
  width: 5rem;
  height: 5rem;
  background: ${props => props.color || 'black'};
  border-radius: 50%;

  ${props => 
    props.big && css`
      width: 10rem;
      height: 10rem;
  `}
`;

function App() {

  // const [check, setCheck] = useState(false);
  // const onChange = e => {
  //   setCheck(e.target.checked);
  // };

  // // return (
  // //   <>
  // //   <Box/>
  // //   </>
  // // );
  // return ( 
  //   <div>
  //     <Checkbox onChange={onChange} checked={check}>
  //       약관에 모두 동의
  //     </Checkbox>
  //     <p>
  //       <b>check : </b>{check? 'true' : 'false'}
  //     </p>
  //   </div>
  // );

  return (
    <>
      <Circle/>
      <Circle color="deeppink"/>
      <Circle color="blue" big/>
    </>
  );
}

export default App;

 

 

 

6. 할일 메모장 만들기(디자인 완료, 다음 수업시간에 데이터 상태 관리부분 마저할 예정)

   ① styled-components 사용
   ② Context API를 사용한 전역 상태 관리
   ③ 배열 상태 다루기

 

** 컴포넌트 만들기

1) TodoTemplate
 : 할일의 레이아웃을 설정하는 컴포넌트

 

2) TodoHead
 : 오늘의 날짜, 요일을 보여주고 해야할 일이 몇 개 남았는지 보여줌

 

3) ToDoList
 : 할일에 대한 정보가 들어 있는 todos 배열을 내장함수 map을 사용하여 여러개 todoitem 컴포넌트를 렌더링

 

4) TodoItem
 : 각 할일에 대한 정보를 렌더링해주는 컴포넌트
   완료 여부를 toggle 할 수 있다
   할일이 완료되었을때 좌측에 체크가 나타나고 텍스트의 색상을 연하게 만든다
   마우스를 올리면 휴지통 아이콘이 나타나고 휴지통을 누르면 삭제된다

 

5) TodoCreate
 : 새로운 할일을 등록할 수 있게 해준다
   하단부에 초록색 원 버튼을 렌더링해주고 클릭하면 할일을 입력할 수 있는 폼이 나타난다
   버튼을 다시 누르면 폼이 사라진다

 

** app.jsx

import React from 'react';
import {createGlobalStyle} from 'styled-components';
import TodoTemplate from './components/todoTemplate';
import TodoHead from './components/todoHead';
import TodoList from './components/todoList';
import TodoCreate from './components/todoCreate';

const GlobalStyle = createGlobalStyle`
  body {
    background-color: #e9ecef;
  }
`

function App() {
  return (
    <>
      <GlobalStyle/>
      <TodoTemplate>
        <TodoHead/>
        <TodoList/>
        <TodoCreate/>
      </TodoTemplate>
    </>
  );
}

export default App;

 

** index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './app';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

 

** /components/todoTemplate.jsx

import React from 'react';
import styled from 'styled-components';

const TodoTemplateBlock = styled.div`
    width: 512px;
    height: 768px;

    position: relative;
    background-color: white;
    border-radius: 16px;
    box-shadow: 0 0 8px 0 raba(0, 0, 0, 0.01);

    margin: 0 auto;
    margin-top: 96px;
    margin-botton: 32px;
    display: flex;
    flex-direction: column;
`;

function TodoTemplate({children}) {
    return <TodoTemplateBlock>{children}</TodoTemplateBlock>;
}

export default TodoTemplate;

 

** /components/toDoHead.jsx

import React from 'react';
import styled from 'styled-components';

const TodoHeadBlock = styled.div`
    padding: 48px 32px 24px 32px;
    border-botton: 1px solid #e9ecef;

    h1 {
        magin: 0;
        font-size: 36px;
        color: #343a40;
    }

    .day {
        margin-top: 4px;
        color: #868e96;
        font-size: 21px
    }

    .tasks-left {
        color: #20c997;
        font-size: 18px;
        margin-top: 40px;
        font-weight: bold;
    }
`;

function TodoHead() {
    return (
        <TodoHeadBlock>
            <h1>2021년 3월 28일</h1>
            <div className="day">일요일</div>
            <div className="tasks-left">할 일 2개 남음</div>
        </TodoHeadBlock>
    );
}

export default TodoHead;

 

** /components/todoList.jsx

import React from 'react';
import styled from 'styled-components';
import TodoItem from './todoItem';

const TodoListBlock = styled.div`
    flex: 1;
    padding: 20px 32px;
    padding-bottom: 48px;
    overflow-y: auto;
`;

function TodoList() {
    return (
        <TodoListBlock>
            <TodoItem text="프론트엔드 프로젝트 만들기"/>
            <TodoItem text="밥 잘 챙겨먹기" done={true}/>
            <TodoItem text="운동하기" done={true}/>
            <TodoItem text="일기쓰기"/>
        </TodoListBlock>
    );
}

export default TodoList;

 

** /components/todoItem.jsx

import React from 'react';
import styled, {css} from 'styled-components';
import {MdDone, MdDelete} from 'react-icons/md';

const Remove = styled.div`
    display: flex;
    align-items: center;
    justify-content: center;
    color: #dee2e6;
    font-size: 24px;
    cursor: pointer;
    &:hover {
        color: #ff6b6b;
    }
    display: none;
`;

const TodoItemBlock = styled.div`
    display: flex;
    align-items: center;
    padding-top: 12px;
    padding-botton: 12px;
    &:hover {
        ${Remove} {
            display: block;
        }
    }
`;

const CheckCircle = styled.div`
    width: 32px;
    height: 32px;
    border-radius: 16px;
    border: 1px solid #ced4da;
    font-size: 24px;
    display: flex;
    align-items: center;
    justify-content: center;
    margin-right: 20px;
    cursor: pointer;
    ${props => props.done && css`
        border: 1px solid #38d9a9;
    `}
`;

const Text = styled.div`
    flex: 1;
    font-size: 21px;
    color: #495057;
    ${props => props.done && css`
        color: #ced4da;
    `}
`;

function TodoItem({id, done, text}) {
    return (
        <TodoItemBlock>
            <CheckCircle done={done}>{done && <MdDone/>}</CheckCircle>
            <Text done={done}>{text}</Text>
            <Remove>
                <MdDelete/>
            </Remove>
        </TodoItemBlock>
    );
}

export default TodoItem;

 

** /components/todoCreate.jsx

import React, {useState} from 'react';
import styled, {css} from 'styled-components';
import {MdAdd} from 'react-icons/md';

const CircleButton = styled.button`
    background-color: #39d9a9;

    &:hover {
        background-color: #63e6be;
    }
    &:active {
        background: #20c997;
    }

    z-index: 5;
    cursor: pointer;
    width: 80px;
    height: 80px;
    display: flex;
    align-items: center;
    jusify-content: center;
    font-size: 60px;
    position: absolute;
    left: 50%;
    bottom: 0px;
    transform: translate(-50%, 50%);
    color: white;
    border-radius: 50%;
    border: none;
    outline: none;
    transition: 0.2s all ease-in;

    ${props => props.open && css`
        background-color: #ff6b6b;
        &:hover {
            background: #ff8787;
        }
        &:active {
            background: #fa5252;
        }
        transform: translate(-50%, 50%) rotate(45deg);
    `}
`;

const InsertFormPositioner = styled.div`
    width: 100%;
    bottom: 0;
    left: 0;
    position: absolute;
`;

const InsertForm = styled.form`
    background: #f8f9fa;
    padding: 32px 32px 72px 32px;

    border-bottom-left-radius: 16px;
    border-bottom-right-radius: 16px;
    border-top: 1px solid #e9ecef;
`;

const Input = styled.input`
    padding: 12px;
    border-radius: 4px;
    border: 1px solid #dee2e6;
    width: 100%;
    outline: none;
    font-size: 18px;
    box-sizing: border-box;
`;

function TodoCreate() {

    const [open, setOpen] = useState(false);

    const onToggle = () => setOpen(!open);

    return (
        <>
            {open && (
                <InsertFormPositioner> 
                    <InsertForm>
                        <Input autoFocus placeholder="할 일을 입력 후 Enter를 누르세요"/>
                    </InsertForm>
                </InsertFormPositioner>
            )}
            <CircleButton onClick={onToggle} open={open}>
                <MdAdd/>
            </CircleButton>
        </>
    );
}

export default TodoCreate;

'웹_프론트_백엔드 > 프론트엔드' 카테고리의 다른 글

2021.04.04  (0) 2021.05.24
2021.04.03  (0) 2021.05.18
2021.03.27  (0) 2021.05.14
2021.03.21  (0) 2021.05.12
2021.03.20  (0) 2021.05.11