'Coin Site Project'

[TIL/Coin Site Project] 2023/12/18

✅ 다시 공식문서 공식 문서의 theming과 palette 파트에 최근 며칠간 deep dive 했다. 끝난 줄 알았다. 아뿔싸... dark mode 파트를 공부하지 않으면 의미가 없다는 것을 공식 문서를 훑어보며 깨달았다. 도대체 무슨 말을 하고 싶은 걸까?

2023년 12월 18일3min read

✅ 다시 공식문서

공식 문서의 theming과 palette 파트에 최근 며칠간 deep dive 했다. 끝난 줄 알았다. 아뿔싸... dark mode 파트를 공부하지 않으면 의미가 없다는 것을 공식 문서를 훑어보며 깨달았다. 도대체 무슨 말을 하고 싶은 걸까?

✅ Dark mode

MUI에는 light와 dark, 두 가지 palette 모드가 있다.

1. Dark mode by default ✍️

createTheme에 mode: 'dark'를 추가하여, MUI에서 제공하는 기본적인 dark mode를 적용할 수 있다.

code
import { ThemeProvider, createTheme } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';

const darkTheme = createTheme({
  palette: {
    mode: 'dark',
  },
});

export default function App() {
  return (
    <ThemeProvider theme={darkTheme}>
      <CssBaseline />
      <main>This app is using the dark mode</main>
    </ThemeProvider>
  );
}

다만, 이 부분은 나의 관심사가 아니다. mode: 'dark'라는 코드 한 줄 알자고 공식문서를 읽은 것은 아니기 때문이다. 핵심은 mode: 'dark'를 추가하면 palette 값이 다음의 값으로, 자동적으로 변경된다는 점이다.

Typography, Buttons, Background, Divider라는 구분이 있고 각각 palette 객체 내부에서 text, action, background, divider로 활용된다는 것을 알아야 커스텀을 할 것 아닌가!

2. Dark mode with a custom palette ✍️

사실상 이 파트가 최종보스가 되시겠다.

공식문서에서는 custom palette를 사용하기 위해, 모드에 맞는 palette를 반환하는 함수를 만드는 방법이 있다고 제시하고 있다. 코드는 다음과 같다.

code
const getDesignTokens = (mode: PaletteMode) => ({
  palette: {
    mode,
    ...(mode === 'light'
      ? {
          // palette values for light mode
          primary: amber,
          divider: amber[200],
          text: {
            primary: grey[900],
            secondary: grey[800],
          },
        }
      : {
          // palette values for dark mode
          primary: deepOrange,
          divider: deepOrange[700],
          background: {
            default: deepOrange[900],
            paper: deepOrange[900],
          },
          text: {
            primary: '#fff',
            secondary: grey[500],
          },
        }),
  },
});

형태가 중요한게 아니다. Typography를 커스텀하고 싶다면 text key를 활용해야 하고, 배경색을 변경하고 싶다면 background key를 사용해야 한다. 처음 공식문서의 해당 파트를 읽을 때, 무슨 자신감으로 이 부분을 그냥 넘어갔는지 모르겠다.

공식 문서는 구체적인 custom palette 예제를 제공하고 있다.

code
import * as React from 'react';
import Box from '@mui/material/Box';
import { ThemeProvider, useTheme, createTheme } from '@mui/material/styles';
import { amber, deepOrange, grey } from '@mui/material/colors';

const getDesignTokens = (mode) => ({
  palette: {
    mode,
    primary: {
      ...amber,
      ...(mode === 'dark' && {
        main: amber[300],
      }),
    },
    ...(mode === 'dark' && {
      background: {
        default: deepOrange[900],
        paper: deepOrange[900],
      },
    }),
    text: {
      ...(mode === 'light'
        ? {
            primary: grey[900],
            secondary: grey[800],
          }
        : {
            primary: '#fff',
            secondary: grey[500],
          }),
    },
  },
});

function MyApp() {
  const theme = useTheme();
  return (
    <Box
      sx={{
        display: 'flex',
        width: '100%',
        alignItems: 'center',
        justifyContent: 'center',
        bgcolor: 'background.default',
        color: 'text.primary',
        borderRadius: 1,
        p: 3,
      }}
    >
      This is a {theme.palette.mode} mode theme with custom palette
    </Box>
  );
}

const darkModeTheme = createTheme(getDesignTokens('dark'));

export default function DarkThemeWithCustomPalette() {
  return (
    <ThemeProvider theme={darkModeTheme}>
      <MyApp />
    </ThemeProvider>
  );
}

bgcolor에는 'background.default'를, color에는 'text.primary'를 적용하고 있다.

17번과 28번 라인을 변경하면?

3. 생각을 해봅시다! ✍️

1. index.js

App이 MUIProvider로 감싸져 있다. MUIProvider는 DarkModeProvider로 감싸져 있다.

2. DarkModeProvider.js

context를 만들고, 하위에서 마음껏 사용할 수 있도록 useDarkContext도 만들었다. value로 mode라는 상태와 toggleColorMode라는 함수를 전달하고 있기에, 개별 컴포넌트에서는 useDarkContext 함수를 실행하면 해당 값을 자유롭게 꺼내서 사용할 수 있게 된다.

3. MUIProvider.js

언급한 대로, useDarkContext의 반환값인 mode와 toggleColorMode로 구성된 객체를 context라는 변수에 담았다. MUIProvider는 모드에 따라 변경된 theme을 하위에 적용하는 provider다.

4. getDesignTokens.js

context.mode가 무엇이냐에 따라 미리 정해놓은 커스텀 컬러가 적용된다.

5. App.js

변경 버튼을 적용하기 위해 useDarkContext를 실행했고, 스타일링은 간단하다.

bgcolor에는 background.default가 적용되어 있다. 버튼을 클릭하는 순간 DarkModeProvider의 mode 상태가 dark로 변경되고, 해당 mode는 MUIThemeProvider에서 mode에 맞는 theme으로 반영된다. 최종적으로 getDesignTokens에서는 모드에 맞는 background.default를 반영하게 된다. color에 적용되어 있는 text.primary도 마찬가지의 논리를 따른다.

4. 구현된 모습 ✍️

정말 준비가 끝났다. 내일은 프로젝트에서 불필요한 코드를 솎아낸 뒤, 오늘까지 정리한 모든 내용을 적용할 것이다.

✅ 회고

오늘도 또 한 번 느낀 것은, 역시 프로젝트는 수단에 불과하다는 점이다. 사실 화면에서는 단순한 버튼 하나만 눈에 띌 것이다. 그런데 그 버튼 안에는 theming, palette, custom theme, context, custom hook, state 등 다양한 개념이 녹아들어 있다.

이 모든 개념들을, 책을 읽는 것처럼 하나하나 배웠다면 굉장히 비효율적이었을 것이라는 생각이 든다. 자신이 해결하고자 하는 문제를 정의하고, 그 문제를 중심으로 학습해 나가는 과정이 가장 효율적인 방법일 수 있겠다. 물론 이런 과정은 간단하지 않겠지만 말이다.