데브코스

[14주차 - DAY3] 도서 정보 사이트 - 테마 스위치

미안하다 강림이 좀 늦었다 2024. 5. 29. 15:40

 

 

styled-components

설치

npm i styled-components

 

style/theme.ts

light 모드와 dark 모드의 css를 다음과 같이 지정한다.

export type ThemeName = 'light' | 'dark';
type ColorKey = 'primary' | 'background' | 'secondary' | 'third';

interface Theme {
    name: ThemeName,
    color: Record<ColorKey, string>;
}

export const light: Theme = {
    name: 'light',
    color: {
        primary: 'brown',
        background: 'lightgray',
        secondary: 'blue',
        third: 'green'
    }
};

export const dark: Theme = {
    name: 'dark',
    color: {
        primary: 'coral',
        background: 'midnightblue',
        secondary: 'darkblue',
        third: 'darkgreen'
    }
}

export const getTheme = (themeName: ThemeName): Theme => {
    switch (themeName) {
        case 'light':
            return light;
        case 'dark':
            return dark;
    }
}

 

style/global.ts

전역 스타일을 지정하는 파일이다.

 props로 테마 이름을 받는다. GlobalStyle을 export 했기 때문에 외부에서 컴포넌트로 사용할 수 있다.

import "sanitize.css";
import { createGlobalStyle } from "styled-components";
import { ThemeName } from "./theme";

interface Props {
    themeName: ThemeName;
}

export const GlobalStyle = createGlobalStyle<Props>`
    body {
        margin: 0;
        padding: 0;
        background-color: ${(props) => props.themeName === 'light' ? 'white' : 'black'};

    h1 {
        margin: 0;
    }

    * {
        color: ${(props) => props.themeName === 'light' ? 'black' : 'white'}
    }
`;

 

 

테마 스위치

App.tsx

BookStoreThemeProvider로 다른 컴포넌트들을 감싸서 테마가 적용될 수 있도록 한다.

import Layout from "./components/layout/Layout"
import Home from "./pages/Home"
import ThemeSwitcher from "./components/header/ThemeSwitcher"
import { BookStoreThemeProvider } from "./context/themeContext"

function App() {
  return (
    <>
      <BookStoreThemeProvider>
        <ThemeSwitcher />
        <Layout>
          <Home />
        </Layout>
      </BookStoreThemeProvider>
    </>
  )
}

 

context/themeContext

ThemeContext의 하위 컴포넌트에서도 현재 테마 이름과 테마를 토글 할 수 있는 함수에 접근할 수 있도록 ThemeContext를 선언한다.

import { ReactNode, createContext, useEffect, useState } from "react";
import { ThemeName, getTheme } from "../style/theme";
import { ThemeProvider } from "styled-components";
import { GlobalStyle } from "../style/global";

const DEFAULT_THEME_NAME = 'light';
const THEME_LOCAL_STORAGE_KEY = 'book_store_theme';

interface State {
    themeName: ThemeName;
    toggleTheme: () => void;
}

export const state = {
    themeName: DEFAULT_THEME_NAME as ThemeName,
    toggleTheme: () => { }
};

export const ThemeContext = createContext<State>(state);
...

ThemeContext.Provider의 value를 현재 테마 이름과 테마를 토글 할 수 있는 함수로 설정한다.

export const BookStoreThemeProvider = ({ children }: { children: ReactNode }) => {
    const [themeName, setThemeName] = useState<ThemeName>('light');

    const toggleTheme = () => {
        setThemeName(themeName === 'light' ? 'dark' : 'light');
        localStorage.setItem(
            THEME_LOCAL_STORAGE_KEY,
            themeName === 'light' ? 'dark' : 'light'
        )
    }

    useEffect(() => {
        const savedThemeName = localStorage.getItem(THEME_LOCAL_STORAGE_KEY) as ThemeName;
        setThemeName(savedThemeName || DEFAULT_THEME_NAME);
    }, [])

    return (
        <ThemeContext.Provider value={{ themeName, toggleTheme }}>
            <ThemeProvider theme={getTheme(themeName)}>
                <GlobalStyle themeName={themeName} />
                {children}
            </ThemeProvider>
        </ThemeContext.Provider>
    )
}

 

components/header/ThemeSwitcher.tsx

토글 할 수 있는 버튼은 App.tsx의 BookStoreThemeProvider의 하위 컴포넌트인 ThemeSwitcher에 존재한다.

버튼이 클릭되었을 때 테마가 바뀔 수 있도록 ThemeContext의 toggleTheme 함수를 onClick 이벤트에 지정한다.

import { useContext } from "react";
import { ThemeContext } from "../../context/themeContext";

function ThemeSwitcher() {
    const { themeName, toggleTheme } = useContext(ThemeContext);

    return (
        <button onClick={toggleTheme}>
            {themeName}
        </button>
    )
}

light
dark

 

배운 점

  • styled-components를 사용하니까 동적 스타일링이 편해지지만 js 파일이 복잡하고 styled/일반 컴포넌트의 구분이 어려웠다.
  • contextAPI를 사용하여 테마를 변경하는 방법을 학습했다.