
이제 국가를 설정하는 드롭다운 메뉴를 만들 것이다. 되게 간단하다!
먼저 피그마는 아래와 같이 있었다.
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가 나오는 순서보다 변수에 담겨지는 그 과정이 느려서 저렇게 바로 아톰이 변하지 않는 것이 나오긴 한다. 근데 값이 제대로 변하긴 한다! 혼동 노!
고생하셨습니다 모바일 만드시는거 같네요 화이팅입니다!