디자인 시스템을 만드는데 가장 고려했던 점은
그래서 처음에는 emotion 을 선택했다
토큰을 사용하기 위해서는 시스템에서 토큰 기능을 지원해야 한다
또한 토큰에 저장된 값을 확인하기 위해서 피그마나 설정 파일을 왔다갔다 할 일은 없어야 한다.
이를 위해서는 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파일에 토큰 등록
또한 시멘틱 토큰을 지원해서 시스템을 내에서 토큰의 계층 구조를 구현할 수 있다
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;
이런식으로 스타일을 관리하게 되면
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 를 통해 적용하면 된다