본문으로 바로가기

우선 평소에하듯 theme을 만듭니다.

여기서 light 모드에 사용할 색과 dark 모드에 사용할 색을 구별하여 적어줍시다.

이 부분은 실제로 스타일링해가면서 색을 미묘하게 맞춰야하기 때문에 일단은 lightColor의 색을 그대로 사용하도록하겠습니다.

// px to rem
const calcRem = (px: number): string => `${px / 16}rem`;

const lightColor = {
  red: "#dc3545",
  orange: "#fd7e14",
  yellow: "#ffc107",
  green: "#28a745",
  teal: "#20c997",
  blue: "#007bff",
  indigo: "#6610f2",
  purple: "#6f42c1",
  pink: "#e83e8c",
  cyan: "#17a2b8",
  primary: "#007bff",
  secondary: "#6c757d",
  success: "#28a745",
  info: "#17a2b8",
  warning: "#ffc107",
  danger: "#dc3545",
};

const darkColor = {
  ...lightColor,
  // dark에서만 사용할 color를 자율적으로 추가.
};

const fontSizes = {
  xsmall: calcRem(12),
  small: calcRem(14),
  base: calcRem(16), // 1rem
  lg: calcRem(18),
  xl: calcRem(20),
  xxl: calcRem(22),
  xxxl: calcRem(24),
  xxxxl: calcRem(16 * 2),
  xxxxxl: calcRem(16 * 3),
};

const paddings = {
  xxsmall: calcRem(4),
  xsmall: calcRem(6),
  small: calcRem(8),
  base: calcRem(10),
  lg: calcRem(12),
  xl: calcRem(14),
  xxl: calcRem(16),
  xxxl: calcRem(18),
  xxxxl: calcRem(24),
  xxxxxl: calcRem(36),
  global: calcRem(16 * 6),
};

const margins = {
  small: calcRem(8),
  base: calcRem(10),
  lg: calcRem(12),
  xl: calcRem(14),
  xxl: calcRem(16),
  xxxl: calcRem(18),
  xxxxl: calcRem(24),
  xxxxxl: calcRem(28),
  global: calcRem(16 * 6),
};

const deviceSizes = {
  tablet: `@media all and (min-width: 767px) and (max-width: 1023px)`,
  desktop: `@media all and (min-width: 1023px)`,
};

const theme = {
  lightColor,
  darkColor,
  fontSizes,
  paddings,
  margins,
  deviceSizes,
};

type ThemeType = typeof theme;
export { ThemeType };
export default theme;

 

그 다음은 globalstyle을 통해 아래와 같이 theme을 --var에 묵어줍니다.

여기서 adaptive gray를 적용하였습니다. 이 부분은 토스의 발표를 참고했습니다. www.youtube.com/watch?v=ElsZ-v4Ow08

그리고, dark mode 클래스가 붙은 곳에서 별도의 컬러를 사용하도록 적용해줍니다. 

import { createGlobalStyle } from "styled-components";
import { ThemeType } from "./theme";

const GlobalStyle = createGlobalStyle<{ theme: ThemeType }>`
    :root {
        --red: ${(props) => props.theme.lightColor.red};
        --orange: ${(props) => props.theme.lightColor.orange};
        --yellow: ${(props) => props.theme.lightColor.yellow};
        --green: ${(props) => props.theme.lightColor.green};
        --teal: ${(props) => props.theme.lightColor.teal};
        --blue: ${(props) => props.theme.lightColor.blue};
        --indigo: ${(props) => props.theme.lightColor.indigo};
        --purple: ${(props) => props.theme.lightColor.purple};
        --pink: ${(props) => props.theme.lightColor.pink};
        --cyan: ${(props) => props.theme.lightColor.cyan};
        --primary: ${(props) => props.theme.lightColor.primary};
        --secondary: ${(props) => props.theme.lightColor.secondary};
        --success: ${(props) => props.theme.lightColor.success};
        --info: ${(props) => props.theme.lightColor.info};
        --warning: ${(props) => props.theme.lightColor.warning};
        --danger: ${(props) => props.theme.lightColor.danger};

        /* adaptive gray lightmode */
        --adaptiveGray50: #f9fafb;
        --adaptiveGray100: #f2f4f6;
        --adaptiveGray200: #e5e8eb;
        --adaptiveGray300: #d1d6db;
        --adaptiveGray400: #b0b8c1;
        --adaptiveGray500: #8b95a1;
        --adaptiveGray600: #6b7684;
        --adaptiveGray700: #4e5968;
        --adaptiveGray800: #333d4b;
        --adaptiveGray900: #191f28;
    }

    /* 다크 모드일 때 body에 삽일할 것. */
    .dark-mode {
        --red: ${(props) => props.theme.darkColor.red};
        --orange: ${(props) => props.theme.darkColor.orange};
        --yellow: ${(props) => props.theme.darkColor.yellow};
        --green: ${(props) => props.theme.darkColor.green};
        --teal: ${(props) => props.theme.darkColor.teal};
        --blue: ${(props) => props.theme.darkColor.blue};
        --indigo: ${(props) => props.theme.darkColor.indigo};
        --purple: ${(props) => props.theme.darkColor.purple};
        --pink: ${(props) => props.theme.darkColor.pink};
        --cyan: ${(props) => props.theme.darkColor.cyan};
        --primary: ${(props) => props.theme.darkColor.primary};
        --secondary: ${(props) => props.theme.darkColor.secondary};
        --success: ${(props) => props.theme.darkColor.success};
        --info: ${(props) => props.theme.darkColor.info};
        --warning: ${(props) => props.theme.darkColor.warning};
        --danger: ${(props) => props.theme.darkColor.danger};

        --adaptiveGray50: #202027;
        --adaptiveGray100: #2c2c35;
        --adaptiveGray200: #3c3c47;
        --adaptiveGray300: #4d4d59;
        --adaptiveGray400: #62626d;
        --adaptiveGray500: #7e7e87;
        --adaptiveGray600: #9e9ea4;
        --adaptiveGray700: #c3c3c6;
        --adaptiveGray800: #e4e4e5;
        --adaptiveGray900: #ffffff;
    }

    html,
    body {
        font-family: 'Gothic A1', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
        -webkit-font-smoothing: antialiased;
    }

    * {
        box-sizing: border-box;
    }
`;

export default GlobalStyle;

 

사용시에는 평소 css를 사용하듯 사용하면 됩니다.

const S = {
  Wrapper: styled.div`
    background: var(--adaptiveGray50);
    color: var(--adaptiveGray900);
    height: 80vh;

    .color {
      color: var(--red);
    }
  `,
};

 

그리고 고민인 지점은, 다크모드를 state로 변화시킬 것인지, 단순히 body에 클래스를 넣을 것인지에 대한 toggle을 구현하는 방법론입니다. 이 부분에 있어선, 굳이 state를 사용하지 않게 하겠습니다.

 

React인데 직접 이렇게 DOM을 함부로 만져도 되는거냐?

라는 비판은 충분히 제기할 수 있을겁니다. 맞는 말입니다만, 이번만큼을 직접 dom을 제어하도록하겠습니다.

 

왜냐하면, 전역 상태 관리가 필요하지 않은 가벼운 케이스도 있을텐데 다크 모드만을 위해 전역 상태 관리 툴을 붙이는 것은 오버헤드가 크다고 판단했기 때문입니다.

 

나중에 프로젝트를 진행하다가 문제가 생기면 별도로 관리해주도록하겠습니다만,

우선은 다음과 같은 코드로도 충분히 다크모드가 구현되는 것을 확인할 수 있습니다.

function Header() {
  const toggleTheme = () => {
    
    return document.body.classList.toggle("dark-mode");
    
    // toggle이 있는지 몰랐을 때...
    //if ([...document.body.classList].includes("dark-mode")) {
    //  return document.body.classList.remove("dark-mode");
    //}
    //return document.body.classList.add("dark-mode");
  };
  return (
    <S.Wrapper>
      <div>logo</div>
      <div>
        <button type="button" onClick={toggleTheme}>
          toggle dark mode
        </button>
      </div>
    </S.Wrapper>
  );
}

export default Header;

 

 

+ 추가

 

몰랐는데 toggle 메서드가 있었다. 아 ㅋㅋㅋ 바보 같이

출처 : https://www.w3schools.com/howto/howto_js_toggle_dark_mode.asp

 

element.classList.toggle("dark-mode");

 


darren, dev blog
블로그 이미지 DarrenKwonDev 님의 블로그
VISITOR 오늘 / 전체