전 글에는 드롭다운의 로직을 짜봤다. 이제 만든 Props를 가지고 연결해보자.
전편에 만들었던 드롭다운 Props interface
를 다시 보자.
interface DropDownProps {
label?: string; // 제목
list: string[]; // 드롭다운 리스트
selected: string; // 선택된 항목(selected state)
dropDownPlaceHolder?: string; // 드롭다운 플레이스홀더
handleSelectedChange: (selected: string) => void; // 선택된 항목 변경 함수(selected setState 변경 함수)
description?: string; // 드롭다운 설명
disabled?: boolean; // 드롭다운 비활성화
}
export default function DropDownCommon({
label,
list,
selected,
dropDownPlaceHolder,
handleSelectedChange,
description,
disabled = false,
}: DropDownProps)
위와 같다. 이제 클릭할 때 드롭다운의 내용이 변하고, disabled
, 제목 등의 디자인을 해보자.
전에 text.tsx
에 썼던 const [selected, setSelected] = useState<string>("");
문장을 썼고 그를 Props로 내려줬다.
값을 바꾸기위해 setter인 handleSelectedChange
로 setSelected
를 받아주도록 연결하고, getter인 selected
를 값으로 연결해 받아줄 것이다.
const toggleDropdown = () => {
setIsActive(!isActive);
};
const handleSelect = (item: string) => {
handleSelectedChange(item);
setIsActive(false);
};
return (
<S.Container>
{label && <S.DropDownLabel disabled={disabled}>{label}</S.DropDownLabel>}
<S.DropdownContainer>
<S.DropdownBody
onClick={toggleDropdown}
isActive={isActive}
>
<S.DropdownSelected>
{selected || dropDownPlaceHolder || "Select an item"}
</S.DropdownSelected>
<S.DropdownSelected>
<Image
src="/auth/arrow_down.svg"
alt="arrow"
width={20}
height={20}
/>
</S.DropdownSelected>
</S.DropdownBody>
<S.DropdownMenuContainer isActive={isActive}>
{list.map((item) => (
<S.DropdownItemContainer
key={item}
onClick={() => {
handleSelect(item);
}}
>
{item}
</S.DropdownItemContainer>
))}
</S.DropdownMenuContainer>
</S.DropdownContainer>
{description && <S.Description>{description}</S.Description>}
</S.Container>
);
...
handleSelect
에 handleSelectedChange
작성handleSelectedChange
는 setter함수item
을 파라미터로 받음. item
은 DropdownMenuContainer
의 list
에서 고른 string
DropdownSelected
추가 selected
를 받아줘서 있으면 렌더링selected
가 없다면 Placeholder, 없으면 기본값 렌더링DropdownItemContainer
에 handleSelect(item);
넣음onClick
할 때 item
값을 받고 setter에 전해주기label
, description
: TextInputUI
만들 때 처럼 넣어주기이런 식으로 넣었다. 이렇게 된다면 list
의 onClick
으로 발생된 이벤트 handleSelect(item);
가 실행되고, handleSelectedChange(item);
가 실행돼서 바뀔 것이다!
처음에 이거 하는데 로직이 좀 헷갈려서 30분을 헤맸다.. 항상 상태 getter, setter를 같이 사용해주도록 생각하자.
노가다의 꽃 디자인이다. 드롭다운 디자인을 다시 보자!
disabled
이 3가지 상태가 필요하다. 그래서 disabled
, selected
, isActive
를 비교해서 디자인을 해야한다.
일단 기본적인 디자인은 아래와 같다.
const S = {
Container: styled.div`
display: flex;
flex-direction: column;
`,
DropDownLabel: styled.label<{ disabled: boolean }>`
margin-bottom: 8px;
font-size: 16px;
font-weight: 700;
color: ${({ theme, disabled }) =>
disabled ? theme.color.gray500 : theme.color.gray700};
`,
DropdownContainer: styled.div`
position: relative;
&:hover {
cursor: pointer;
}
`,
DropdownBody: styled.div`
display: flex;
justify-content: space-between;
align-items: center;
width: 452px;
height: 50px;
background-color: ${({ theme }) =>
theme.color.white};
border: 1px solid
${({ theme }) =>
theme.color.gray600};
border-radius: 8px;
`,
DropdownSelected: styled.div`
font-size: 16px;
color: ${({ theme }) =>
theme.color.gray900
};
padding: 14px 16px;
`,
DropdownMenuContainer: styled.ul<{ isActive: boolean }>`
position: absolute;
top: calc(100% + 8px);
left: 0;
z-index: 1;
width: 452px;
height: 312px;
padding: 8px 0;
background-color: ${({ theme }) => theme.color.white};
border: 1px solid ${({ theme }) => theme.color.gray600};
border-radius: 8px;
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.1);
overflow-y: auto;
display: ${(props) => (props.isActive ? "block" : "none")};
`,
DropdownItemContainer: styled.li`
display: flex;
align-items: center;
padding: 16px;
font-size: 16px;
color: ${({ theme }) => theme.color.gray900};
&:hover {
background-color: ${({ theme }) => theme.color.gray200};
cursor: pointer;
}
`,
Description: styled.div`
height: 17px;
font-size: 12px;
color: ${({ theme }) => theme.color.gray900};
flex: none;
order: 2;
align-self: stretch;
flex-grow: 0;
margin: 8px 0px;
`,
};
일단 만들면서 disabled
디자인도 같이 적용하겠다. 선택 전 선택 중 선택 후 디자인 차이를 만들어보자.
이런 식이다. 그걸 만들어보자.
DropdownBody: styled.div<{ disabled: boolean; isActive: boolean }>`
display: flex;
justify-content: space-between;
align-items: center;
width: 452px;
height: 50px;
background-color: ${({ theme, disabled }) =>
disabled ? theme.color.gray200 : theme.color.white};
border: 1px solid
${({ theme, isActive }) =>
isActive ? theme.color.gray600 : theme.color.gray200};
cursor: ${({ disabled }) => (disabled ? "not-allowed" : "pointer")};
border-radius: 8px;
`,
DropdownSelected: styled.div<{ disabled: boolean; selected: string }>`
font-size: 16px;
color: ${({ theme, disabled, selected }) =>
disabled
? theme.color.gray500
: selected
? theme.color.gray900
: theme.color.gray500};
padding: 14px 16px;
`,
isActive
로 일단 드롭다운 기본 박스인 DropdownBody
의 border를 조작했다. 그 다음 그 안 내용의 디자인을 정하는 DropdownSelected
에 selected
가 있다면 gray900
, 없다면 흐린 폰트 색깔인 gray500
를 만들어 놨다.
거기에 disabled
가 된다면 클릭하지 못한다는 UX를 주기 위해서 배경: gray200
, 폰트색: gray500
으로 설정했다. 이중 삼항연산자라 읽기가 불편하긴 한데 Styled-components Props 특성 상 어쩔 수 없다.
return (
...
<S.DropdownMenuContainer isActive={isActive}>
{list.map((item) => (
<S.DropdownItemContainer
key={item}
isSelected={item === selected}
onClick={() => {
handleSelect(item);
}}
>
{item}
</S.DropdownItemContainer>
))}
</S.DropdownMenuContainer>
...
const S = {
...
DropdownItemContainer: styled.li<{ isSelected: boolean }>`
display: flex;
align-items: center;
padding: 16px;
font-size: 16px;
color: ${({ theme }) => theme.color.gray900};
&:hover {
background-color: ${({ theme }) => theme.color.gray200};
cursor: pointer;
}
${({ isSelected, theme }) =>
isSelected &&
`
background-color: ${theme.color.gray200};
`}
`,
...
boolean
을 받는 isSelected
Props를 만들어서 item
과 selected
를 일치 연산자로 하여금 일치되는 리스트가 있는지 판단isSelected
와 &&
연산자로 true
라면 gray200
으로 배경색 적용하기gray200
으로 hover 적용이렇게 하면 선택된 항목이 있으면 드롭다운 컨테이너에 나타나게 된다! 길고 긴 드롭다운 디자인 기능 만들기 성공이다.
일단 먼저 test.tsx
을 만들어 적용해보자.
import { useState } from "react";
import { Margin } from "../components/ui";
import DropDown from "../components/ui/dropDownUI";
import styled from "styled-components";
export default function Test() {
const [selected, setSelected] = useState<string>("");
// const [isButtonDisabled, setButtonDisabled] = useState<boolean>(true);
const handleSelectedChange = (selected: string) => {
setSelected(selected);
};
const countryList = [
{
code: "KR",
nation: "대한민국",
add_code_nation: "KR 대한민국",
country: "REPUBLIC OF KOREA",
},
...
];
return (
<S.Wrapper>
<Margin size={50} direction="column" />
<div>
<Margin size={8} direction="row" />
<DropDown
label="안녕"
list={countryList.map((item) => item.country)}
selected={selected}
dropDownPlaceHolder="선택하세요"
// disabled={isButtonDisabled}
handleSelectedChange={handleSelectedChange}
description="테스트"
/>
</div>
</S.Wrapper>
);
}
disabled
적용드롭다운을 클릭할 수 없게 됐다!
짠! 잘됐다!
이건 좀 노가다성이 좀 크고 어디부터 어디까지 공통 UI로 만들어서 해야할지 고민이었다. 객체 데이터를 주로 받아서 하기 때문에 객체 자체를 받을 수 없어 어떻게 받아줄지도 고민이었다.
그래서 고민 끝에 string[]
으로 이루어진 데이터만 받게 기획했고, 그에 따라 술술 풀려갔다.
다음엔 상태의 getter와 상태의 setter를 받고 어디 배치할지 어떻게 순서가 이루어질지도 잘 생각해서 해봐야겠다. 은근 삽질을 많이 했다.
고생하셨습니다 드롭다운이 확실히 복잡하네요 ㅎㅎ