단순한 SelectBox 만들기 ~
요구사항 분석
필요한 컴포넌트
- SelectorBox - 선택된 값이 표시되는 요소
- OptionListBox - 옵션 리스트 팝업
- OptionItem - 옵션 리스트 내의 각각의 아이템
- CaretIcon - 옵션 리스트 팝업의 열림/닫힘을 표시하는 아이콘
구현할 동작
- SelectorBox를 누르면 OptionListBox가 열린다.
- OptionListBox에서 특정 OptionItem을 선택하면 OptionListBox가 닫히면서 SelectorBox에 선택한 값을 표시한다. (상위 데이터 변경)
- OptionListBox의 상태에 따라 CaretIcon을 바꿔준다.
- 외부 영역을 클릭했을 시에도 OptionListBox를 닫아준다.
- 마우스 hover 상태의 OptionItem은 밝은 색상의 배경색을 부여한다.
- 선택된 OptionItem은 글자색과 배경색으로 표시해준다.
구현
구조
<SelectorContainer>
<CommonSelectorBox>
{isClose ? (
<FaCaretDown />
) : (
<FaCaretUp />
)}
</CommonSelectorBox>
{isOpen && (
<CommonOptionListBox>
{options.map((option, idx) => (
<CommonOptionItem />
))}
</CommonOptionListBox>
)}
</SelectorContainer>
Props
export interface CommonSelectorProps {
item: string;
options: string[];
placeholder?: string;
onChange: (item: string) => void;
}
- item: 선택된 옵션 값
- options: 옵션 리스트
- placeholder: 선택된 옵션 값이 없을 경우 SelectorBox에 표시할 문구
- onChange: 상위에서 item 값을 바꿔주기 위한 함수
1. OptionListBox Open/Close
옵션 리스트 팝업을 열고 닫는 동작이 더욱 자연스럽도록 애니메이션을 추가해줬다.
animation: ${fadeIn} 0.3s ease forwards;
다만 OptionListBox를 열 때는 애니메이션이 정상적으로 동작하지만 닫을 때는 isOpen 변수가 변경되며 OptionListBox가 바로 언마운트되므로 애니메이션 효과가 적용되지 않는다.
그래서 isOpen 변수와 isClose 변수를 따로 분리하여 닫힘 시에도 애니메이션을 적용하고자 했다.
즉, 닫을 때 동작은
- isClose 변수가 true로 변경
- isClose 조건부로 fadeOut 애니메이션 실행
- 0.3초 이후 isOpen 변수가 false로 변경되며 OptionListBox 컴포넌트 언마운트
const handlePopup = () => {
if (isOpen) {
setIsClose(true);
setTimeout(() => {
setIsOpen(false);
}, 300);
} else {
setIsClose(false);
setIsOpen(true);
}
};
export const OptionListBox = styled.div<{ $isclose?: boolean }>`
...
animation: ${fadeIn} 0.3s ease forwards;
${({ $isclose }) =>
$isclose &&
css`
animation: ${fadeOut} 0.3s ease forwards;
`}
`;
2. 상위 state 변경
react는 단방향 데이터 흐름으로 상위의 state를 하위에서 변경하는 것이 불가능하다.
대신 상위의 함수를 하위 컴포넌트에 props로 전달해 상위의 state를 변경할 수 있다.
즉, OptionItem을 선택했을 때 props로 받은 onChange 함수에 선택한 값을 파라미터로 전달하여, 상위 컴포넌트의 onChange 함수에서 item 값을 변경한다.
<CommonOptionListBox $isclose={isClose}>
{options.map((option, idx) => (
<CommonOptionItem
key={idx}
onClick={() => handleClick(option)}
>
{option}
</CommonOptionItem>
))}
</CommonOptionListBox>
const handleClick = (item: string) => {
onChange(item); // props로 전달받은 함수 onChange
handlePopup();
};
3. CaretIcon
isClose 값에 따라 조건부로 렌더링해줬다.
{isClose ? (
<FaCaretDown className="caret-icon" />
) : (
<FaCaretUp className="caret-icon" />
)}
4. 외부 영역 클릭 시 OptionListBox 닫기
지난번 Datepicker를 구현할 때와 동일하게 진행했다.
const handleClickOutside = (e: MouseEvent) => {
if (
selectorRef.current &&
!selectorRef.current.contains(e.target as Node) &&
isOpen
) {
handlePopup();
}
};
useEffect(() => {
document.addEventListener("click", handleClickOutside);
return () => {
document.removeEventListener("click", handleClickOutside);
};
}, [selectorRef.current]);
5. 상태별 OptionItem 스타일링
export const OptionItem = styled.div<{
$disabled?: boolean;
$selected?: boolean;
$focused?: boolean;
}>`
...
${({ $disabled }) =>
$disabled &&
css`
color: #c9c9c9;
background-color: #f7f7f7;
cursor: not-allowed;
`}
${({ $selected }) =>
$selected &&
css`
background-color: ${({ theme }) => theme.teamColors.blueSoft};
color: ${({ theme }) => theme.teamColors.blueHard};
font-weight: 500;
`}
&:hover {
background-color: rgb(24, 144, 255, 0.05);
}
`;
- 비활성화된 옵션 → 선택 불가, 회색 처리
- 선택된 옵션 → 파란색 글자 + 배경색
- 마우스 hover된 옵션 → 연한 파란색 배경
결과
'React' 카테고리의 다른 글
[ React ] input 태그의 placeholder로 icon 사용하기 (0) | 2024.07.10 |
---|---|
[ React ] 검색 가능한 SelectBox 컴포넌트 구현 (0) | 2024.07.04 |
[ React ] TimeTable 시간표 컴포넌트 구현 (0) | 2024.06.24 |
[ React ] weekSelector 컴포넌트 구현 (0) | 2024.06.21 |
useRef로 영역 외 클릭 시 감지 기능 구현하기 (0) | 2024.06.19 |