디자인 시스템 만들기 - emotion에서 pandacss로 변경한 이유

jh·2024년 6월 8일

디자인 시스템

목록 보기
4/14

디자인 시스템을 만드는데 가장 고려했던 점은

  • 유지보수가 용이해야 한다
    변경이 굉장히 많을 것으로 예상되기 때문에 손쉽게 변경이 가능해야 한다
  • 가독성이 좋아야 한다
    단순히 코드의 가독성 뿐 아니라, 누구나 이해하기 쉬워야지 쓰임새가 좋을 것 같다
  • TS + IDE 자동완성이 지원되어야 한다

그래서 처음에는 emotion 을 선택했다

  • theme, global 등을 사용하여 테마 설정 가능
  • 타입 지정을 통해 IDE 자동완성 가능

토큰 시스템

토큰을 사용하기 위해서는 시스템에서 토큰 기능을 지원해야 한다
또한 토큰에 저장된 값을 확인하기 위해서 피그마나 설정 파일을 왔다갔다 할 일은 없어야 한다.
이를 위해서는 IDE 자동완성, 즉 타입 지원이 되어야 한다

emotion에서 토큰 시스템을 사용하려면
1. emotion의 theme에 토큰을 등록
2. 타입을 따로 설정해서 global module에 등록

import "@emotion/react";
import theme, { PaletteType, CommonType } from "./token";

declare module "@emotion/react" {
  export interface Theme {
    palette: PaletteType;
    common: CommonType;
  }
}

pandacss의 경우에는
1. panda.config.ts파일에 토큰 등록

  • 끝이다

panda token

또한 시멘틱 토큰을 지원해서 시스템을 내에서 토큰의 계층 구조를 구현할 수 있다

import { defineConfig } from '@pandacss/dev'
 
export default defineConfig({
  theme: {
    tokens: {
      colors: {
        red: { value: '#EE0F0F' },
        green: { value: '#0FEE0F' }
      }
    },
    semanticTokens: {
      colors: {
        danger: { value: '{colors.red}' },
        success: { value: '{colors.green}' }
      }
    }
  }

이외에도 텍스트 스타일, 레이어 스타일 등도 토큰으로 설정할 수 있다

import { defineTextStyles } from '@pandacss/dev'
 
export const textStyles = defineTextStyles({
  body: {
    description: 'The body text style - used in paragraphs',
    value: {
      fontFamily: 'Inter',
      fontWeight: '500',
      fontSize: '16px',
      lineHeight: '24',
      letterSpacing: '0',
      textDecoration: 'None',
      textTransform: 'None'
    }
  }
})

컴포넌트 스타일 적용

컴포넌트의 스타일 속성(width, height, color 등)을 개별적으로 지정하는 대신, 미리 규칙을 정한 뒤 유한한 속성 사이에서 관리하게 되면 더욱 일관적인 스타일을 적용하기 쉽다

Ex)
버튼의 height는 44px,48px 2가지
버튼의 스타일은 1. background color가 있고, borderColor가 없는 버전 / 2. backgroundColor가 없고 borderColor만 있는 버전 2가지

이렇게 선택지를 몇가지로 한정해 버린 뒤에 -> 코드에서 쉽게 사용하기 위해서 약간의 추상화를 더해주면 된다

Ex)
버튼의 height는 44px,48px로 고정 -> 이를 size: 'medium' | 'small' 로 지칭
버튼의 스타일은 background color가 있고, borderColor가 없는 버전 / backgroundColor가 없고 borderColor만 있는 버전 - 이를 style : 'cta' | 'text'

이를 emotion에서 구현한다면

/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react';

const buttonSizes = {
  small: css`
    height: 44px;
  `,
  medium: css`
    height: 48px;
  `,
};

type size = keyof typeof buttonSizes

const buttonStyles = {
  cta: css`
    background-color: #007bff;
    border: none;
    color: white;
  `,
  text: css`
    background-color: transparent;
    border: 1px solid #007bff;
    color: #007bff;
  `,
};

type style = keyof typeof buttonStyles

const Button = ({ size = 'medium', style = 'cta'} : {size: Size; style: Style}) => {
  return (
    <button
      css={[
        buttonSizes[size],
        buttonStyles[style],//상황별로 다르게 사용할 수 있는 스타일
        css`
          padding: 0 16px; // 버튼의 고정된 스타일
          font-size: 16px;
          border-radius: 4px;
          cursor: pointer;
          &:hover {
            opacity: 0.8;
          }
        `,
      ]}
      {...props}
    >
      {children}
    </button>
  );
};

export default Button;

이런식으로 스타일을 관리하게 되면

  • 서비스에서 사용되는 모든 버튼의 스타일의 경우의 수는 총 4가지로 한정된다 - UI의 일관성이 지켜짐
  • 스타일의 변경이 필요할 경우 variant 부분만 변경하면 일괄적으로 변경되기 때문에 유지보수하기 편하다

emotion의 경우
객체를 통해 variant 별 속성을 정의하고,
keyof를 사용해서 해당 객체에서 타입을 뽑아온 뒤, 이를 props type에 적용한다

그 다음에는 emotion의 css prop을 사용 or styled를 사용해서 buttonSizes[size] 이런 식으로 실제 스타일을 적용해주면 된다

또한 이렇게 동적으로 style을 지정하는 것이 성능상의 이슈가 존재한다

pandacss에서는 recipe API를 이용하면 variant 설정과 타입 지정을 한번에 해결할 수 있다

import { defineRecipe } from '@pandacss/dev'
 
export const buttonRecipe = defineRecipe({
  className: 'button',
  description: 'The styles for the Button component',
  base: {
    display: 'flex'
  },
  variants: {
    visual: {
      funky: { bg: 'red.200', color: 'white' },
      edgy: { border: '1px solid {colors.red.500}' }
    }
  },
  defaultVariants: {
    visual: 'funky',
    size: 'sm'
  }
})

defipeRecipe(이외에도 여러 api 존재)를 사용하면
빌드 타임에 variant를 적용할 수 있는 함수와, variant 타입이 자동으로 생성된다

import React from 'react'
import { button, type ButtonVariants } from '../styled-system/recipes'
 
type ButtonProps = ButtonVariants & {
  children: React.ReactNode
}
 
const Button = (props: ButtonProps) => {
  const { children, size, visual } = props
  return (
    <button {...props} className={button({ size,visual })}>
      {children}
    </button>
  )
}

스타일은 button을 통해 적용하면 되고, variant 별 타입은 ButtonVariants 를 통해 적용하면 된다

  • button은 직접적으로 스타일시트를 생성하는 게 아니라, 이미 생성된 스타일을 적용할 수 있는 함수

0개의 댓글