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 자체로 배열에 담았다. 어느 것이든 '고유한 값'을 쓰면 된다.
...
.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시간 정도 썼는데 시간이 너무 오래 걸려서 디자이너님한테 체크박스를 빼달라고 할까 생각했는데 성공했다 🥰