팀프로젝트: Wingle(1.5) API 연결 - Auth: 회원가입 국가 드롭다운

윤뿔소·2023년 4월 28일
0

팀프로젝트: Wingle

목록 보기
9/16
post-thumbnail

이제 국가를 설정하는 드롭다운 메뉴를 만들 것이다. 되게 간단하다!

기능 / 디자인 기획

먼저 피그마는 아래와 같이 있었다.

  1. 나라 리스트를 드롭다운에 넣을 수 있게 배열 하나 만들기
  2. 초깃값을 Republic of Korea으로
  3. 선택하면 선택된 값을 리코일 아톰에 넣기

이런 식으로 가면 될 것이다. 사실 노가다가 좀 많긴 하다.

드롭다운 UI 만들기

먼저 기본적인 형태를 만들어보자. 여기서 내장 드롭다운 태그인 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;
  `,
};
  1. isActive를 관리하는 핸들러, onActiveToggle를 클릭이벤트로 넣음
  2. onSelectItem 핸들러를 만들어 setItem이 되게 만듦, 클릭하면 닫히게
  3. 선택값을 관리하는 item 상태에 초깃값을 설정
  4. DropdownMenuisActive를 props로 받아 block, none으로 관리 및 position 값을 조정

대략적인 디자인은 위와 같다. 이렇게 하면 거의 끝이다. 이제 리코일 아톰에 닫는 함수까지 넣어보자.

리코일 아톰 넣기

큰 수정점은 DropdownMenuonClick하면 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가 나오는 순서보다 변수에 담겨지는 그 과정이 느려서 저렇게 바로 아톰이 변하지 않는 것이 나오긴 한다. 근데 값이 제대로 변하긴 한다! 혼동 노!

profile
코뿔소처럼 저돌적으로

8개의 댓글

comment-user-thumbnail
2023년 4월 28일

고생하셨습니다 모바일 만드시는거 같네요 화이팅입니다!

1개의 답글
comment-user-thumbnail
2023년 4월 30일

데이터가 점점 어마어마하게 불어나네요 ㅋㅋㅋ ㅠ

답글 달기
comment-user-thumbnail
2023년 4월 30일

저는 드롭다운 select로 했는데 디자인이 별로라 .. 이 방법을 한번 시도해봐야겠어요 껄껄 훔쳐가기~~!!

1개의 답글
comment-user-thumbnail
2023년 4월 30일

드롭다운 ui 까지 직접 만드시는군요 👍👍

1개의 답글
comment-user-thumbnail
2023년 5월 1일

하나씩 완성되가는 모습이군요!!

답글 달기