checkbox 디자인 커스텀 + 복수 선택 구현

Sheryl Yun·2022년 10월 31일
1
post-thumbnail

구현 상세

  1. 기본 브라우저의 checkbox가 아닌 Figma의 svg 아이콘으로 체크 모양을 커스텀했다.
  2. 체크박스를 여러 개 체크하면 각각 체크 표시가 되도록 했다. (복수 체크 가능)
  3. 체크박스와 label 텍스트가 아닌, li 컴포넌트의 빈 곳을 클릭해도 체크 표시가 되게 했다.

React + Typescript 코드

import { useState } from 'react';
import { ReactComponent as Checkbox } from 'styles/svg/checkbox.svg'; // 커스텀할 svg 파일
import { CATEGORY } from 'utils/mock';  // 카테고리 명이 포함된 배열 데이터
import styles from './index.module.scss'; // 같은 레벨의 scss 파일

export const Sidebar = () => {
  // 선택된 아이템 배열
  const [checkedItem, setCheckedItem] = useState<string[]>([]);

  /* li 클릭 시 실행 */
  const handleCheckBox = (e: React.ChangeEvent<HTMLInputElement>) => {
    const selectedItem = e.currentTarget; // 주의: target 아니고 currentTarget이다

  /* checked 여부에 따라 선택된 li 아이템 추가 또는 삭제 */
    // 특정 아이템을 체크 + 선택한 아이템 배열에 체크한 아이템이 없는 경우
    if (selectedItem.checked && !checkedItem.includes(selectedItem.id)) {
    // 선택된 아이템 배열에 선택한 아이템 추가
      setCheckedItem([...checkedItem, selectedItem.id]);
     
     // 체크 해제 + 클릭한 아이템이 선택된 아이템 배열에 있는 경우
    } else if (!selectedItem.checked && checkedItem.includes(selectedItem.id)) { 
    // 해당 아이템을 선택된 아이템 배열에서 삭제
       setCheckedItem(checkedItem.filter((name) => selectedItem.id !== name));
      );
      
     // 그 외의 경우 
    } else return;
  };

  return (
    <div className={styles.wrapper}>
      <div className={styles.title}>카테고리</div>
      
      <ul className={styles.list}>
        {CATEGORY.map(({ id, name }) => (
          <li key={id} className={styles.listItem}>
            <input // 클릭 함수가 동작하는 checkbox 인풋 (화면에는 보이지 않는다)
              id={name} // label의 htmlFor와 똑같이 맞추면 서로 연결된다
              type='checkbox'
              onChange={handleCheckBox} // checkbox 인풋을 클릭하면 실행
              
            />
            <label htmlFor={name}>
              <p> // 실제 화면에 보이는 checkbox
                <span>
                  <Checkbox /> // 커스텀할 check 아이콘 
                </span>
              </p>
              {name} // checkbox 옆의 카테고리명
            </label>
          </li>
        ))}
      </ul>
    </div>
  );
};

input 체크박스는 display: none으로 숨겨지기 때문에 실제로는 li 안에 label(p, span 태그 포함) 태그만 렌더링된다.

체크가 됐을 때의 체크박스 모습과 아닐 때의 체크박스 모습을 조절하기 위해 p 태그 안에 span 태그로 SVG 컴포넌트를 한 번 더 감싸고,

p는 체크가 안 된 체크박스를 렌더링할 때, span은 체크가 된 상태를 스타일링했다.

위 코드는 수정본이다. 기존 버전은 복수 체크만 잘 될 뿐 실제 배열에는 제대로 반영되지 않았다. 또한 이전 코드는 input의 tabIndex를 사용하여 비교하고 id와 htmlFor에 map에서 꺼낸 id를 사용했는데, 이번 코드는 CATEGORY 배열 데이터의 name이 고유해서 name 자체로 배열에 담았다. 어느 것이든 '고유한 값'을 쓰면 된다.

SCSS 코드

...

.list .listItem {
  display: flex; 
  gap: 1rem;
  cursor: pointer;

  input[type='checkbox'] { 
    display: none; // 브라우저의 기본 checkbox는 안 보이게 한다
  }

  label {
    width: 100%; 
    cursor: pointer;
  }

  /* 커스텀할 체크 박스 */
  label p {
    display: inline-block; // name을 p 옆에 올려서 가로로 배치시킨다 (flex를 주면 {name} 부분도 태그로 감싸야 함)
    vertical-align: middle; // inline-block인 태그에 사용 - flex의 'align-items: center' 효과
    
    // 체크 상태가 아닐 때의 기본 CSS
    width: 20px;
    height: 20px;
    margin-right: 8px;
    border: 1px solid #d4d4d4;
    border-radius: 4px;
    
	// 처음에는 check 아이콘 안 보이게 하기
    span {
      display: none; 
    }
  }

  /* input에 check가 되었을 때의 CSS !! */
  input[type='checkbox']:checked ~ label p {
    border: 1px solid #8ec96d; // 연두색
    background-color: #8ec96d;

    // **check 아이콘 보이게 하기
    span {
      display: flex; // flex를 주면 자동으로 block이 된다
      justify-content: center;
      align-items: center;
      width: 20px;
      height: 20px;
    }
  }
}

여기까지 체크박스 커스텀 및 복수 체크하기 성공이다 🎉
사흘 정도 고민하고 실제 구현에 6시간 정도 썼는데 시간이 너무 오래 걸려서 디자이너님한테 체크박스를 빼달라고 할까 생각했는데 성공했다 🥰

profile
영어강사, 프론트엔드 개발자를 거쳐 데이터 분석가를 준비하고 있습니다 ─ 데이터분석 블로그: https://cherylog.tistory.com/

0개의 댓글