치춘짱베리굿나이스

[프리온보딩] 220503 강의 메모 04 (투두 리스트 예시) 본문

프로젝트/원티드 프리온보딩

[프리온보딩] 220503 강의 메모 04 (투두 리스트 예시)

치춘 2022. 5. 9. 17:50

투두리스트 작성 예시

dribble.com

디자이너들끼리의 커뮤니티(?) 로, 사람들이 만든 웹 디자인들이 올라온다

Figma 없이 하나의 테마만 갖고 작업할 때 참고하기 좋다

다만 디자이너들이 온갖 기교를 넣어서 만들어놓은 동작이나 애니메이션이 많기 때문에 쳐낼 기능은 쳐내면서 작업하자..

ColorSlurp

화면상의 색상을 찍어 색상코드를 받아올 수 있는 프로그램 (mac용)

프로젝트 초기화 하기

.eslintrc.json

{
  "extends": ["airbnb", "airbnb/hooks", "react-app", "prettier"],
  "env": {
    "browser": true,
    "jasmine": true,
    "jest": true
  },
  "settings": {
    "import/extensions": [".js", ".jsx"],
    "import/resolver": {
      "node": {
        "extensions": [".js", ".jsx"]
      }
    },
    "react": {
      "pragma": "React",
      "version": "detect"
    }
  },
  "rules": {
    "arrow-body-style": "off",
    "semi": ["warn", "never"],
    "react/jsx-one-expression-per-line": "off",
    "react/jsx-filename-extension": [
      "error",
      { "extensions": [".js", ".jsx"] }
    ],
    "react/jsx-indent": "warn",
    "react/jsx-props-no-spreading": "off",
    "react/no-array-index-key": "warn",
    "react/require-default-props": "off",
    "react/jsx-wrap-multilines": "off",
    "react/jsx-uses-react": "off",
    "react/react-in-jsx-scope": "off",
    "import/prefer-default-export": "off",
    "import/no-unresolved": "off",
    "import/order": "off",
    "import/no-anonymous-default-export": "off",
    "import/no-extraneous-dependencies": [
      "error",
      {
        "devDependencies": [".storybook/**", "src/stories/**"]
      }
    ],
    // "max-lines": ["warn", 150],
    "no-param-reassign": ["error", { "props": false }],
    "no-use-before-define": "off",

    "no-shadow": "off",
    "no-unused-expressions": ["warn"],
    "prefer-const": ["warn"],
    "prefer-destructuring": ["error", { "object": true, "array": false }],
    "lines-between-class-members": "off",
    "jsx-a11y/click-events-have-key-events": "off",
    "jsx-a11y/label-has-associated-control": [
      "error",
      {
        "labelComponents": ["label"],
        "labelAttributes": ["htmlFor"],
        "controlComponents": ["input"]
      }
    ],
    "import/extensions": [
      "error",
      "ignorePackages",
      {
        "js": "never",
        "jsx": "never"
      }
    ]
  }
}

.prettierrc.yml

jsxSingleQuote: true
semi: false
printWidth: 120
proseWrap: never
singleQuote: true
htmlWhitespaceSensitivity: "css"
endOfLine: "lf"

.stylelintrc.json

{
  "extends": ["stylelint-config-standard-scss", "stylelint-config-recess-order"],
  "overrides": [
    {
      "files": ["**/*.scss"],
      "customSyntax": "postcss-scss"
    }
  ],
  "plugins": ["stylelint-declaration-strict-value"],
  "rules": {
    "at-rule-empty-line-before": null,
    "color-function-notation": "legacy",
    "color-hex-case": "lower",
    "color-hex-length": "long",
    "custom-property-empty-line-before": null,
    "declaration-empty-line-before": "never",
    "function-name-case": [
      "lower",
      {
        "ignoreFunctions": "/^[a-z_](|[a-zA-Z0-9]+)$/"
      }
    ],
    "keyframes-name-pattern": "^[a-z][a-zA-Z0-9_]+$",
    "max-nesting-depth": 6,
    "no-descending-specificity": null,
    "no-invalid-position-at-import-rule": null,
    "number-leading-zero": "always",
    "property-no-vendor-prefix": [
      true,
      {
        "ignoreProperties": ["appearance"]
      }
    ],
    "scale-unlimited/declaration-strict-value": [
      "color",
      {
        "ignoreValues": ["inherit", "initial"]
      }
    ],
    "scss/at-function-pattern": "^[a-z_](|[a-zA-Z0-9-]+)$",
    "scss/at-mixin-pattern": "^[a-z_](|[a-zA-Z0-9]+)$",
    "scss/dollar-variable-empty-line-before": null,
    "scss/dollar-variable-pattern": [
      "^[A-Z0-9_]+$",
      {
        "ignore": "local"
      }
    ],
    "scss/double-slash-comment-empty-line-before": null,
    "selector-max-id": 0,
    "selector-class-pattern": "^[a-z](|[a-zA-Z0-9]+)$",
    "selector-pseudo-class-no-unknown": [
      true,
      {
        "ignorePseudoClasses": ["global"]
      }
    ],
    "string-quotes": "single"
  }
}

todoList 기본 구조

<div className={styles.todoList}>
  <div className={styles.centering}>
    <h1>Hi! this is your assignment.</h1>
    <ul className={styles.tasks}>
      <p className={styles.tasksTitle}>Today&apos;s</p>
      {todoList.map((todo) => (
        <li key={`todo-${todo.id}`} className={styles.task}>
          <div className={styles.checkboxWrapper}>
            <input type='checkbox' checked={todo.done} data-id={todo.id} onChange={handleChange} />
            <CheckIcon />
          </div>
          <p className={styles.title}>{todo.title}</p>
        </li>
      ))}
    </ul>
    <button type='button' className={styles.addButton} onClick={handleAddClick} aria-label='Add button' />
  </div>
</div>

styles.todoList

투두리스트의 가장 바깥을 감싸는 태그

styles.centering

가운데 정렬을 맞추기 위한 태그

h1

최상단 타이틀

이미지상에서는 “What’s up, [사용자명]!” 이 들어간다

ul

투두가 리스트 형태로 여러 개 들어가기 때문에, ulli 태그를 사용하여 리스트로 구성한다

li

각각의 투두 컴포넌트

styles.checboxWrapper

투두 컴포넌트 왼쪽의 체크박스를 감싸는 태그

체크박스 위에 체크 아이콘이 올라가야 하므로 position: relative를 적용한 wrapper로 감싸준다

input type=’checkbox’

각각의 투두 컴포넌트 왼쪽에 위치한 체크박스

투두 완료 여부를 체크한다

CheckIcon

✔️ 체크 아이콘 (svg)

p

투두 컴포넌트의 제목 (할 일)

Pay for rent 같은 게 들어가는 부분

button

우측 하단의 투두를 추가할 수 있는 페이지가 나오는 아이콘

스타일 적용하기

개발자 도구에서 미리보기

개발자 도구의 Styles 탭에서 임의로 css 스타일을 추가하면 파일에 추가하기 전에 브라우저에서 미리 적용된 모습을 확인가능하다

매번 파일 수정하고 저장하고 미리보기 하지 말고 한번 개발자도구에 넣어보고 적용해보자

CSS에서 input type 구분하기 (타입 선택자)

input[type=checkbox] {
    //
    &:checked {
        // 체크되었을 때 선택자
    }
}

체크박스 타입의 input에만 스타일이 적용된다

체크박스가 체크되었을 때 스타일은 선택자 :checked를 이용한다

색상 지정 시 rgb() 는 레거시 코드

rgb(R G B / 투명도) // 투명도는 0 ~ 1 사이

stylelint에서 권장하지 않는 레거시 코드이니

rgba(R, G, B, 투명도) // 투명도는 0 ~ 100% 사이

rgba를 사용하자

position mixin

@use '/src/styles/mixins/position'

...
@include position.middleBox; // 부모 태그의 정중앙에 배치하는 mixin

position 속성 관련 사전 설정이 들어있는 mixin으로, 컴포넌트 겹치기 같은 스타일을 적용하고 싶을 때 사용한다

check input과 svg 겹칠 때 주의사항

position: absolute; 와 z-index를 이용하여 두 컴포넌트를 앞뒤로 겹치게 되면, 뒤에 있는 컴포넌트가 클릭이 원활히 되지 않는다 (안 겹치는 부분을 클릭해야만 이벤트가 실행됨)

svgpointer-events: none 을 적용하면 해당 svg는 클릭 이벤트를 받지 않으므로 뒤에 있는 check input이 정상적으로 클릭된다

::before, ::after 선택자를 사용한 예

.addButton {
  @include position.absolute(auto 40px 40px auto);
  width: 66px;
  height: 66px;
  background-color: colors.$BLUE;
  border-radius: 50%;
  box-shadow: 0 10px 20px 0 rgba(colors.$BLACK, 15%);
  transition: 0.2s;

  &::before,
  &::after {
    @include position.middleBox;
    width: 20px;
    height: 2px;
    content: '';
    background-color: colors.$WHITE;
  }
    ...
    &::after {
    transform: translate(-50%, -50%) rotate(90deg);
    transform-origin: center;
  }
}

addButton66*66px의 동그란 파란색 버튼으로 만들어준 후, ::before::after 선택자를 이용하여 내부 내용물인 플러스 막대기를 만들어주었다

::before, ::after 모두 css 스타일을 적용받으므로 이를 이용하여 가로세로 길이를 정하고 position Mixin을 통해 버튼의 앞에 배치하였으며, ::after 선택자의 내용물만 90도 회전시켜 + 기호처럼 보이도록 하였다

추가적인 태그 요소를 내부에 배치할 필요 없이 CSS만으로 태그 요소처럼 작성할 수 있는 것 (쩐다)

transform-origin은 요소를 회전시킬 때 중심점을 지정하는 태그이다

글자 오버플로우 막기

white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
  • white-space: nowrap
    • 요소 안의 텍스트가 요소의 너비를 넘어서버릴 경우 (overflow), 화이트스페이스 (공백, 개행, 탭 등) 를 기준으로 개행을 넣어 다음 줄로 옮길지 말지 결정하는 속성
    • nowrap은 자동으로 다음 줄로 넘어가지 않게 한다 (개행을 넣지 않는다)
  • text-overflow: ellipsis
    • 요소 안의 텍스트가 요소의 너비를 넘어서버릴 경우 (overflow), 넘어간 텍스트를 어떻게 처리할 지 결정하는 속성
    • overflow 속성과 같이 사용한다
    • ellipsis는 빠져나간 텍스트를 ... 으로 대체해준다
  • overflow: hidden
    • 요소 안의 내용물이 (텍스트이든 자식 요소든) 요소의 너비를 넘어서버릴 경우 (overflow), 넘어간 내용물의 모습을 결정하는 속성
    • hidden 속성은 요소의 너비를 넘어간 부분을 가려버린다
    • scroll 속성으로 스크롤을 지정할 수도 있다

이벤트로 data 받아오기

const [id, setID] = useState(0);

const handleChange = (e) => {
    console.log(e.currentTarget.dataset);
}

...
<input 
    type='checkbox' 
    ... 
    data-id={todo.id} 
    onChange={handleChange} 
/>

data-[데이터 key이름] 이런 식으로 props를 설정하면, 이벤트 객체의 dataset 항목으로 받아올 수 있다

<input 
    type='checkbox' 
    ... 
    data-id={todo.id} 
    data-test="aaa"
    onChange={handleChange} 
/>

예를 들어 input이 이렇게 이루어져 있다면, e.currentTarget.dataset 에서 key가 id인 값과 test인 값 2개를 받아올 수 있는 것

구조분해 할당을 통해 값을 받아와 사용하자

Comments