Front-end/web design

특정 props의 유무에 따라 디자인이 달라지도록 컴포넌트 작성하기

kjyook 2023. 5. 31. 15:39
728x90

 우리의 프로젝트를 진행하면서 자주 사용할 UI들은 컴포넌트를 만들어서 여러 페이지에서 사용하려고 했다. 하지만 아래의 이미지의 컴포넌트를 하나의 컴포넌트로 관리하자는 말이 있어서, 이를 어떻게 설계해야 할지 고민해야 했다.

이 3개의 컴포넌트를 하나의 컴포넌트로 구현하고 싶었다.

 위 컴포넌트는 ListItem이라는 컴포넌트였고, 이 컴포넌트에는 Icon, Title, Description이라는 값이 들어갈 수 있다. Title은 반드시 들어가야 하는 값은 Title 값이고, Description과 Icon 값은 존재할 수도 존재하지 않을 수도 있다.

 

처음에는 Icon과 Description의 유무에 따라 해당 컴포넌트의 Wrapper를 따로 작성하였다. 이렇게 코드를 작성하니 Wrapper를 총 3개 작성해야 했고, 앞으로 다른 경우의 수가 생긴다면 최대 8개의 코드를 작성하는 일이 생길 뻔했다. 그래서 같이 작업을 하는 팀원 분의 조언으로 하나의 Wrapper를 통해서 해당 컴포넌트를 styling 해보기로 하였고, 그래서 아래와 같은 코드를 작성하였다.

 

//ListItem.tsx
import React from 'react';
import * as S from './ListItem.styles';
import { SVGIcon, ViewBoxSize } from '@/components/UI/SVGIcon';
import { IconRegistryKey } from '@/components/UI/SVGIcon/SVGIcon.registry';

export type ListItemTitle = string;
export type ListItemDescription = string;

export type ListItemProps = {
  title: ListItemTitle;
  description?: ListItemDescription;
  itemSize?: { width?: string; height?: string };
  icon?: {
    name: IconRegistryKey;
    width: string | number;
    height: string | number;
    viewBox?: ViewBoxSize;
  };
  onClick?: (...args: any) => void;
};

export const ListItem = ({ title, description, icon, onClick, itemSize }: ListItemProps) => {
  const IconRender = !!icon
    ? () => (
        <SVGIcon
          name={icon.name}
          height={icon.height}
          width={icon.width}
          viewBox={icon?.viewBox ?? '0 0 50 50'}
        />
      )
    : null;

  return (
    <S.ListItemRoot onClick={onClick} itemSize={itemSize}>
      <S.ListItemWrapper flex={'rowStart'}>
        {IconRender && (
          <S.IconContainer flex={'rowCenter'}>
            <IconRender />
          </S.IconContainer>
        )}
        <S.TextContainer flex={'columnCenter'}>
          <h3>{title}</h3>
          {description && <h4>{description}</h4>}
        </S.TextContainer>
      </S.ListItemWrapper>
    </S.ListItemRoot>
  );
};

export default ListItem;
//ListItem.style.ts
import styled from '@emotion/styled';
import { Flex } from '@/components/UI/FlexBox';

export const ListItemRoot = styled.li<{ itemSize?: { width?: string; height?: string } }>`
  width: 100%;
  min-height: 8.6rem;
  padding: 0 2.2rem 0 2.2rem;
  box-sizing: border-box;
  list-style: none;
  ${({ itemSize }) => {
    const totalWidth = itemSize?.width ? itemSize?.width : '100%';
    const totalHeight = itemSize?.height ? itemSize?.height : '8.6rem';
    const minHeight = itemSize?.height ? '0px' : '8.6rem';
    return `
      width: ${totalWidth};
      height: ${totalHeight};
      min-height:${minHeight};
    `;
  }}
`;

export const ListItemWrapper = styled(Flex)`
  height: 100%;
  width: 100%;
  background: ${({ theme }) => theme.color.background.default};

  h3 {
    font-weight: 600;
    width: 100%;
    font-size: ${({ theme }) => theme.size.font.md};
    line-height: 1.8rem;
    text-align: start;
    color: ${({ theme }) => theme.color.text.g2};
  }

  h4 {
    font-weight: 400;
    width: 100%;
    font-size: ${({ theme }) => theme.size.font.xs};
    line-height: 1.6rem;
    text-align: start;
    color: ${({ theme }) => theme.color.text.g3};
  }
`;

export const IconContainer = styled(Flex)`
  height: 100%;
  margin-right: 1.8rem;
`;

export const TextContainer = styled(Flex)`
  gap: 0.6rem;
  width: 100%;
  height: 100%;
`;

 

현재 이 ListItem이 사용되는 곳에서는 Title이 필수로 사용되기 때문에 위와 같이 코드를 작성하였지만, Title이 사용되지 않는 경우가 생긴다면 해당 부분만 수정하면 될 것이다. ( 아마 한 줄 정도...? ㅎㅎ ) 이 전처럼 새로운 Wrapper를 작성하지 않아도 원하는 화면을 구성할 수 있다! 또한 예전에는 하면 위치를 조정하기 위해 px단위를 사용하였지만 값의 유무에 따라 해당 props들이 있어야 할 위치들이 조금씩 달라지는데, 이를 하나하나 지정해 주기가 힘드므로 rem을 통하여 구현하였더니 이쁘게 위치를 찾아갔다!!!

 

rem과 px의 차이, 그리고 rem의 장점은 밑의 블로그를 보고 알 수 있었다.

https://yozm.wishket.com/magazine/detail/1410/

 

웹 디자이너가 PX대신 REM을 사용해야 하는 이유 | 요즘IT

스케치(Sketch)와 피그마(Figma)[1]에서 큰 고민 없이 픽셀(Pixel, 줄여서 px)을 사용하고 있나요? 저 역시 한때 그랬습니다. 제가 팀에 처음 합류했을 때 모두가 당연한 듯 픽셀을 사용하고 있었습니다.

yozm.wishket.com

 

그래서 나는 위 디자인과 px단위까지 같은 화면은 아닐 수 있지만, 우리의 눈에 똑같아 보이고 여러 화면에서 지원이 가능한 UI를 하나의 컴포넌트로, 또 하나의 Wrapper만을 사용하여 구현할 수 있었다 ^~^.

728x90