[CryptoMeter] 스타일 시스템 구축하기

0

CryptoMeter

목록 보기
3/6
post-thumbnail

팀원들과 논의하여 프로젝트를 다음의 3가지 마일스톤으로 나누었다.

  • 💀 컴포넌트 스켈레톤 제작
    • 스타일링🖐
    • 컴포넌트 인터페이스 설계
    • 공용 컴포넌트(인풋, 버튼 등) 제작
  • 🖥️ 스크린 구성하기
    • 컴포넌트 조립
    • 반응형 스타일 적용
    • 추가 컴포넌트 제작(ex. 모달)
    • 애니메이션 추가
  • 🚀 기능 구현하기
    • API 붙이기
    • 페이지네이션 구현
    • SNS 공유기능
    • 전역 상태 관리
    • 차트 구현

첫 번째로 내가 맡은 역할은 컴포넌트 스켈레톤 제작 마일스톤의 스타일링이다.
CSS 라이브러리로 styled-components를 선택했기에 최대한 해당 라이브러리의 이점을 살리려고 고민하던 중 컴포넌트처럼 props를 사용하여 스타일링을 할 수 있다는 점에 착안하여 디자인 시스템과 유사한 환경을 만들어 보기로 했다.

디자인 시스템이란?

디자인 시스템
디자인 원칙과 규격, 재사용 가능한 UI 패턴과 컴포넌트, 코드를 포괄하는 시스템이다. 단순 스타일 가이드, 패턴 라이브러리 역할만 하는 디자인 시스템도 있고, 브랜드 원칙과 UX 원칙에 이르는 하나의 철학을 구성하는 시스템도 있다. 정해진 디자인 패턴과 컴포넌트를 재사용하여 제품을 구축하고, 개선하는 시간을 단축시켜준다. 일종의 레고 세트라고 볼 수 있다.

디자인 시스템은 프로젝트가 아닙니다. 제품을 제공하는 제품입니다.
- Nathan Curtis, EightShapes

출처: https://yozm.wishket.com/magazine/detail/1830/

디자인 시스템은 앞선 설명과 같이 하나로 정의할 수 없는 큰 개념이다. 물론 우리의 프로젝트에서 기업이 사용할만큼 방대하고 디테일한 디자인 시스템은 만들 수도 없고, 만들 필요도 없다.
그럼에도 디자인 시스템과 조금이나마 유사한 환경이 필요하다고 생각한 이유는, 팀 프로젝트의 능률을 부스트하기 위함이었다. 팀원 모두의 일관된 css문의 작성을 위해 일종의 공통된 룰이 필요하다고 생각했다. 컨벤션으로 해결할 수 있을까 생각해보았지만 이미 정해진 컴벤션 위에 추가로 디테일한 컨벤션을 얹는것은 더욱 헷갈리게만 만들것 같았다.
styled components를 사용하는 이점과 디자인 시스템을 연관지어 고민을 해본 결과 props를 사용할 수 있다는 점을 이용하여 미니어처 디자인 시스템을 만들기로 하였다.
tailwind css처럼 사용할 수 있는 스타일 시스템을 만드는 것이 목표였다.

준비된 기획안

다행히 내가 모든 디자인을 만드는 것이 아니었다. 처음부터 어느정도 갖추어진 시스템이 존재했다. 부트캠프 디자인 팀에서 제공해준 피그마 기획안으로 썩 어렵지 않게 시스템을 구축할 수 있었다.

타이포그래피 구축용 노가다

하지만 힘들었던 점은 타이포그래피 시스템의 부재였다. 피그마에 스타일 변수로 저장된 타이포그래피 시스템이 없었던 것이다. 그래서 기획안에 쓰인 폰트 사이즈와 굵기를 헤딩, 서브헤딩, 본문, 캡션별로 내 나름으로 분류하고 모바일에서 각 폰트가 어떻게 변하는지도 정리하여 코드로 옮겼다. 상당히 많은 시간이 걸렸으며, 이런 작업을 해본 적이 없으니 처음에 많이 헤맸다. 우여곡절을 거쳐 일단은 타이포그래피 분류가 완성되었다..ㅎㅎ

내가 기대했던 것

처참한 비주얼의 결과물


내 결과물을 보면 디폴트라고 중간중간에 표시되어 있는데, 각각의 폰트가 모바일뷰에서 작아지는 비율이 달랐기 때문에 이를 표시해둔 것이다. 디폴트라고 되어있는 행은 내가 구축할 스타일 시스템에서 @media..로 자동 대응하게 할것이라는 표시이다.
이해가 잘 안될텐데, 예시를 들어 설명해보도록 하자.
Desktop 뷰에서 차트 제목의 캡션 텍스트와, 사이드바의 입력란 placeholder에 쓰이는 텍스트는 모두 s1(18px semibold)이다.
하지만 모바일 뷰로 변하면, 차트 캡션은 15px로 줄어들고, placeholder는 16px로 줄어든다. 이러면 뭐가 문제일까?

s1을 코드로 옮길 때 아래와 같은 문제가 발생한다.

const s1 = css`
  font-family: "Pretendard-SemiBold";
  font-size: 18px;
  line-height: 24px;
  letter-spacing: -0.3px;

  @media only screen and (max-width: 375px) {
    font-size: 16px ?? 15px ??;  // 모바일에서 몇으로 줄어들게 해야함? 
  }
`;

이런 해법을 생각해보았다. s1을 용도에 맞게 두 변수로 쪼개는 것이다.

const s1_caption = css`
  font-family: "Pretendard-SemiBold";
  font-size: 18px;
  line-height: 24px;
  letter-spacing: -0.3px;

  @media only screen and (max-width: 375px) {
    font-size: 15px;
  }
`;

const s1_placeholder = css`
  font-family: "Pretendard-SemiBold";
  font-size: 18px;
  line-height: 24px;
  letter-spacing: -0.3px;

  @media only screen and (max-width: 375px) {
    font-size: 16px;
  }
`;

하지만 이게 디자인 시스템이 과연 맞을까? 위처럼 할거라면 그냥 시스템 없이 하는것과 다름없지 않을까? 그래서 그냥 s1은 디폴트로 모바일에서 16px로 줄어들게 해 놓고, caption처럼 모바일에서 맞는 사이즈로 자동으로 줄어들지 않는 경우는 피그마에 다음과 같이 표기를 해 두었다.

이게 맞나... 더 좋은 방법 있으면 소개해주세요.

그래도 페이지가 하나라 다행히 그리 오래걸리진 않았다. 그리고 메세지도 직관적이고, 팀원들과 잘 공유했기에 헷갈릴 일도 없을것 같다. 오히려 피그마의 협업 기능을 사용한 경험으로 만들 수 있을듯 하다.

스타일 시스템 구축

styled-system이라는 라이브러리를 알게 되었다. 내가 만드려는 것과 상당히 유사해 보였다. 하지만 우리 프로젝트는 필수적인 api 호출과 차트 모듈 로딩 등 큼지막한 모듈들에 의존하고 있어 굳이 라이브러리를 사용하지 않고 싶었다.

내가 구축하려는 styled components를 이용한 디자인 시스템은 디자인 시스템이라 칭하기 애매한 부분이 있어 마이크로 컴포넌트 사용을 통한 스타일 시스템이라고 칭하기로 했다. 마침 비슷한 이름과 내용의 라이브러리도 있어서 적합한 이름 같다.

추출한 스타일 코드로 표현하기

다음과 같이 피그마 기획안에서 추출한 공통 색상들과 css 속성들을 나열해준다.

//colors.js
const colors = {
  green: "#00A661",
  lightgreen: "#DAF1E5",
  red: "#FD493D",
  lightred: "#F8EAE7",
  blue: "#4C32ED",
  white: "#FFFFFF",
  gray1: "#F5F8F9",
  gray2: "#E7E9F0",
  gray3: "#CED2DD",
  gray4: "#A2A7B7",
  gray5: "#848898",
  gray6: "#616575",
  gray7: "#474B58",
  gray8: "#262A38",
  gray9: "#161C2F",
  black: "#0B0E1B",
  tooltip: "rgba(29, 29, 29, 0.9)",
  green_gradient:
    "linear-gradient(180.34deg, rgba(0, 166, 97, 0.5) 0.29%, rgba(9, 177, 76, 0) 99.52%)",
  red_gradient:
    "linear-gradient(180deg, rgba(253, 73, 61, 0.5) 0%, rgba(253, 73, 61, 0) 100%)",
  background: "f8f5f9",
};

export default colors;
// text.style.js
import { css } from "styled-components";

import colors from "@/styles/colors";

const white = css`
  color: ${colors.white};
`;

const green = css`
  color: ${colors.primary};
`;

const red = css`
  color: ${colors.red};
`;

const black = css`
  color: ${colors.black};
`;

const g9 = css`
  color: ${colors.gray9};
`;

const g8 = css`
  color: ${colors.gray8};
`;

const g7 = css`
  color: ${colors.gray7};
`;

const g6 = css`
  color: ${colors.gray6};
`;

const g5 = css`
  color: ${colors.gray5};
`;

const g4 = css`
  color: ${colors.gray4};
`;

const g3 = css`
  color: ${colors.gray3};
`;

const g2 = css`
  color: ${colors.gray2};
`;

const g1 = css`
  color: ${colors.gray1};
`;

const h1 = css`
  font-family: "Pretendard-Bold";
  font-size: 3rem;
  line-height: 3.6rem;
  letter-spacing: -0.0187rem;

  @media only screen and (max-width: 375px) {
    font-size: 2rem;
    line-height: 2.4rem;
  }
`;

const h2 = css`
  font-family: "Pretendard-SemiBold";
  font-size: 2.25rem;
  line-height: 2.7rem;
  letter-spacing: -0.0187rem;

  @media only screen and (max-width: 375px) {
    font-size: 1.5rem;
    line-height: 1.8rem;
  }
`;

const h3 = css`
  font-family: "Pretendard-Bold";
  font-size: 1.625rem;
  line-height: 1.95rem;
  letter-spacing: -0.0187rem;

  @media only screen and (max-width: 375px) {
    font-size: 1.25rem;
    line-height: 1.5rem;
  }
`;

const h4 = css`
  font-family: "Pretendard-Bold";
  font-size: 1.25rem;
  line-height: 1.5rem;
  letter-spacing: -0.0187rem;

  @media only screen and (max-width: 375px) {
    font-size: 1.125rem;
    line-height: 1.35rem;
  }
`;

const s1 = css`
  font-family: "Pretendard-SemiBold";
  font-size: 1.125rem;
  line-height: 1.5rem;
  letter-spacing: -0.0187rem;

  @media only screen and (max-width: 375px) {
    font-size: 1rem;
    line-height: 1.2rem;
  }
`;

const s2 = css`
  font-family: "Pretendard-SemiBold";
  font-size: 0.9375rem;
  line-height: 0.9375rem;
  letter-spacing: -0.0187rem;

  @media only screen and (max-width: 375px) {
    font-size: 0.875rem;
    line-height: 1.05rem;
  }
`;

const s3 = css`
  font-family: "Pretendard-SemiBold";
  font-size: 0.875rem;
  line-height: 1.125rem;
  letter-spacing: -0.0187rem;
`;

const b1 = css`
  font-family: "Pretendard-Medium";
  font-style: normal;
  font-weight: normal;
  font-size: 1.0625rem;
  line-height: 1.375rem;
  letter-spacing: -0.0187rem;

  @media only screen and (max-width: 375px) {
    font-size: 0.9375rem;
    line-height: 1.125rem;
  }
`;

const b2 = css`
  font-family: "Pretendard-Medium";
  font-style: normal;
  font-weight: normal;
  font-size: 0.9375rem;
  line-height: 0.9375rem;
  letter-spacing: -0.0187rem;

  @media only screen and (max-width: 375px) {
    font-size: 0.8125rem;
    line-height: 0.975rem;
  }
`;

const b3 = css`
  font-family: "Pretendard-Medium";
  font-style: normal;
  font-weight: normal;
  font-size: 0.875rem;
  line-height: 1.125rem;
  letter-spacing: -0.0187rem;

  @media only screen and (max-width: 375px) {
    font-size: 0.8125rem;
    line-height: 0.975rem;
  }
`;

const c1 = css`
  font-family: "Pretendard-Medium";
  font-size: 1rem;
  line-height: 1.2rem;
  letter-spacing: -0.0187rem;

  @media only screen and (max-width: 375px) {
    font-size: 0.8125rem;
    line-height: 0.975rem;
  }
`;

const c2 = css`
  font-family: "Pretendard-Regular";
  font-size: 0.875rem;
  line-height: 18px;

  @media only screen and (max-width: 375px) {
    font-size: 0.625rem;
    line-height: 0.75rem;
  }
`;

const c3 = css`
  font-family: "Pretendard-Medium";
  font-size: 0.75rem;
  line-height: 14px;
`;

export {
  white,
  black,
  green,
  red,
  g1,
  g2,
  g3,
  g4,
  g5,
  g6,
  g7,
  ...(전부)
};

마이크로 컴포넌트 만들기

마이크로 컴포넌트라는 이름으로 잔 스타일링이 아주 잦은 styled.div와 styled.span태그를 만들어 준다.
그리고 블록요소 스타일링에 많이 쓰이는 css속성들을 직관적이고 간단한 이름으로 정의해준다. 이렇게 정의한 css문들을 이제 styled.div와 styled.span에 넣어주는데, 다음과 같이 prop으로 들어온 값에 따라 스타일링이 되게 만든다.

import styled from "styled-components";

import {
  mg,
  pd,
  bg,
  bd,
  bc,
  ...(전부)
} from "@/styles/block.style";

const SDiv = styled.div`
  /* flexbox 속성 */
  ${(props) => props.row && row}
  ${(props) => props.col && col}
  ${(props) => props.ct && ct}
  ${(props) => props.start && start}
  ${(props) => props.aed && aed}
  ${(props) => props.jed && jed}
  ${(props) => props.ast && ast}
  ${(props) => props.jst && jst}
  ${(props) => props.act && act}
  ${(props) => props.jct && jct}
  ${(props) => props.sb && sb}

  /* background color 속성*/
  ${(props) => props.bg && bg}
  ${(props) => props.white && white}
  ${(props) => props.lightgreen && lightgreen}
  ${(props) => props.lightred && lightred}
  ${(props) => props.black && black}
  ${(props) => props.tooltip && tooltip}

  /* div 내부 텍스트 속성 */
  ${(props) => props.disableSelect && disableSelect} 

  /* 마진 속성 */
  margin-top: ${(props) => props.mgt || 0}px;
  margin-bottom: ${(props) => props.mgb || 0}px;
  margin-left: ${(props) => props.mgl || 0}px;
  margin-right: ${(props) => props.mgr || 0}px;
  ${(props) => props.mg && mg}

  /* 패딩 속성 */
  padding-top: ${(props) => props.pdt || 0}px;
  padding-bottom: ${(props) => props.pdb || 0}px;
  padding-left: ${(props) => props.pdl || 0}px;
  padding-right: ${(props) => props.pdr || 0}px;
  ${(props) => props.pd && pd}

  /* border 속성 */
  border-radius: ${(props) => props.br || 0}px;
  ${(props) => props.bc && bc};
  ${(props) => props.bd && bd}

  /* 나머지 속성 */
  ${(props) => props.flex && flex}

  width: ${(props) => (props.w ? `${props.w}px` : "auto")};
  height: ${(props) => (props.h ? `${props.h}px` : "auto")};
  min-height: ${(props) => (props.mh ? `${props.mh}%` : "auto")};

  /* gap 속성 */
  gap: ${(props) => (props.g ? `${props.g}px` : 0)};

  ${(props) => props.center && center}
  ${(props) => props.left && left}
  ${(props) => props.right && right}
`;

export default SDiv;
// block.style.js
import { css } from "styled-components";

import colors from "@/styles/colors";

const mg = css`
  margin: ${(props) => props.mg};
`;

const pd = css`
  padding: ${(props) => props.pd};
`;

const bg = css`
  background-color: ${(props) => props.bg};
`;

const bd = css`
  border: ${(props) => props.bd};
`;

const bc = css`
  border-color: ${(props) => props.bc};
`;

const flex = css`
  flex: ${(props) => props.flex};
`;

const row = css`
  display: flex;
  flex-direction: row;
`;

const col = css`
  display: flex;
  flex-direction: column;
`;

const ct = css`
  display: flex;
  justify-content: center;
  align-items: center;
`;

const sb = css`
  display: flex;
  justify-content: space-between;
`;

const start = css`
  display: flex;
  justify-content: flex-start;
  align-items: flex-start;
`;

const ast = css`
  display: flex;
  align-items: flex-start;
`;

const jst = css`
  display: flex;
  justify-content: flex-start;
`;

const aed = css`
  display: flex;
  align-items: flex-end;
`;

const jed = css`
  display: flex;
  justify-content: flex-end;
`;

const jct = css`
  display: flex;
  justify-content: center;
`;

const act = css`
  display: flex;
  align-items: center;
`;

const white = css`
  background-color: ${colors.white};
`;

const lightgreen = css`
  background-color: ${colors.lightgreen};
`;

const lightred = css`
  background-color: ${colors.lightred};
`;

const black = css`
  background-color: ${colors.black};
`;

const tooltip = css`
  background-color: ${colors.tooltip};
`;

const disableSelect = css`
  user-select: none;
`;

const center = css`
  text-align: center;
`;

const left = css`
  text-align: left;
`;

const right = css`
  text-align: right;
`;

export {
  mg,
  pd,
  bg,
  bd,
  bc,
  ... (전부)
};

div하고 span만 만들었다가 버튼도 많이 쓸것 같아서 styled.button도 추가해줬다.
그리고 생각해보니 헤딩을 전부 span태그로 만들면 너무 semantic하지 못한것 같아서 다음과 같이 SHeading이라는 이름을 한 번 더 추상화를 해서 제공했다.

/* eslint-disable object-curly-newline */
import styled from "styled-components";

import { black, g5, h1, h2, h3, h4 } from "@/styles/text.style";

const SHeading1 = styled.h1`
  ${h1}
  ${black}
`;

const SHeading2 = styled.h2`
  ${h2}
  ${g5}
`;

const SHeading3 = styled.h3`
  ${h3}
  ${black}
`;

const SHeading4 = styled.h4`
  ${h4}
  ${black}
`;

export { SHeading1, SHeading2, SHeading3, SHeading4 };

이제 드디어 미니어처 스타일 시스템 완성~🤹‍♂️

사용하기

버튼 제작 예시

  1. 폰트 사이즈 및 색상: 피그마 기획안 코멘트 참고

  • b3, g8 이라고 되어있는데 b3가 어떤 사이즈인지, g8은 컬러코드가 어떻게 되는지 찾을 필요가 없음! 그냥 <SButton b3 g8 ... />해주면 된다!
  1. 박스 사이즈, 보더 등 버튼 스타일 속성: 피그마 기획안 Inspect탭 참고
  1. /src/styles/micro-components/StyledButton.js의 props와 block.styles.js의 css속성변수 참고

  1. 마이크로 컴포넌트를 이용하여 스타일링
<SButton col ct bd={`1px solid ${colors.gray3}`} w={81} h={40} br={12}>
  <SText b3 g8>
    검색 기록
  </SText>
</SButton>
  1. 결과물

0개의 댓글