React, Typescript: 사이드바 외부 클릭 시 숨기기, 아코디언 접기

hotbreakb·2022년 5월 21일
2
post-custom-banner

청년을 구해줘!에서 메인 페이지 컴포넌트를 만들고 있다. 여기에 사이드바가 들어간다.

지금 만들어둔 건 최적화를 하지 않은 상태라서 번쩍번쩍 불이 자주 난다. 오늘 저녁에 할 작업 중 하나이다 (행복하다!)

사이드바 분석

처음에 바닐라 JS에서 리액트로 넘어올 때 어떤 걸 컴포넌트로 만들어야 할지 고민이 많았다. 내가 보기엔 그냥 다 한 덩어리 같았다. 나와 같은 고충을 겪고 있을 사람들을 위해 하나를 만들기 위해 작게 세분화한 방법을 적어보겠다.

사이드바를 보았을 때 든 생각이다.

  1. 한 줄씩 되어있네.
  2. "좋아요, 알림 설정, 고객센터"는 같은 색에 굵기도 같네. 오? 로그아웃, 연동해제끼리 비슷해.
  3. 버튼이 있는 것도 있고 없는 것도 있네.
  4. 아래쪽에 선이 있기도 하고 없기도 하네.
  5. "늘 행운을 빌어요! :)"는 버튼이 아닌데?

이것을 기반으로 컴포넌트를 기획하였다.

  1. 한 줄씩 컴포넌트를 만들어서 조합한다.
  2. 작은 컴포넌트 안에는 폰트 색상 color, 굵기 font-weight, 크기 font-size, 버튼 방향, 밑줄 굵기 등을 props로 받는다.

OptionItem

한 줄을 OptionItem이라고 이름을 지었다. OptionItem을 모으면 OptionList가 된다.

props

const OptionItem = ({
  children,
  fontSize = '1rem',
  fontWeight = 'regular',
  underlineHeight = '0',
  direction = null,
  disabled = false,
  isGetReady = false,
}: OptionItemProps) => {

props 하나씩 설명해보자면,

  • children: OptionItem 사이에 들어간다.
  • underlineHeight: 밑줄의 굵기이다. underlineWidth로 단어를 바꾸어도 괜찮을 거 같다.
  • direction: 버튼의 방향이다. 여기서는 위, 아래, 오른쪽이 있다.
  • disabled: 버튼이 눌리지 않도록 설정할 수 있다.
  • isGetReady: 화면에 나타나 있지만 실제로 사용할 수 없는 서비스를 "준비 중"이라고 표시하기 위해 만들었다.

화살표 위치 지정

생각해낸 방식은 2가지이다.

<button>
  // span
  // button image
</button>
  1. display: flex
    • button: flex, space-between
  2. position
    • button: relative
    • button image: absolute

둘 다 될 거 같은데, input 만들 때 relative 사용했던 기억이 있어서 복습하고자 하는 마음에 2번을 선택했다.

코드

OptionList

OptionItem을 모아서 OptionList를 만든다.

  return (
    <StyledOptionList ref={sidebarRef} isOpen={isOpen}>
      <StyledSidebarHeader>
        <StyledCloseContainer onClick={() => onSidebarOpen(false)}>
          <Close />
        </StyledCloseContainer>
        청년을 구해줘!
      </StyledSidebarHeader>
      {children} -------------->>> 여기에 OptionItem들이 모여있다.
    </StyledOptionList>
  );

외부 영역 클릭 시 닫힘

ref를 사용하면 편하다. 화면에 click EventListener를 단다. 화면에 클릭 이벤트가 발생되었을 때 sidebarRef의 바깥쪽이 클릭 되면 return onSidebarOpen(false);를 한다.

const OptionList = ({ children, onSidebarOpen, isOpen }: OptionListProps) => {
  const sidebarRef = useRef<HTMLElement>(null);

  const onClickOutside = (event: Event) => {
    if (!sidebarRef.current?.contains(event.target as Node)) {
      return onSidebarOpen(false);
    }
  };

  useEffect(() => {
    document.addEventListener('click', onClickOutside, true);
    return () => {
      document.removeEventListener('click', onClickOutside, true);
    };
  });

코드

디자인에 있는 option을 Array로 저장한 후에 OptionItem의 props로 전달하여 보여주면 된다.

return (
    <OptionList onSidebarOpen={onSidebarOpen} isOpen={isOpen}>
      {userSetting.map((option) => (
        <Link key={`${option.name}-${option.link}`} to={option.link}>
          <OptionItem
            fontSize={option.fontSize}
            fontWeight={option.fontWeight}
            underlineHeight={option.underlineHeight}
            direction={option.direction}
            disabled={option.disabled}
            isGetReady={option.isGetReady}
          >
            {option.name}
          </OptionItem>
        </Link>
      ))}

아코디언 접기

맨 윗쪽에 있는 컴포넌트가 눌렸을 때 하단에 있는 컴포넌트를 안 보이게 해주면 된다.

{titleClicked && (
        <div>
          {tails.map((tail) => (
            <Link key={`${tail.name}-${tail.link}`} to={tail.link}>
              <OptionItem
                fontSize={tail.fontSize}
                fontWeight={tail.fontWeight}
                underlineHeight={tail.underlineHeight}

참고 자료

profile
글쟁이 프론트 개발자, 헬렌입니다.
post-custom-banner

0개의 댓글