Text 디자인 시스템🎨 구축하기
Text 컴포넌트를 만들어서 텍스트 스타일 통일하기🍀
Table Of Contents
텍스트가 너무 많아!
팀에 합류하고 Figma 링크를 전달받았다! 디자인 시스템부터 살펴보는데 굉장히 많은 텍스트 스타일이 있었다🤯
이걸 다... 사용하게 되나요?!
CSPG 프로젝트는 스타일링 라이브러리로 tailwind를 쓰는데, 나는 tailwind로 어느 정도 큰 프로젝트를 해본 적이 없어서 어느 정도 불안함이 있었다.
다행히도 디자인 시스템은 다른 개발자분께서 미리 세팅해 주셨다고 한다...! 그렇게 코드를 열었는데 개발자분의 깊은 고민이 담긴 코드를 볼 수 있었다🥲
미니맵 낑기는거보세요,,,
tailwind.config.ts
파일을 이용해서 모든 사이즈 / 두께에 대해서 스타일을 미리 정의해놓은 모습이다.
이렇게 텍스트 스타일을 미리 정의해 놓으면
<h1 className="title-2-regular">h1입니다!</h1>
처럼 글자에 스타일을 한 번에 줄 수 있기 때문에 편리하다.
하지만 중복되는 코드가 너무 많고, 무엇보다 텍스트 관련 스타일만 200줄에 달하기 때문에 뭔가 아쉽다는 느낌을 받았다🤔
중복되는 코드 줄이기
tailwind에서 제공하는 기능을 이용해 코드 중복을 줄일 수 있을 지 여러 방면으로 고민해봤는데, 내 결론은 모듈화를 이용하자! 였다.
tailwind의 기능으로는 만족스러운 코드를 작성할 수 없었고, 결국 나는 React를 사용하고 있으니 React의 컴포넌트를 활용하기로 한 것이다.
1. 컴포넌트 구상하기
나는 컴포넌트의 세부 구현보다는 어떻게 불러와서 사용할 것인가를 먼저 생각해봤다.
다시 한 번 우리 디자인 시스템을 보자.
텍스트는 4가지 타입(Title, Heading, Body, Caption)으로 구성되어 있고, 그 안에 1, 2, 3같이 사이즈가 변화한다. 그리고 또 그 안에서 weight가 변화하는 구조다.
따라서
// Title 3 extrabold <Title level={3} weight="extrabold">이건 Title 3 extrabold입니다!</Title> // Heading 1 normal <Heading level={1} weight="normal">이건 Heading 1입니다!</Heading> // Body 5 medium <Body level={5} weight="medium">이건 Body 5 medium입니다!</Body> // Caption bold <Caption weight="bold">이건 Caption bold입니다!</Caption>
처럼 사용할 수 있는 컴포넌트를 만들기로 했다.
단, Title
, Body
같은 컴포넌트명은 다른 곳에서도 흔하게 사용하는 이름이다.
따라서 Text라는 컴포넌트로 한 번 감싸준 다음에 그 안에서 불러오는 식으로 사용하는 방향으로 잡았다.
실제 사용 시에는 아래처럼 불러올 것이다.
import Text from "경로"; // Title 3 extrabold <Text.Title level={3} weight="extrabold">이건 Title 3 extrabold입니다!</Text.Title> // Heading 1 normal <Text.Heading level={1} weight="normal">이건 Heading 1입니다!</Text.Heading> // Body 5 medium <Text.Body level={5} weight="medium">이건 Body 5 medium입니다!</Text.Body> // Caption bold <Text.Caption weight="bold">이건 Caption bold입니다!</Text.Caption>
이렇게 사용하면 여기에서 사용하는 컴포넌트가 Title이라는 영역이 아니라 Text 중에서도 Title임을 보기 쉽게 알려줄 수 있을 것 같았다.
2. Title 구현하기
우선 Title이 가장 먼저 있으니까 Title부터 시작하자
- 잠시, 그 전에 Text 컴포넌트는 어디서든 사용할 수 있어야 하기 때문에
src/components/atoms/Text
경로에 생성하기로 했다.
2-1. 타입 정의
Title
은 1, 2, 3으로 나뉜다. 따라서
- level에는
1 | 2 | 3
만 들어올 수 있도록 하고, - weight도 가능한 경우의 수
"black" | "bold" | "medium" | "normal"
만 가능하도록 타입을 작성한다.
type TitleProps = { level?: 1 | 2 | 3; weight?: "black" | "bold" | "medium" | "normal"; children: ReactNode; };
<Text.Title level={3} weight="extrabold"> 이건 Title 3 extrabold입니다! // 이 부분이 children이다. </Text.Title>
처럼 호출하므로 내용은 children
으로 받아서 그대로 렌더링할 예정이다.
default props는 가장 먼저 오는 값들로 지정해준다.
2-2.적절한 태그 선택하기
이제 이 컴포넌트를 렌더링해야 하는데, Title
컴포넌트는 Body
와는 달리 p
태그가 아니라 h1
~ h6
태그를 이용하는 게 HTML의 구조상 더 좋을 것 같았다.
마침 레벨도 1~3으로 나뉘어 있어서 h1
~ h3
까지의 태그를 이용해 렌더링했다.
const TagName = `h${level}` as keyof JSX.IntrinsicElements;
처럼 어떤 태그를 사용할 지 계산하고,
<TagName className={`font-suit font-${weight}`}>{children}</TagName>
처럼 불러오면 h1
~ h3
태그로 렌더링된다.
Title
은 모두 SUIT 폰트를 사용하기 때문에 font 스타일도 적용해줬다.
지금까지의 코드는 아래와 같다.
type TitleProps = { level?: 1 | 2 | 3; weight?: "black" | "bold" | "medium" | "normal"; children: ReactNode; }; function Title({ level = 1, weight = "black", children }: TitleProps) { const TagName = `h${level}` as keyof JSX.IntrinsicElements; return <TagName className={`font-suit font-${weight}`}>{children}</TagName>; }
2-3. 스타일링
이제 텍스트의 레벨에 따라서 상세 스타일을 적용해야 하는데, 이 부분은 object를 이용해서 처리했다.
// src/components/ataoms/Text/Title.tsx const style = { "1": "text-[32px] leading-[44px] tracking-[-0.96%]", "2": "text-[28px] leading-[40px] tracking-[-0.84%]", "3": "text-[24px] leading-[36px] tracking-[-0.72%]", } as const; type TitleProps = { level?: 1 | 2 | 3; weight?: "black" | "bold" | "medium" | "normal"; children: ReactNode; }; function Title({ level = 1, weight = "black", children }: TitleProps) { const TagName = `h${level}` as keyof JSX.IntrinsicElements; return ( <TagName className={`${style[level]} font-suit font-${weight}`}> {children} </TagName> ); }
디자인 시스템에서는 rem이 아니라 px 단위를 사용했기 때문에 text-[32px]
처럼 사용했는데, 이 부분은 나중에 수정할 방법이 있었으면 좋겠다...!
2-4. Text 컴포넌트로 감싸 내보내기
위에서 언급했듯이, Title
컴포넌트는 단독으로 쓰기보다는 Text
에서 불러와 쓰는 것을 권장한다.
Text
컴포넌트를 만들고, Title
을 감싸서 내보내자.
// src/components/ataoms/Text/index.tsx import Title from "./Title"; const Text = { Title, }; export default Text;
이제 Title을 src/components/atoms/Text
에서 import해서 사용할 수 있다.
import Text from "@/components/atoms/Text"; <div> <Text.Title weight="bold">Stay Connected</Text.Title> <Text.Title weight="bold">with the Latest Trends and Events</Text.Title> </div>;
개발자 도구로 선택해봐도 h1
태그, font weight 등이 정상적으로 적용되어서 나온다.
3. Body 구현하기
Heading
은 Title과 크게 다르지 않으므로 똑같이 구현하면 된다.
대신 Body
는 Title, Heading과 달리 p
태그로 렌더링한다는 점이 다르다.
따라서 이렇게 구현해 주면 된다.
// src/components/ataoms/Text/Body.tsx const style = { 1: "text-[20px] leading-[30px] tracking-[-0.54%]", 2: "text-[18px] leading-[26px] tracking-[-0.54%]", 3: "text-[16px] leading-[24px] tracking-[-0.48%]", 4: "text-[14px] leading-[20px] tracking-[-0.32%]", 5: "text-[12px] leading-[18px] tracking-[-0.36%]", } as const; type BodyProps = { level?: 1 | 2 | 3 | 4 | 5; weight?: "bold" | "medium" | "normal"; children: ReactNode; }; function Body({ level = 1, weight = "bold", children }: BodyProps) { return ( <p className={`${style[level]} font-pretendard font-${weight}`}> {children} </p> ); }
가능한 level, weight의 종류에만 주의해주자.
Caption 역시 p
로 렌더링하되, level만 사라진다고 보면 된다.
4. 추가적인 스타일링 적용하기
문제가 하나 발생했다!
이렇게 Sub Text의 경우에는 글자 색이 달라질 수도 있는데, 현재로써는 글자색을 변경할 수 있는 방법이 없다.
이런 경우를 처리하기 위해, 모든 컴포넌트에 className
이라는 속성을 주어 추가적인 스타일링을 가능하도록 했다.
// src/components/ataoms/Text/Title.tsx type TitleProps = { level?: 1 | 2 | 3; weight?: "black" | "bold" | "medium" | "normal"; className?: string; children: ReactNode; }; function Title({ level = 1, weight = "black", className = "", children, }: TitleProps) { const TagName = `h${level}` as keyof JSX.IntrinsicElements; return ( <TagName className={`${style[level]} font-suit font-${weight} ${className}`} > {children} </TagName> ); }
이제 아래처럼 추가적인 스타일을 입힐 수 있다.
<Text.Heading level={5} weight="medium" className={"text-label-assistive"}> {text} </Text.Heading>
화면에도 잘 나타난다!
프로젝트 구조
최종적인 프로젝트 구조는 이렇다.
마무리
이렇게 텍스트 컴포넌트를 만듦으로써 텍스트가 사용되는 부분을 쉽게 알 수 있게 되었고, 스타일링 코드의 중복도 줄일 수 있었다고 생각한다.
혹시 더 좋은 방법이 있다면 알려주세요🥹