이제 국가를 설정하는 드롭다운 메뉴를 만들 것이다. 되게 간단하다!
먼저 피그마는 아래와 같이 있었다.
Republic of Korea
으로이런 식으로 가면 될 것이다. 사실 노가다가 좀 많긴 하다.
먼저 기본적인 형태를 만들어보자. 여기서 내장 드롭다운 태그인 label
+ select
태그를 사용하지 않을 것이다. 왜냐하면 피그마에서 디자인한 것처럼 만들어야하는데 CSS를 적용하는데 한계가 있기 때문이다.
onClick
이벤트로 드롭다운 리스트가 보이도록 하는 div
를 만드는 핸들러를 선택된 요소가 보이는 div
에 넣어서 드롭다운 컴포넌트를 만들 것이다. 즉, 선택 요소 div
, 고를 수 있는 리스트가 보이는 div
2개가 필요한 것. 그걸 만들어보자.
interface StyledInputProps {
isActive: boolean;
}
export default function DropDown() {
const [isActive, setIsActive] = useState(false);
const [item, setItem] = useState("Republic of Korea");
const onActiveToggle = useCallback(() => {
setIsActive((prev) => !prev);
}, []);
const onSelectItem = useCallback((e: any) => {
setItem(e.target.innerText);
setIsActive((prev) => !prev);
}, []);
return (
<S.Container>
<Text.Body1 color="gray700">국적</Text.Body1>
<S.DropdownContainer>
<S.DropdownBody onClick={onActiveToggle}>
<S.Img>
<Text.Body3 color="gray900">{item}</Text.Body3>
</S.Img>
<S.Img>
<img src="/auth/arrow_down.svg" alt="arrow"></img>
</S.Img>
</S.DropdownBody>
<Margin direction="column" size={8} />
<S.DropdownMenu isActive={isActive}>
{List.map((item) => (
<S.DropdownItemContainer id="item" key={item} onClick={onSelectItem}>
<Text.Body3 color="gray900">{item}</Text.Body3>
</S.DropdownItemContainer>
))}
</S.DropdownMenu>
<Margin direction="column" size={16} />
</S.DropdownContainer>
</S.Container>
);
}
const S = {
Container: styled.div``,
DropdownContainer: styled.div`
margin-top: 8px;
&:hover {
cursor: pointer;
}
`,
DropdownBody: styled.div`
display: flex;
justify-content: space-between;
align-items: center;
width: 452px;
height: 50px;
border: 1px solid #dcdce0;
border-radius: 8px;
`,
Img: styled.div`
padding-left: 15px;
padding-right: 15px;
`,
DropdownMenu: styled.ul<StyledInputProps>`
overflow: auto;
position: absolute;
background-color: white;
display: ${(props) => (props.isActive ? `block` : `none`)};
width: 452px;
height: 208px;
border: 1px solid #dcdce0;
border-radius: 8px;
padding-top: 21px;
`,
DropdownItemContainer: styled.li`
display: flex;
align-items: center;
padding-top: 0px;
padding-bottom: 26px;
padding-left: 16px;
`,
};
isActive
를 관리하는 핸들러, onActiveToggle
를 클릭이벤트로 넣음onSelectItem
핸들러를 만들어 setItem
이 되게 만듦, 클릭하면 닫히게item
상태에 초깃값을 설정DropdownMenu
에 isActive
를 props로 받아 block
, none
으로 관리 및 position
값을 조정대략적인 디자인은 위와 같다. 이렇게 하면 거의 끝이다. 이제 리코일 아톰에 닫는 함수까지 넣어보자.
큰 수정점은 DropdownMenu
를 onClick
하면 handleSelectItem
이 실행되지 않는가? 거기에 그냥 아톰 setter 함수를 넣어주면 된다.
import { useSetRecoilState } from "recoil";
import { signUpFormDataAtom } from "@/src/atoms/auth/signUpAtoms";
interface StyledInputProps {
isActive: boolean;
}
export default function DropDown() {
const [isActive, setIsActive] = useState(false);
const [nation, setNation] = useState("Republic of Korea");
const setSignUpFormData = useSetRecoilState(signUpFormDataAtom);
const onActiveToggle = useCallback(() => {
setIsActive((prev) => !prev);
}, []);
const handleSelectItem: React.MouseEventHandler<HTMLLIElement> = useCallback(
(e) => {
const target = e.target as HTMLLIElement;
const selectedNation = target.innerText;
setNation(selectedNation);
setIsActive(false);
setSignUpFormData((prev) => ({
...prev,
nation,
}));
},
[setSignUpFormData, nation]
);
return (
<S.Container>
<Text.Body1 color="gray700">국적</Text.Body1>
<S.DropdownContainer>
<S.DropdownBody onClick={onActiveToggle}>
<S.Selected>
<S.CountryItem color="gray900">{nation}</S.CountryItem>
</S.Selected>
<S.Selected>
<Image src="/auth/arrow_down.svg" alt="arrow" width={20} height={20} />
</S.Selected>
</S.DropdownBody>
<Margin direction="column" size={8} />
<S.DropdownMenu isActive={isActive}>
{List.map((item) => (
<S.DropdownItemContainer id="item" key={item} onClick={handleSelectItem}>
<S.CountryItem color="gray900">{item}</S.CountryItem>
</S.DropdownItemContainer>
))}
</S.DropdownMenu>
<Margin direction="column" size={16} />
</S.DropdownContainer>
</S.Container>
);
}
const S = {
Container: styled.div``,
DropdownContainer: styled.div`
margin-top: 8px;
&:hover {
cursor: pointer;
}
`,
DropdownBody: styled.div`
display: flex;
justify-content: space-between;
align-items: center;
width: 452px;
height: 50px;
border: 1px solid #dcdce0;
border-radius: 8px;
cursor: pointer;
`,
Selected: styled.div`
padding-left: 15px;
padding-right: 15px;
`,
DropdownMenu: styled.ul<StyledInputProps>`
overflow: auto;
position: absolute;
background-color: white;
display: ${(props) => (props.isActive ? `block` : `none`)};
width: 452px;
height: 208px;
border: 1px solid #dcdce0;
border-radius: 8px;
padding-top: 21px;
`,
DropdownItemContainer: styled.li`
display: flex;
align-items: center;
padding-top: 0px;
padding-bottom: 26px;
padding-left: 16px;
`,
CountryItem: styled(Text.Body3)`
cursor: pointer;
`,
};
setter
를 넣었다.
거기에 큰 수정점들이 있는데, 변수 네이밍 수정과 타입, 메소드 체이닝이 2개 이상인 것들은 따로 변수로 선언 등이 있다.
이게 다다. 노다가성이 있을 뿐이지 드롭다운의 UI 특성에 대해서 이해하고 상태를 넣어 구현할 수 있다면 쉽게 할 수 있다.
완성작을 보자.
됐다! 디자인도 깔꼼히 반영된 모습이다.
참고로 log에는 값이 바꾼 모양 그대로 안나오기는 하는데 log가 나오는 순서보다 변수에 담겨지는 그 과정이 느려서 저렇게 바로 아톰이 변하지 않는 것이 나오긴 한다. 근데 값이 제대로 변하긴 한다! 혼동 노!
고생하셨습니다 모바일 만드시는거 같네요 화이팅입니다!