IMMERSIVE #13 - TODO LIST

geonhwiยท2019๋…„ 8์›” 9์ผ
1

๊ฐœ์ธ ํ”„๋กœ์ ํŠธ TODO LIST์˜ ์‹œ์ž‘์ž…๋‹ˆ๋‹ค!!! ์ง์ง์ง ๐Ÿ‘๐Ÿป๐Ÿ‘๐Ÿป๐Ÿ‘๐Ÿป

์ดํ‹€์ด์ง€๋งŒ ๊ฝค๋‚˜ ๊ธด ์—ฌ์ •์ด ๋  ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

์ฐจ๊ทผ์ฐจ๊ทผ ๋ธ”๋กœ๊ทธ์— ๊ธฐ๋กํ•ด๊ฐ€๋ฉด์„œ LIST๋ฅผ ์Œ“์•„๋‚˜๊ฐ€๊ฒ ์Šต๋‹ˆ๋‹ค.

CodeStates์—์„œ ๋ฐฐ์šด๋Œ€๋กœ๋งŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ๋”ฐ๋กœ ํ›…์Šค๋‚˜ ๋‹ค๋ฅธ ๊ธฐ๋ฒ•์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ฒ ์Šต๋‹ˆ๋‹ค.

๋˜ํ•œ, ์ƒํƒœ๊ฐ€ ๊ผญ ํ•„์š”ํ•œ ๋ถ€๋ถ„์ด ์•„๋‹ˆ๋ฉด ๋ชจ๋“  ์ƒํƒœ๋Š” App.js์—์„œ ๊ด€๋ฆฌํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.


๐Ÿ“’ TODO LIST

๐Ÿค” THINKING

์–ด๋–ป๊ฒŒ ๊ตฌ์„ฑํ•ด์•ผ ํ• ๊นŒ์š”? ๋งŒ๋“ค๊ธด ์–ด๋ ต๊ฒ ์ง€๋งŒ, ์ž…์ฝ”๋”ฉ์ด๋ผ๋„ ํ•ด๋ด…์‹œ๋‹ค!

  1. ํฐ ํ‹€๋กœ ์ „์ฒด๋ฅผ ๊ฐ์‹ธ์ฃผ์–ด์•ผ ํ•  ๋“ฏ ํ•ฉ๋‹ˆ๋‹ค. ์•ฑ์ฒ˜๋Ÿผ ๋ณด์ด๊ฒŒ์š”!
  2. ์ถ”๊ฐ€ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด ์ €์žฅ์ด๋˜๋ฉด์„œ, ์ƒˆ๋กœ์šด ๋ฆฌ์ŠคํŠธ๊ฐ€ ๋‚˜์™€์•ผ ๊ฒ ๋„ค์š”!
  3. ์ œ์ผ ์œ„์ชฝ์— Title์„ ๊ตฌ์„ฑํ•˜๊ตฌ์š”. ์˜ค๋ฅธ์ชฝ์€ ๋‚ ์งœ๋„ ์žˆ์œผ๋ฉด ์ข‹๊ฒ ๋„ค์š”.
  4. Input์ฐฝ์—์„œ ์ž…๋ ฅํ•˜๋ฉด ๋ฐ”๋กœ ๊ธ€์”จ๊ฐ€ ์จ์ง€๊ณ , submit์œผ๋กœ ๋ฐ”๋กœ ๊ทธ ์ž๋ฆฌ์— ์ถ”๊ฐ€ํ•ด์ค์‹œ๋‹ค.
  5. ๊ทธ๋ฆฌ๊ณ  ๋ฐ”๋กœ ๋‹ค์Œ ์ž…๋ ฅ์นธ์œผ๋กœ ํฌ์ปค์Šค๊ฐ€ ๋„˜์–ด๊ฐ€๊ณ , ํ•  ์ผ์„ ์ž‘์„ฑํ•ด์•ผ์ฃ .
  6. ๊ฐ ํ•  ์ผ์„ ํด๋ฆญํ•˜๋ฉด ์™„๋ฃŒํ•œ ์ผ์„ ํ‘œ์‹œํ•ด์ค˜์•ผํ•ฉ๋‹ˆ๋‹ค.
  7. ๊ฐ ํ•  ์ผ๋“ค์„ ์‚ญ์ œ๋„ ํ•ด์ค˜์•ผ๊ฒ ์ฃ ? ์˜ค๋ฅธ์ชฝ ์ฏค์— ๋ฒ„ํŠผํ•˜๋‚˜ ์ถ”๊ฐ€ํ•ฉ์‹œ๋‹ค

1. Base Setting

๊ธฐ๋ณธ ์…‹ํŒ…์„ ํ•ด๋ด…์‹œ๋‹ค.

๋จผ์ € CRA๋ฅผ ์ด์šฉํ•˜์—ฌ ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

npx create-react-app <ํ”„๋กœ์ ํŠธ ์ด๋ฆ„>

ํ”„๋กœ์ ํŠธ๋ฅผ ๊น”๋”ํ•˜๊ฒŒ ๊ตฌ์„ฑํ•˜๊ธฐ ์œ„ํ•ด ESLint์™€ Prettier๋ฅผ ์‚ฌ์šฉํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

๋งˆ์ผ“ํ”Œ๋ ˆ์ด์Šค์—์„œ ๋‘ ๊ฐœ๋ฅผ ์„ค์น˜๋ถ€ํ„ฐ ํ•ด์ฃผ์‹œ๊ณ  ์•„๋ž˜๋กœ ๋”ฐ๋ผ์™€์ฃผ์…”์•ผํ•ด์š” :)

package.json์— ์•„๋ž˜๋ฅผ ๋ถ™์—ฌ๋„ฃ๊ณ  yarn install๋กœ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค.

// package.json
"devDependencies": {
    "eslint-config-prettier": "^6.0.0",
    "eslint-plugin-prettier": "^3.1.0",
    "eslint-plugin-react": "^7.14.3",
    "husky": "^3.0.2",
    "lint-staged": "^9.2.1",
    "prettier": "1.18.2"
  },
  "lint-staged": {
    "*.{js,jsx}": [
      "eslint --fix",
      "git add"
    ]
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  }

๋‹ค์Œ .eslintrcํŒŒ์ผ์„ ์ƒ์„ฑํ•˜์—ฌ ์•„๋ž˜์™€ ๊ฐ™์ด ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.

// .eslintrc
{
  "extends": [
    "eslint:recommended",
    "plugin:prettier/recommended",
    "plugin:react/recommended",
    "prettier",
    "prettier/react"
  ],
  "plugins": ["prettier", "react"],
  "parserOptions": {
    "ecmaVersion": 6,
    "sourceType": "module",
    "ecmaFeatures": {
      "jsx": true
    }
  },
  "env": {
    "es6": true,
    "browser": true,
    "node": true
  },
  "rules": {
    "prettier/prettier": [
      "error",
      {
        "singleQuote": true,
        "printWidth": 120
      },
      {
        "usePrettierrc": false
      }
    ],
    "no-console": "warn",
    "semi": 2,
    "no-undef": "warn"
  }
}

๋งˆ์ง€๋ง‰์œผ๋กœ, ESLint์™€ Prettier ์„ค์ •์„ Setting.json์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

// Setting.json
{
  // ...์ƒ๋žต...
  "eslint.autoFixOnSave": true,
  "eslint.packageManager": "yarn",
  "eslint.validate": [
    "javascript",
    "javascriptreact",
    "html",
    "typescriptreact"
  ],
  "editor.formatOnSave": true,
  "javascript.format.enable": false,
  "prettier.eslintIntegration": true
}

์ด์ œ App.js๋กœ ๋“ค์–ด๊ฐ€์„œ Command + sํ•ด์ฃผ์‹œ๋ฉด ์ž๋™ ์ •๋ ฌ์ด ๋ฉ๋‹ˆ๋‹ค!

ํ˜น์‹œ ์ฝ”๋“œ ์ž‘์„ฑํ•˜์‹œ๋‹ค๊ฐ€ babel๊ด€๋ จ Lint์—๋Ÿฌ๊ฐ€ ๋‚˜์‹ ๋‹ค๋ฉด ์•„๋ž˜๋ฅผ .eslintrc์— ์ถ”๊ฐ€ํ•ด๋ณด์„ธ์š”!

// .eslintrc
"parser": "babel-eslint",

2. ๊ธฐ๋ณธ ํ™”๋ฉด ๊ตฌ์„ฑ

์ด๋ฒˆ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” Sass๋ฅผ ์—ฐ์Šตํ•ด๋ณด๊ธฐ ์œ„ํ•ด์„œ ์ดํ›„์— scssํŒŒ์ผ์„ ๋งŒ๋“ค๊ฑฐ์—์š”.

App.js / App.css / index.js / index.css ๋งŒ ๋‚จ๊ฒจ๋‘๊ณ  ์‚ญ์ œํ• ๊ฒŒ์š”!

๊ทธ ๋’ค ๊ฐ„๋‹จํ•œ ํ™”๋ฉด ๊ตฌ์„ฑ์„ ์œ„ํ•ด์„œ ๋‚ด์šฉ์„ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค!

/* index.css */
body {
  margin: 1rem;
  padding: 0;
  background: #2196f3;
}
// App.js
import React, { Component } from 'react';
import './App.css';

class App extends Component {
  render() {
    return <div className="App">TODO LIST๋ฅผ ๋งŒ๋“ค์–ด๋ณผ๊ฑฐ์—์š”!</div>;
  }
}

export default App;
/* App.css */
.App {
  width: 760px;
  margin: 0 auto;
  padding: 2rem;
  background: #fff;
  border-radius: 1em;
  box-shadow: 0 5px 5px rgba(0, 0, 0, 0.3);
}

์ด์ œ yarn start๋กœ ํ™•์ธํ•ด๋ณผ๊นŒ์š”? ๐Ÿ‘‡๐Ÿป๐Ÿ‘‡๐Ÿป๐Ÿ‘‡๐Ÿป


3. Title

๊ฐ„๋‹จํ•˜๊ฒŒ ํƒ€์ดํ‹€๋ถ€ํ„ฐ ๋งŒ๋“ค์–ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

srcํด๋”์— components ํด๋”๋ฅผ ์ƒ์„ฑํ•˜๊ณ , Title.jsx์™€ Title.scss๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

Sass๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์œผ์‹œ๋ฉด yarn add node-sass๋ฅผ ์‚ฌ์šฉํ•˜์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค!

๋ฌผ๋ก  css๋ฅผ ์‚ฌ์šฉํ•˜์…”๋„ ๋งŒ๋“œ๋Š” ๊ฑฐ์— ํฐ ์ง€์žฅ์€ ์—†์ง€๋งŒ, ์ €๋Š” ์—ฐ์Šต์„ ์œ„ํ•ด ์‚ฌ์šฉํ•ด๋ณผ๊ฒŒ์š”!

์ด๋ฒˆ๊นŒ์ง€๋งŒ import์™€ export๋ฅผ ์ ๊ณ , ์ดํ›„์—๋Š” ์ƒ๋žตํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค!

// Title.jsx
import React from 'react';
import './Title.scss';

const Title = () => {
  return <div className="title">TODO LIST</div>;
};

export default Title;
// Title.scss
.title {
  padding-bottom: 1rem;
  font-size: 2.5rem;
  font-weight: 700;
  text-align: center;
  border-bottom: 0.8px solid rgba($color: #0000ff, $alpha: 0.2);
}

์ด์ œ App.js์—์„œ Title์„ ๋ถˆ๋Ÿฌ์™€์ฃผ์„ธ์š”.

์•„๋ž˜์™€ ๊ฐ™์ด ๋‚˜์˜จ๋‹ค๋ฉด ์™„์„ฑ์ž…๋‹ˆ๋‹ค! css๋Š” ์ž์œ ๋กญ๊ฒŒ ๋งŒ๋“ค์–ด์ฃผ์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.


4. Form

์–ด๋Š ์ •๋„๊นŒ์ง€ ๋งŒ๋“ค๊ณ  ํฌ์ŠคํŒ…์„ ํ•ด์•ผํ•  ์ง€ ์• ๋งคํ•œ๋ฐ์š”~ ์ผ๋‹จ ๋ด…์‹œ๋‹ค!

Form.jsx, Form.scss๋ฅผ ๋งŒ๋“ค๊ณ  ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.

๊ฐ‘์ž๊ธฐ PropTypes๊ฐ€ ๋‚˜์™”์ฃ ...?!

๊ฐ prop์˜ ํƒ€์ž…์„ ์ง€์ •ํ•ด์ฃผ๋Š”๊ฑด๋ฐ์š”, ESLint์—์„œ ์—๋Ÿฌ๋ฅผ ์ฃผ์–ด์„œ ์„ค์น˜ํ–ˆ์Šต๋‹ˆ๋‹ค.

์•„ ๋ฌผ๋ก , ํƒ€์ž… ์ง€์ •์„ ํ•ด์ฃผ๋Š” ๊ฑด ์•„์ฃผ ๋ฐ”๋žŒ์งํ•œ ์ฝ”๋”ฉ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค!

yarn add prop-types๋ฅผ ํ•˜์‹œ๊ณ  ์ถ”๊ฐ€ํ•˜์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค!

TypsScript๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๊นŒ์ง„ ํƒ€์ž… ์ง€์ •์„ ํ•ด์ฃผ์–ด์„œ ์˜ค๋ฅ˜๋ฅผ ๋ฐฉ์ง€ํ•ด์ค์‹œ๋‹ค!

// Form.jsx
import PropTypes from 'prop-types';

const Form = ({ inputRef, onSubmit, onChange, inputText }) => {
  return (
    <form className="form" onSubmit={e => onSubmit(e)}>
      <input className="form-input" ref={input => inputRef(input)} value={inputText} onChange={e => onChange(e)} />
      <input className="form-submit" type="submit" />
    </form>
  );
};

Form.propTypes = {
  inputRef: PropTypes.func.isRequired,
  onSubmit: PropTypes.func.isRequired,
  onChange: PropTypes.func.isRequired,
  inputText: PropTypes.string.isRequired
};

Form์˜ ref๋Š” App.js์—์„œ ์ดˆ๊ธฐ์— input์— focus๋ฅผ ์ฃผ๊ธฐ ์œ„ํ•ด์„œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

๋‚˜๋จธ์ง€ ํ•จ์ˆ˜๋“ค๋„ ๋ชจ๋‘ App.js์—์„œ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด prop์œผ๋กœ ์ „๋‹ฌ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค!

onChange๋‚˜ onClick event๋Š” ํ™”์‚ดํ‘œ ํ•จ์ˆ˜๋กœ ํ•จ์ˆ˜์— ์ง์ ‘ ์ „๋‹ฌํ•ด์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

// Form.scss
.form {
  .form-input {
    width: 90%;
    margin: 1.5rem;
    padding: 1rem;
    font-size: 1.2rem;
    color: #2196f3;
    border: 1px solid #eee;
    border-radius: 3px;

    &:focus {
      outline-color: rgba($color: #6cccf8, $alpha: 0.1);
    }
  }
  .form-submit {
    display: none;
  }
}

์ด์ œ ์ด ๋ชจ๋“ ๊ฑธ ๊ด€๋ฆฌํ•  App.js๋ฅผ ์ˆ˜์ •ํ•ด์ค๋‹ˆ๋‹ค!

๊ฐ„๋‹จํ•˜๊ฒŒ ์ฃผ์„์œผ๋กœ ์ฝ•์ฝ• ์„ค๋ช…ํ• ๊ฒŒ์š”.

// App.js
class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      text: ''
    };
  }

  // TODO : ์ฒ˜์Œ ์‹œ์ž‘ ์‹œ์— Input์ฐฝ์„ ํฌ์ปค์Šค
  componentDidMount() {
    this.textInput.focus();
  }

  // TODO : Input์ž…๋ ฅ ์‹œ ๊ฐ’ ๋ณ€ํ™”
  onChange = e => {
    this.setState({ text: e.target.value });
  };

  // TODO : input๊ฐ’์„ ์ž…๋ ฅํ•˜๊ณ  submitํ•˜๋ฉด ๊ฐ’์„ ๋น„์›๋‹ˆ๋‹ค.
  onSubmit = e => {
    e.preventDefault();
    this.setState({
      text: ''
    });
  };

  // TODO : input focus๋ฅผ ์œ„ํ•œ ํ•จ์ˆ˜
  inputRef = input => {
    this.textInput = input;
  };

  render() {
    // state destructuring
    const { text } = this.state;
    // func destructuring
    const { onChange, onSubmit, inputRef } = this;
    return (
      <div className="App">
        <Title />
        <Form inputText={text} inputRef={inputRef} onChange={onChange} onSubmit={onSubmit} />
      </div>
    );
  }
}

render์—์„œ ์‚ฌ์šฉํ•  state์™€ function์„ ํ•˜๋‚˜์”ฉ ๋น„๊ตฌ์กฐํ™” ํ• ๋‹น์„ ํ•ด์ฃผ์‹œ๋ฉด

์ฝ”๋“œ๋„ ๊น”๋”ํ•ด์ง€๊ณ , ์‚ฌ์šฉํ•  ์ƒํƒœ๋‚˜ ํ•จ์ˆ˜๊ฐ€ ๋ฌด์—‡์ธ์ง€ ๋ฐ”๋กœ๋ฐ”๋กœ ํ™•์ธํ•˜์‹ค ์ˆ˜ ์žˆ์–ด์š”!

์ด์ œ ํ™”๋ฉด์„ ๋ณด๋ฉด...

ํ™”๋ฉด์„ ๋ณด์ž๋งˆ์ž Input ํฌ์ปค์‹ฑ์ด ๋“ค์–ด๊ฐ€๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค! ๐Ÿ‘๐Ÿป


5. TodoList & TodoListItem

์ด์ œ ํ•  ์ผ ๋ชฉ๋ก์„ ๋งŒ๋“ค์–ด ๋ณผํ…๋ฐ์š”,

๋ชฉ๋ก๊ณผ ๋ชฉ๋ก์— ๋“ค์–ด๊ฐˆ ์•„์ดํ…œ์„ ๋ถ„๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋ž˜์•ผ ๋ชฉ๋ก์— ๊ฐ ํ•  ์ผ๋“ค์„ ๋‹ด์•„์„œ ํ‘œํ˜„ํ•ด์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์•ˆ ํ•ด์ค˜๋„ ๋˜์ง€๋งŒ, ๋‚˜์ค‘์— ์ถ”๊ฐ€ ๊ธฐ๋Šฅ ๊ตฌํ˜„์—์„œ ํŽธ๋ฆฌํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

๋จผ์ €, App.js์—์„œ ์ƒํƒœ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ ๋ฐ์ดํ„ฐ๋ฅผ 2๊ฐœ ์ •๋„ ๋งŒ๋“ค์–ด๋‘˜๊ฒŒ์š”.

// App.jsx
// ... ์ƒ๋žต...
this.state = {
      id: 3,
      text: '',
      checked: false,
      todoList: [{ id: 1, text: '๊ฐ์ž ์‚ถ์•„๋จน๊ธฐ', checked: true }, { id: 2, text: '์˜ท ์‚ฌ๋Ÿฌ ๊ฐ€๊ธฐ', checked: false }]
    };
...

์ด์ œ TodoList.jsx, TodoListItem.jsx ๋ฅผ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.

๋งŒ๋“ค์–ด ๋‘์—ˆ๋˜ todoList๋ฐฐ์—ด์„ ์ˆœํšŒํ•˜๋ฉด์„œ TodoListItem์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

TodoListItem์—๋Š” ์„ ํƒ ๋ฒ„ํŠผ, ํ…์ŠคํŠธ, ์‚ญ์ œ ๋ฒ„ํŠผ์„ ๋ฏธ๋ฆฌ ์ƒ์„ฑํ•ด ๋‘ก๋‹ˆ๋‹ค.

className์— checked๋ฅผ ์‚ผํ•ญ ์—ฐ์‚ฐ์ž๋กœ ๊ฒ€์‚ฌํ•˜์—ฌ ์ฒดํฌ ๋™์ž‘์„ ๊ตฌํ˜„ํ•ด ์ค„๊ฒ๋‹ˆ๋‹ค.

// TodoList.jsx
const TodoList = ({ todoList }) => {
  return (
    <div className="todo-list">
      {todoList.map(list => (
        <TodoListItem key={list.id} text={list.text} checked={list.checked} />
      ))}
    </div>
  );
};

TodoList.propTypes = {
  todoList: PropTypes.array.isRequired
};
// TodoListItem.jsx
const TodoListItem = ({ text, checked }) => {
  return (
    <div className={`todo-list-item ${checked ? 'checked' : ''}`}>
      <button className="check-button"></button>
      <span className="todo-text">{text}</span>
      <button className="delete-button">โœ•</button>
    </div>
  );
};

TodoListItem.propTypes = {
  text: PropTypes.string.isRequired,
  checked: PropTypes.bool.isRequired
};

์ด์ œ App.jsx์— ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  ๊ฐ๊ฐ์˜ scssํŒŒ์ผ์„ ์ƒ์„ฑํ•˜์—ฌ ์•ฝ๊ฐ„์˜ ๋ชจ์–‘์„ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.

์•„์ง TodoList.scss๋Š” ๋ณ„๋‹ค๋ฅธ ์ˆ˜์ •์„ ํ•˜์ง€ ์•Š๊ฒ ์Šต๋‹ˆ๋‹ค.

// App.jsx
// ...์ƒ๋žต...
render() {
    // state destructuring
    const { text, todoList } = this.state;
    // func destructuring
    const { onChange, onSubmit, inputRef } = this;
    return (
      <div className="App">
        <Title />
        <Form inputText={text} inputRef={inputRef} onChange={onChange} onSubmit={onSubmit} />
        <TodoList todoList={todoList} />
      </div>
    );
  }
// TodoListItem.scss
@mixin button() {
  width: 20px;
  height: 20px;
  border: none;
  border-radius: 5px;
  transition: all 0.2s;
  outline: none;
}

.todo-list-item {
  margin: 0.3rem;
  padding: 0.5rem;
  .todo-text {
    font-size: 1.3rem;
    margin-left: 12px;
  }
  .check-button {
    @include button;

    background: rgba(0, 0, 0, 0.3);
    &:hover {
      background: rgba(0, 0, 0, 0.2);
    }
  }
  .delete-button {
    @include button;

    font-size: 1.2rem;
    font-weight: 800;
    color: rgba(255, 0, 0, 0.2);
    &:hover {
      color: red;
    }
  }
}

.checked {
  margin-left: 1.5rem;
}

๋™์ž‘์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์ „์— ๋จผ์ € ํ™”๋ฉด์„ ํ™•์ธํ•ด ๋ด…์‹œ๋‹ค.

์ด์ œ onSubmit ๋™์ž‘ ์‹œ ๋ฆฌ์ŠคํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ๋˜๊ฒ ๋„ค์š”!

profile
ggit

2๊ฐœ์˜ ๋Œ“๊ธ€

comment-user-thumbnail
2019๋…„ 8์›” 10์ผ

์ž˜ ์ฝ์—ˆ์Šต๋‹ˆ๋‹ค. ๋•๋ถ„์— husky์™€ git-hooks๋ฅผ ๊ณต๋ถ€ํ•˜๊ณ  ๊ฐ‘๋‹ˆ๋‹ค ใ…‹ใ…‹
์ƒ๊ฐํ•ด๋‘” ๋””์ž์ธ์„ Figma ๊ฐ™์€ ํ”„๋กœํ† ํƒ€์ž… ํˆด์„ ์ด์šฉํ•ด ๋งŒ๋“ค์–ด๋‘๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™์•„์š” ใ…Žใ…Ž

1๊ฐœ์˜ ๋‹ต๊ธ€