이전 포스팅에서 디자인 시스템을 구성하기 위해 vanilla-extract를 선택했다. 이번에는 실제로 vanilla-extract를 사용하여 디자인 시스템을 구성해보자.
우선, 내가 직접 디자인을 할 능력이 안돼서, 피그마로 제공되는 디자인을 가져와서 구성해보자.
굳이 여러개 찾아볼 필요가 있을까 싶어서, MUI에서 Preview로 제공되는 디자인을 가져와서 구성해보자.
vanilla-extract로 theme 구성하기
theme
위를 참고해서 theme을 구성해보자.
color palette를 보면 크게 아래와 같이 나뉜다.
- text : 텍스트의 기본 색상
- primary : 주요 요소(브랜드) 색상, 버튼, 링크, 강조 요소 등에 사용
- secondary : 보조 색상, 부차적인 요소, 액션에 사용
- error : 오류 색상
- warning : 경고 색상
- info : 정보성 메시지나, 알림 색상
- success : 성공적인 작업, 긍정적 상태 색상
- background : 페이지나, 컴포넌트의 배경색
- action : 사용자 인터렉션과 관련된 요소(호버, 포커스, 선택된 상태 등)의 색상
- common : 자주 사용되는 공통 색상(주로 흰색, 검정색)
인터페이스 정의
색상을 정의하는 인터페이스를 정의해보자.
export interface Color {
50?: string;
100?: string;
200?: string;
300?: string;
400?: string;
500?: string;
600?: string;
700?: string;
800?: string;
900?: string;
a700?: string;
a400?: string;
a200?: string;
a100?: string;
}
material-ui에서 제공하는 색상들은 여러가지가 있는데 이를 모두 사용할 필요는 없다.
import { Color } from "./color.types";
export const yellow: Color = {
50: "#fffde7",
100: "#fff9c4",
200: "#fff59d",
300: "#fff176",
400: "#ffee58",
500: "#ffeb3b",
600: "#fdd835",
700: "#fbc02d",
800: "#f9a825",
900: "#f57f17",
a700: "#ffd600",
a400: "#ffea00",
a200: "#ffff00",
a100: "#ffff8d",
};
...
palette 구성
다크모드에 대응하기 위해 아래와 같이 mode값을 기준으로 palette를 구성해보자.
import { blue, yellow } from "../color";
import { PaletteColor } from "../palette.types";
const light: PaletteColor = {
main: blue[500] || "",
dark: blue[600] || "",
light: blue[400] || "",
contrast: yellow[50],
};
const dark: PaletteColor = {
main: blue[300] || "",
dark: blue[400] || "",
light: blue[200] || "",
contrast: yellow[900],
};
나머지 secondary, error, warning, info, success 등의 palette도 위와 같은 방식으로 구성한다.
여기서 고민인것은 지금은 @packages/ui
라이브러리에서 기본적인 시스템을 제공하고, 이를 사용하는 애플리케이션에서 확장성을 고려해서 만들어야하지 않을까? 라는 점이다.
mui처럼 만들기에는 너무 복잡하기도하고 ... 실무에서는 어떤식으로 제공할까? 분명 다양한 프로젝트에서 사용할수있게 범용성을 고려해서 만들텐데 ... 참고할수있는 좋은 예시가 있을까?
다시 한번 생각해보자... 내가 만들고자하는 디자인 시스템은 뭘까? 라이브러리를 제공하고싶은건지, 아니면 여러 프로젝트에서 보고 참조할수있는 문서를 제공하고싶은건지?
후자라면 스토리북을 사용해서 문서로 제공하는게 좋을것이고, 전자라면 라이브러리를 제공하는게 좋을것이다.
전자라면 이전 포스팅에서 스토리북과 @packages/ui
를 나누려고 할 필요도 없다.
이런 고민을 하게된 이유는 디자인시스템을 여러 프로젝트에 일관적으로 사용할수있게 하는것이 목적이기 때문이다.
프론트엔드를 제외하고 다른 프로젝트는 디자인시스템을 사용하지 않을것이다. 따라서 디자인시스템을 여러 프로젝트에서 일관적으로 사용할수있게 하는것이 목적이라면, 라이브러리를 제공하는것이 좋을것이다.
나의 목적은 '공통 UI 컴포넌트'를 제공하는 것이다. 라이브러리로 제공하자. (코드를 짜다보니 이생각 저생각이 들어서 길을 잃었다...)
palette를 구성한 결과물은 아래와 같다.
import { action } from "./action";
import { background } from "./background";
import { error } from "./error";
import { info } from "./info";
import { primary } from "./primary";
import { secondary } from "./secondary";
import { success } from "./success";
import { text } from "./text";
import { warning } from "./warning";
export const light = {
text: text.light,
primary: primary.light,
secondary: secondary.light,
error: error.light,
warning: warning.light,
info: info.light,
success: success.light,
background: background.light,
action: action.light,
};
export const dark = {
text: text.dark,
primary: primary.dark,
secondary: secondary.dark,
error: error.dark,
warning: warning.dark,
info: info.dark,
success: success.dark,
background: background.dark,
action: action.dark,
};
typography
typography는 폰트의 크기, 두께, 간격 등을 의미한다.
typography 역시 material-ui에서 제공하는 디자인을 가져와서 구성해보자.
우선은 디자인 시스템을 구성하면서 필요한 utils를 구성해보자.
setFont는 font-family를 설정하고, remToPx와 pxToRem은 후에 반응형 디자인을 구성할 때 사용되지만 미리 구성해두자.
/**
* Set font family
*/
export const setFont = (fontName: string) => {
return `"${fontName}",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"`;
};
/**
* Convert rem to px
*/
export const remToPx = (value: string): number => {
return Math.round(parseFloat(value) * 16);
};
/**
* Convert px to rem
*/
export const pxToRem = (value: number): string => {
return `${value / 16}rem`;
};
import { pxToRem, setFont } from "../style/utills";
/**
* Font family
*/
export const defaultFont = "Pretendard";
export const primaryFont = setFont(defaultFont);
export const secondaryFont = setFont("Noto Sans KR");
/* -------------------------------------------------------------------------- */
/**
* Typography
*/
export const typography = {
fontFamily: primaryFont,
secondaryFontFamily: secondaryFont,
fontWeightLight: "300",
fontWeightRegular: "400",
fontWeightMedium: "500",
fontWeightSemiBold: "600",
fontWeightBold: "700",
h1: {
fontWeight: 800,
lineHeight: 80 / 64,
fontSize: pxToRem(40),
fontFamily: secondaryFont,
},
h2: {
fontWeight: 800,
lineHeight: 64 / 48,
fontSize: pxToRem(32),
fontFamily: secondaryFont,
},
h3: {
fontWeight: 700,
lineHeight: 1.5,
fontSize: pxToRem(24),
fontFamily: secondaryFont,
},
h4: {
fontWeight: 700,
lineHeight: 1.5,
fontSize: pxToRem(20),
},
h5: {
fontWeight: 700,
lineHeight: 1.5,
fontSize: pxToRem(18),
},
h6: {
fontWeight: 600,
lineHeight: 28 / 18,
fontSize: pxToRem(17),
},
subtitle1: {
fontWeight: 600,
lineHeight: 1.5,
fontSize: pxToRem(16),
},
subtitle2: {
fontWeight: 600,
lineHeight: 22 / 14,
fontSize: pxToRem(14),
},
body1: {
lineHeight: 1.5,
fontSize: pxToRem(16),
},
body2: {
lineHeight: 22 / 14,
fontSize: pxToRem(14),
},
caption: {
lineHeight: 1.5,
fontSize: pxToRem(12),
},
overline: {
fontWeight: 700,
lineHeight: 1.5,
fontSize: pxToRem(12),
textTransform: "uppercase",
},
button: {
fontWeight: 700,
lineHeight: 24 / 14,
fontSize: pxToRem(14),
textTransform: "unset",
},
};