ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 디자인 시스템을 기반으로 Storybook을 작성해보자
    Studying/React 2023. 5. 24. 09:18

    디자인 시스템(Design System)이란?

    디자인 시스템이란, 디자인 원칙, 재사용 가능한 UI 패턴/컴포넌트, 코드로 구성된 시스템을 말한다. 어떤 기업에서 추구하고자 하는 이미지/컨셉을 고려하여 설계한 하나의 디자인 원칙이라고 생각하면 된다.

     

    구글, 네이버, 카카오 등 기업마다 일관되는 디자인이 있는데, 이들 모두 디자인과 관련된 규칙이 정해져 있기 때문이다. 이렇게 전체 서비스에 일관된 디자인을 적용할 수 있도록 도와주는 협업 시스템을 디자인 시스템이라고 한다.

     

    디자인 시스템은 다음과 같은 것들을 정의한다.

     

    • 디자인 원칙
    • 스타일 가이드
    • 컴포넌트
    • 패턴
    • 디자인 및 개발 프로세스에 대한 지침

     

     

    디자인 시스템이 구축된 후 개발자는

    • 디자인 시스템에 포함 된 디자인 토큰(JSON, YAML 등)을 통해 색상, 폰트, 간격 등의 스타일을 일관되게 적용한다.
    • CSS 전처리기/Css-in-JS 라이브러리를 사용해 컴포넌트를 만들고 디자인 시스템의 컴포넌트 라이브러리에 적용한다.
    • 디자인 시스템에서 제공하는 컴포넌트 라이브러리를 활용하여 데스크톱 버전의 UI를 구현한다. CSS 미디어 쿼리를 사용해 반응형 레이아웃을 만든다.
    • 디자인 시스템의 크로스 플랫폼 원칙을 참고하여 플랫폼별 특성을 고려한 코드를 작성한다.
    • 디자이너와 협업하여 일관성 있는 애플리케이션을 구현한다.

     

     

     

    디자인 시스템을 위한 도구들

    회사에서 개발자가 디자인을 할 일은 없지만, 개인 프로젝트를 할 때 사용하면 좋다.

     

    1. Figma 
    2. Tokens Studio for Figma (Figma Tokens) - Figma 플러그인. 디자인 토큰(디자인 시스템에서 재사용할 수 있는 스타일 값-색상, 글꼴, 간격 등)을 관리하고 쉽게 적용할 수 있음. 이를 JSON 형식으로 추출해 개발자가 프로젝트에 적용.
    3. Sketch - macOS 전용 디자인 툴. 대규모 프로젝트에 사용되는 디자인 요소들 관리에 용이.
    4. Storybook - 컴포넌트 개발과 문서화를 도와주는 도구. React, Vue, Angular 등 다양한 프레임워크 지원
    5. InVision Design System Manager (DSM) - 클라우드 기반 도구. 디자인 요소들을 한 곳에서 관리, 다른 팀원들과 공유. Sketch, Adobe XD 디자인 툴과 연동됨.
    6. Zeplin - 디자이너와 개발자 간의 협업을 위한 도구.

     

    디자인 시스템 적용 예시

     

     

     

    Atomic Design

    아토믹 디자인이란 요소를 '원자'처럼 쪼개는 것이다. 더 정확하게는 아래와 같이 정의한다.

     

    UI를 물질의 가장 작은 단위인 원자처럼 최대한 쪼개고, 그것들을 조합하여 점진적으로 확장시켜 일관성 있는 디자인 시스템을 구축하는 것

     

    Atom 요소 예시

     

    요소는 아래와 같은 단계로 쪼개지고, 뭉쳐질 수 있다.

     

    Atoms(원자) - Molecules(분자) - Organisms(유기체) - Templates(템플릿) - Pages(페이지)

     

    • 원자 - 더이상 쪼갤 수 없는 구성요소. input , label , Icon , button
    • 분자 - 원자들이 모여 하나의 단위로 작동. 검색어 입력 부분, 상품 아이템 등
    • 유기체 - 페이지의 특정 섹션. 헤더, 사이드바, 상품아이템 목록 등
    • 템플릿 - 요소들의 배치와 페이지의 전체적인 구조를 정의.
    • 페이지 - 가장 구체적인 단계. 최종 결과물

     

    이렇게 요소를 원자 단위로 나누어 구현하면 재사용이 편리하고, 특정 부분의 디자인이 변경됐을 때 작은 단위의 컴포넌트만 변경하면 전체 컴포넌트에 적용되므로 보수가 훨씬 간편하다.

     

     

     

     

    Figma 디자인 토큰 추출하기

    1. Figma 제목 옆 화살표를 클릭한 뒤, Duplicate to your drafts 를 클릭하여 피그마를 복제한다.

     

    Figma 제목 옆 화살표 클릭 > Duplicate to your drafts

     

     

    2. 복제한 프로젝트에 접속해 ctrl + P 혹은 command + P를 누른 뒤 plugin을 검색한다. 하단에 있는 Find more plugins...를 선택한다.

     

     

    plugin 검색 결과

     

     

    3. Plugins에서 figma token을 검색한 뒤, Tokens Studio for FigmaRun을 누른다.

     

     

    4. 실행된 Figma Tokens 창에서 프로젝트에 적용된 다양한 토큰 종류를 확인할 수 있다.

     

     

    5. 특정 컴포넌트를 선택하면 해당 컴포넌트에 지정된 디자인 토큰을 확인 가능하다.

     

     

    6. 플러그인 왼쪽 하단의 Tools 을 누르고 Export to file/folder을 클릭한 뒤, 토큰을 tokens.json으로 추출한다.

     

     

    Export to file/folder 버튼

     

    Export 화면

     

     

     

     

    프로젝트에 토큰 적용하기

    1. 프로젝트 폴더의 src/tokens 폴더에 추출한 디자인 토큰 파일을 저장한다.

     

    tokens>tokens.json 불러오기

     

     

    2. 디자인 토큰은 가져온대로 사용할 수 없고, 개발자가 실제 사용할 수 있는 값으로 바꾸는 과정이 필요하다. token.json 에 담긴 참조 값을 실제 값으로 변환하기 위해 npm run token 명령어를 실행한다.

     

    위 명령어는 token-transformer 라이브러리를 실행해 토큰을 반환한다. 명령이 실행되면 src/tokens 폴더에 실제 값이 담긴 global.json이 생성 된다.

     

    global.json 생성 된 모습

     

    지금은 해당하지 않지만, 이 방법 외에도 디자인 토큰을 CSS 변수로 변환해 사용하는 방법도 있다.

     

     

     

     

    Button 컴포넌트 만들기

    아래는 버튼 컴포넌트에 대한 Figma 목업이다. 버튼 컴포넌트는 Primary, Secondary 두 가지 동류가 존재한다.

     

     

    Button 컴포넌트 디자인(Primary/Secondary)

     

    또한 기본 Button 옆에 아이콘이 그려진 Icon Button도 존재한다.

     

     

    Icon Button 컴포넌트 디자인

     

     

    아토믹 디자인 원칙에 따라 분자 및 유기체 단위의 컴포넌트 만들기

    컴포넌트를 원자 단위로 쪼개기 위해 Button은 LabelText(버튼 글자)와 Button 으로 구성 된다. Icon Button의 경우 Icon, LabelText, Button 세 가지 요소로 만들면 된다.

     

    아래는 Button 컴포넌트를 구현하기 위해 필요한 모듈 및 아토믹 컴포넌트다.

    import { css, styled } from 'styled-components';
    import globalToken from '../../../tokens/global.json';
    import { LabelText } from '../atoms/Typography';
    import { Icon } from '../atoms/Icon';

     

    Button 컴포넌트는 아래와 같은 인자를 받고 요소를 리턴한다.

    export const Button = ({ primary, label, icon, ...rest }) => {
      return (
        <ButtonContainer primary={primary} gap={Spacing[8].value} {...rest}>
          { icon && 
            <Icon/>
          }
          <LabelText children={label} />
        </ButtonContainer>
      );
    • primary - primary 버튼인지, secondary 버튼인지
    • label - 버튼 안에 들어갈 글자
    • icon - 버튼 옆에 그려놓을 아이콘
    • ...rest - 그 외

     

    일단 styled-component 를 사용하여 Button 컴포넌트를 만들어보자.

     

    컴포넌트의 스타일은 불러온 디자인토큰(globalToken)을 토대로 정의할 것이므로 토큰에서 필요한 값을 가져와야 한다.

     

    특정 컴포넌트가 globalToken 안에서 어떤 스타일 값을 사용하는지 확인하는 방법은 아래와 같다.

     

     

    LabelText 컴포넌트를 클릭했을 때 디자인 토큰

     

     

    Figma 에서 Tokens Studio for Figma 플러그인을 실행시킨 상태로 원하는 컴포넌트를 클릭하면 해당 컴포넌트에서 사용된 디자인토큰들이 표시된다. 위 사진은 Button 컴포넌트 안에 들어있는 LabelText 컴포넌트를 클릭했을 때 보여지는 화면이다.

     

     

    대략 버튼 컴포넌트에 사용되는 토큰을 가져오면 아래와 같다.

    const { Spacing, borderRadius, Accent, Primary, Gray, White, PrimaryFocus, SecondaryDefault } =
      globalToken;

     

    이제 styled-component 를 작성해보자. 가장 먼저 요소의 기본 스타일을 제거한다. 테두리, 아웃라인을 없애고 배경을 투명으로 지정한다.

    const ButtonContainer = styled.button`
      border: none;
      outline: none;
      background-color: transparent;
      `

     

    두 번째는 레이아웃에 대한 스타일을 지정한다.

      display: flex;
      align-items: center;
      gap: ${(props) => props.gap || 0}px;

    Icon Button의 경우 그 안에 들어가는 Icon과 LabelText이 나란히 수평으로 연결되어야 하기 때문에 display: flex; 를 해준다. 내용물을 가운데 정렬 해주고, 간격 gap 은 props로 받는 값으로 정해준다.

     

    그리고 그냥 Button이든, Icon Button이든 모든 버튼에 공통 적용되는 스타일도 정해준다.

      cursor: pointer;
      border-radius: ${borderRadius[8].value}px;

    마우스를 올렸을 때 pointer 모드가 되도록, 모서리 둥글기는 디자인토큰 borderRadius[8] 값으로 정한다.

     

    global.json에 들어가보면 borderRadius 는 아래와 같이 정의되어 있다. border-radius: 8px; 이 되는 거다.

     

     

    디자인토큰 borderRadius

     

     

    이번에는 아이콘에 대한 스타일인데, Button 요소의 직속 자식 요소인 svg 태그의 color 를 부모 상속으로 한다.

    & > svg {
        color: inherit;
      }
    • & - CSS 셀렉터를 표시할 때 사용. 현재 요소를 의미.

     

    이제 (공통된 스타일에 대해선) 마지막으로 아래가 있다.

    padding: ${Spacing[8].value - PrimaryFocus.width.value}px
        ${Spacing[16].value - PrimaryFocus.width.value}px;
      border-style: ${SecondaryDefault.style.value};
      border-width: ${SecondaryDefault.width.value}px;

    padding 값은 상하 디자인토큰 Spacing[8]의 값 - PrimaryFocus.width.value 좌우 Spacing[16] - PrimaryFocus.width.value 이다.

     

    Figma에서 해당 컴포넌트를 클릭해 padding을 확인해보면 상하는  Spacing[8] 이고 좌우는 Spacing[16] 임을 알 수 있다.

     

     

    Button의 상하좌우 padding 값

     

    그렇다면 PrimaryFocus 는 대체 뭘까?

     

     

    PrimaryFocus 디자인 토큰

     

    global.json 에 검색해보니 이렇게 생겼다. 아마 Primary Button 컴포넌트가 클릭되었을 때 border의 color, width, style 인가보다.

     

    Primary Button 이 클릭되었을 때

     

     

    즉 저 계산은 상하 좌우 간격 -  border 두께 인 것이다. 왜 이런 계산을 하는 거냐면

     

    Figma 에서 계산되는 padding은 아래 그림과 같이 border 의 두께를 별도의 존재로 여기지 않고 모두 포함해서 결정 된 너비다.

     

     

     

     

    하지만 CSS에서 border는 요소의 크기에 영향을 미치기 때문에 Figma에서 계산한 padding 값 Spacing[8] - border 두께 만큼 해주어야 Figma와 동일한 크기가 된다.

     

     

    이제부터는 props에 따른 버튼 조건부 스타일이다.

    /* 조건부 스타일 */
    ${(props) =>
    props.primary
      ? css`
          /* Primary 버튼 스타일 */
          border-color: ${Primary.value};
          background-color: ${Primary.value};
          color: ${White.value};
    
          &:hover {
            color: ${Accent.value};
          }
    
          &:active {
            border-color: ${Accent.value};
          }
        `
      : /* Secondary 버튼 스타*/
        css`
          border-color: ${SecondaryDefault.color.value};
          background-color: ${White.value};
          color: ${Gray[700].value};
    
          &:hover {
            background-color: ${SecondaryDefault.color.value};
            color: ${Primary.value};
          }
    
          &:active {
            background-color: ${SecondaryDefault.color.value};
            border-color: ${Primary.value};
          }
        `}

     

    Figma에서 컴포넌트를 클릭해보며 적용된 디자인 토큰을 확인한 뒤, global.json 에서 가져와 사용하면 된다.

     

    처음에는 뭐가 뭔지, Figma를 번갈아 보며 확인하는 것조차 너무 헷갈리고 덤벙거렸는데, 여러 개의 컴포넌트를 하나하나 만들어봤더니 슬슬 적응되고 오히려 전에 이런 기능을 사용하지 않았다는 것에 안타까움을 느꼈다.

     

     

     

     

    Storybook으로 Button 스토리 만들기

    이제 만들어진 Button 컴포넌트에 대한 스토리를 작성해보자.

     

    npm run storybook 명령어로 Storybook을 시작한다. 주소는 http://localhost:6006 이다.

     

     

    일단 사용할 Button 컴포넌트를 가져온다.

    import { Button as Template } from './Button';

     

    스토리 위치를 Basic/Buttons 로 분류한다.

    export default {
      title: 'Basic/Buttons', // 스토리 분류 및 컴포넌트 이름
      component: Template, // 테스트할 컴포넌트Button
    };

     

    Button 스토리를 정의한다. Primary , Secondary 두 가지 버전이 존재한다.

    export const Primary = {
      args: {
        primary: true,
        label: 'Button'
      },
    };
    
    export const Secondary = {
      args: {
        ...Primary.args,
        primary: false,
      },
    };

     

     

    이제 Storybook 을 실행시키면

     

     

    Storybook - Basic/Buttons 생김

     

     

    이렇게 Basic/Buttons 폴더 안에 두 개의 스토리가 잘 나타난 걸 볼 수 있고,

     

     

    primary, label 전달 인자를 받아 모습을 변화시킬 수 있다.

     

     

    Button 컴포넌트의 스타일에 영향을 주는 두 전달인자를 args에 넣어줬더니 사용자가 직접 전달 인자를 입력 하며 차이를 확인할 수 있게 되었다.

Designed by Tistory.