Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] 멀티 테마 기능 #296

Open
2 tasks
yoojinsuah opened this issue Sep 26, 2023 · 2 comments
Open
2 tasks

[FEATURE] 멀티 테마 기능 #296

yoojinsuah opened this issue Sep 26, 2023 · 2 comments
Assignees

Comments

@yoojinsuah
Copy link
Contributor

🔍 Description

여러 테마를 사용할 수 있는 기능을 구현해야 합니다
예를들어 라이트 모드, 다크 모드 두 가지 테마를 전달해 필요할 때마다 변경해서 사용할 수 있는 기능 입니다


🔨 TODO

  • 할일1
  • 할일2

📚 Reference

기능 구현에 참고한 내용이 있나요? (블로그, 공식문서 등등)


🔔 ETC

메모할 사항이 있나요? (다른 개발자가 봤을 때 참고하면 좋을 내용, PR에 전달할 내용 등등)

@yoojinsuah
Copy link
Contributor Author

yoojinsuah commented Sep 26, 2023

문제: 기존에 정의되어있는 color 타입에 맞춰서 덮어쓰는 것은 가능하지만, 새로운 값을 넣어서 쓰는 것이 안된다
스크린샷 2023-09-26 오후 4 04 07
위 예시에서 Hex값을 다른 값으로 덮어쓰는 것은 가능하지만
스크린샷 2023-09-26 오후 4 06 52
위 예시처럼 새로운 프로퍼티를 넣었을 때 타입지원을 받을 수 없다

해결해야 합니다. 타입스크립트 공부좀 해야겠습니다...

@yoojinsuah
Copy link
Contributor Author

yoojinsuah commented Nov 8, 2023

기존의 테마 설정 방법

  1. emotion.d.ts 파일을 만들어서 Theme 타입을 선언한다. 기본적으로 Theme은 빈 객체 타입이며, 우리는 여기에 MusmaTheme을 상속해준 뒤 사용합니다. Emotion - Define a theme
import '@emotion/react'

import { MusmaTheme } from './musma/theme'

declare module '@emotion/react' {
  // eslint-disable-next-line @typescript-eslint/no-empty-interface
  export interface Theme extends MusmaTheme {}
}

  1. 프로젝트에서 사용할 테마는 아래와 같이 이미 정의된 기본값인 DefaultTheme을 불러와서, 커스텀 하고 싶은 부분을 덮어쓰는 형태로 사용한다. 아래 예시(대동)와 같이 theme 변수를 만들어서 MusmaProvider 에 전달합니다. 전달하지 않으면 디자인시스템에 정의되어있는 기본값을 사용합니다.

const theme = {
  ...DefaultTheme,
  colors: {
    ...DefaultTheme.colors,
    primary: {
      lighter: '#1FA2FF',
      light: '#006CE8',
      main: '#013F6B',
      dark: '#013F6B',
      darker: '#003E6A',
    },
    blue: {
      lighter: '#1FA2FF',
      light: '#006CE8',
      main: '#084C9C',
      dark: '#013F6B',
      darker: '#003E6A',
    },
...생략...
}

const App = () => {
  return (
    <I18nextProvider i18n={i18n}>
      <MusmaProvider theme={theme}> //--> props로 theme를 전달
        <ApolloClientProvider>
          <QueryClientProvider>
            <Toaster />
            <Suspense fallback={<LoadingScreen type="fallback" />}>
              <RouterProvider router={AppRouter} />
            </Suspense>
          </QueryClientProvider>
        </ApolloClientProvider>
      </MusmaProvider>
    </I18nextProvider>
  )
}

변경 후

1. 다중 테마를 사용하지 않는 경우

위에서 처럼 emotion.d.ts 파일을 만든 뒤, createTheme 함수에 DefaultTheme를 인자로 전달해 theme 객체를 생성합니다.
또는 DefaultTheme과 함께 덮어쓰고 싶은 프로퍼티를 전달해 커스터마이징 합니다.

// 기본 테마를 사용하거나
const theme = createTheme(DefaultTheme)
// 원하는 값을 덮어쓴다
const theme2 =  = createTheme({
  colors: {
    ...DefaultTheme.colors,
    black: { main: 'black', light: 'yellow', dark: 'green', darker: 'blue', lighter: 'white' },
  },
})

MusmaProvider에 전달할 때는 defaultTheme 속성에 Props로 전달해야 합니다

const rootElement = document.getElementById('root')

if (rootElement) {
  createRoot(rootElement).render(
    <MusmaProvider defaultTheme={theme}>
      <ToastContextProvider position="top-right" limit={3} newestOnTop>
        <BrowserRouter>
          <Routes>
            <Route element={<Component />} path="" />
            <Route element={<div>여기서도 토스트 팝업이 잘 뜨는지 봐주십쇼</div>} path="toast" />
          </Routes>
        </BrowserRouter>
      </ToastContextProvider>
    </MusmaProvider>,
  )
}

2. 다중 테마를 사용해야하는 경우

  1. emotion.d.ts 파일에 위와 같이 Theme을 선언해주되, 새롭게 추가할 프로퍼티의 타입을 추가한다. 여기서는 새로운 색상 세트인 palette 프로퍼티의 타입을 선언했습니다. 기존의 colors 프로퍼티는 key의 이름을 blue, black, gray 등 특정 색상과 직접 연결되는 이름으로 정해두었기 때문에 멀티 테마 기능에 적합하지 않습니다.(예를 들어 검은색인 텍스트가 다크모드에서는 흰색으로 나와야한다면, 텍스트 색상을 black.main으로 사용할 경우 라이트 모드에서만 유의미한 이름이 됩니다. 그보다는 priamry, secondary 와 같이 간접적인 이름이 나을듯합니다)
import type { MusmaTheme } from 'src/theme'
import '@emotion/react'

declare module '@emotion/react' {
  // eslint-disable-next-line @typescript-eslint/no-empty-interface
  export interface Theme extends MusmaTheme {
    palette: {
      primary: {
        lighter: string
        light: string
        main: string
        dark: string
        darker: string
      }
      secondary: {
        lighter: string
        light: string
        main: string
        dark: string
        darker: string
      }
      warning: {
        lighter: string
        light: string
        main: string
        dark: string
        darker: string
      }
    }
  }
}
  1. createTheme 함수를 이용해서 필요한 만큼 테마 객체를 만든다. 이때 위에서 정의한 palette 타입의 객체를 인자로 전달해준다.
const theme1 = createTheme({
  palette: {
    primary: {
      lighter: '#DFE7EB',
      light: '#D9E1E5',
      main: '#D0D5DD',
      dark: '#C4D2E0',
      darker: '#BAC7D5',
    },
    secondary: {
      lighter: '#F2F8FB',
      light: '#118EE5',
      main: '#036DB7',
      dark: '#025996',
      darker: '#003E6A',
    },
    warning: {
      lighter: '#FD9009',
      light: '#FFAB43',
      main: '#FD9009',
      dark: '#E76F00',
      darker: '#D86900',
    },
  },
})

const theme2 = createTheme({
  palette: {
    primary: {
      lighter: '#FD9009',
      light: '#FFAB43',
      main: '#FD9009',
      dark: '#E76F00',
      darker: '#D86900',
    },
    secondary: {
      lighter: '#DFE7EB',
      light: '#D9E1E5',
      main: '#D0D5DD',
      dark: '#C4D2E0',
      darker: '#BAC7D5',
    },
    warning: {
      lighter: '#F2F8FB',
      light: '#118EE5',
      main: '#036DB7',
      dark: '#025996',
      darker: '#003E6A',
    },
  },
})
  1. MusmaProvider에 defaultTheme과 함께 themeOptions를 전달한다. themeOptions는 내부적으로 컨텍스트를 통해 하위 컴포넌트에서 접근할 수 있게되며, 앱 실행중 테마를 변경할 때 사용됩니다.
const themeOptions = [
  { label: 'first', value: theme1 },
  { label: 'second', value: theme2 },
]

const rootElement = document.getElementById('root')

if (rootElement) {
  createRoot(rootElement).render(
    <MusmaProvider defaultTheme={theme1} themeOptions={themeOptions}>
      <ToastContextProvider position="top-right" limit={3} newestOnTop>
        <BrowserRouter>
          <Routes>
            <Route element={<Component />} path="" />
            <Route element={<div>여기서도 토스트 팝업이 잘 뜨는지 봐주십쇼</div>} path="toast" />
          </Routes>
        </BrowserRouter>
      </ToastContextProvider>
    </MusmaProvider>,
  )
}
2023-11-08.1.59.29.mov

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant