React + styled-components 재사용 Toggle 버튼
이슈
현재 개발에 참여 중인 MATATA 서비스의 프론트엔드 업무 중 새로운 요구사항을 전달받았다.
디자인 변경으로 인해, 현재 기본 checkbox input을 애니메이션이 추가된 Toggle 버튼으로 바꿔야 했다.
디자인/기획 담당이 원하는 토글 버튼 디자인의 예시로 보내준 링크를 보니, 다음과 같았다.
예시가 많아 기획자와 상담 후 요구사항을 정리해보니, 다음과 같았다.
- 현재 원형 체크박스 형태인 토글버튼을 타원형(pill)으로 생성
- 선택 위치를 나타내는 원형 오브젝트 외의 공간에 선택한 텍스트 표시
- 동작 애니메이션 추가
- 선택에 따른 색상 변경
- 재사용 컴포넌트로 생성
처음엔 보내준 링크의 생성된 토글 버튼이나 외부 라이브러리를 통해 약간의 수정만 할 생각이었으나, 한 번도 직접 토글 버튼을 생성해 본 경험이 없다는 점이 생각났고, 현재 프로젝트에서 사용 중인 styled-component를 jsx에 사용하는 형태를 위반하고 싶지 않아서 직접 토글 컴포넌트를 생성해보기로 결정했다.
해결 및 결과
먼저 다른 개발자들이 생성한 토글 버튼의 형태를 살펴봤다. 대부분의 경우 input 태그에 ::before, ::after, content를 통해 토글버튼을 구성한 것을 확인했다. 기본적인 구조는 다음과 같다.
- 체크 X
- ::before : 체크 안 된 상태의 텍스트를 content로 입력, 토글의 왼쪽에 위치한다.
- ::after : 빈 content를 block 형태로 생성하고 토글의 오른쪽에 위치한다.
- 체크 O(&:checked)
- ::before : 체크 된 상태의 텍스트를 content로 입력, 토글의 오른쪽에 위치한다.
- ::after : 빈 content를 block 형태로 생성, 토글의 왼쪽에 위치한다.
<CheckBox type="checkbox"/>
/* css 기본 형태 */
.input {
/*체크 안된 상태*/
::before{
content:'텍스트1';
}
::after{
content:'';
display:block;
}
/*체크된 상태*/
&:checked{
::before{
content:'텍스트2';
}
::after{
content:'';
display:block;
}
}
}
Toggle component
해당 형태를 이해한 후, Toggle.js를 생성하고 부모 컴포넌트로부터 props로 한 개의 원 색상, onChange 시 변경값을 전달할 함수, 체크된 상태와 체크 안 된 상태의 각각 2개씩 텍스트 색상, 배경 색상을 받도록 설정했다.
import React from 'react';
import styled from 'styled-components';
export default function Toggle({
left,
right,
leftColor,
rightColor,
leftBgColor,
rightBgColor,
circleColor,
setChecked,
}) {
return (
<Wrapper>
<CheckBox
left={left}
right={right}
leftColor={leftColor}
rightColor={rightColor}
leftBgColor={leftBgColor}
rightBgColor={rightBgColor}
circleColor={circleColor}
onChange={() => setChecked()}
type="checkbox"
/>
</Wrapper>
);
}
CSS Styled-component
props로 받은 색상을 styled-component에서 사용할 수 있도록 각 항목에서 설정하고, css 스타일, 애니메이션을 추가했다. 애니메이션의 경우 transition Mozilla 문서를 참고하여 ease-in-out으로 설정했다.
- props로 값을 할당할 때, content의 경우 기본적으로 값이 ' '에 감싸져 있어야 한다.
- transition의 경우 배경색, 텍스트, 원을 각각 설정해줘야 한다.
// checkbox wrapper
const Wrapper = styled.div`
justify-content: center;
align-items: center;
display: flex;
z-index: 0;
`;
const CheckBox = styled.input`
z-index: 1;
width: 5rem;
height: 2rem;
background: var(--second);
background: ${(props) => props.leftBgColor ?? 'var(--grey)'};
border-radius: 2em;
/* 선택X 텍스트 */
::before {
position: absolute;
content: '${(props) => props.left ?? 'Yes'}';
padding-left: 1em;
width: 5rem;
height: 2rem;
display: flex;
justify-content: flex-start;
align-items: center;
color: ${(props) => props.leftColor ?? 'var(--white)'};
font-weight: var(--bold);
font-size: var(--small);
/* 텍스트 트랜지션 */
transition: all 0.2s ease-in-out;
}
/* 선택X 원 */
::after {
position: relative;
content: '';
display: block;
width: 1.6em;
height: 1.6em;
top: calc((2rem - 1.6em) / 2);
left: calc(5rem - 1.9em);
border-radius: 50%;
background: ${(props) => props.circleColor ?? 'var(--white)'};
/* 원 이동 트랜지션 */
transition: all 0.2s ease-in-out;
}
&:checked {
background: ${(props) => props.rightBgColor ?? 'var(--black)'};
/* 배경색 변경 트랜지션 */
transition: all 0.2s ease-in-out;
/* 선택 O 텍스트 */
::before {
position: absolute;
padding-right: 1em;
content: '${(props) => props.right ?? 'No'}';
align-items: center;
justify-content: flex-end;
color: ${(props) => props.rightColor ?? 'var(--white)'};
}
/* 선택 O 원 */
::after {
content: '';
z-index: 2;
top: calc((2rem - 1.6em) / 2);
left: calc((2rem - 1.6em) / 2);
width: 1.6em;
height: 1.6em;
display: block;
border-radius: 50%;
background: ${(props) => props.circleColor ?? 'var(--white)'};
position: relative;
}
}
`;
회고
단순히 외부 라이브러리 / UI 프레임워크를 사용하여 코드 복사 & 붙여넣기를 하는 것보다 훨씬 많은 것을 배우고 경험할 수 있었다. ::before와 ::after 태그에 대한 더 깊은 이해와 styled-component의 활용에 대해 더 자세한 이해를 했다.
See the Pen React+styled-component Toggle by Yang Chan Woo (@oizys18) on CodePen.