본문 바로가기
타입스크립트로 함께하는 웹 풀 사이클 개발(React, Node.js)/TIL

다크모드 만들기

by 슈크림 붕어빵 2024. 2. 29.

 

themeContext.tsx

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_LOVALSTORAGE_KEY = "book_store_theme";
// 테마상태와 토글 함수 interface 정의
interface State {
  themeName: ThemeName;
  toggleTheme: () => void;
}
// createContext할 때 default값이 필요해서 생성
export const state = {
  themeName: "light" as ThemeName,
  toggleTheme: () => {},
};

// context 만들기(테마상태/ 테마 바꾸는 함수 포함), export
// default value가 필요해서 state를 만들어서 넣은 것
// 사용할 때는 value로 값을 넣어줄 것임.
export const ThemeContext = createContext<State>(state);

// 테마 상태 총괄 provider , export
export const BookStoreProvider = ({ children }: { children: ReactNode }) => {
  //   기본 값 설정, state로 테마 상태 관리
  const [themeName, setThemeName] = useState<ThemeName>("light");

  // reload될 때
  // 로컬 스토리지에서 THEME_LOVALSTORAGE_KEY를 키로 가진 테마 상태 가져옴
  // 로컬 스토리지에서 가져온 게 있다면 테마 상태 setState
  useEffect(() => {
    const savedThemeName = localStorage.getItem(
      THEME_LOVALSTORAGE_KEY
    ) as ThemeName;

    setThemeName(savedThemeName || DEFAULT_THEME_NAME);
  }, []);
  // 토글하고 setState & 로컬 스토리지에 저장
  const toggleTheme = () => {
    setThemeName(themeName === "light" ? "dark" : "light");
    localStorage.setItem(
      THEME_LOVALSTORAGE_KEY,
      themeName === "light" ? "dark" : "light"
    );
  };
  return (
    <ThemeContext.Provider value={{ themeName, toggleTheme }}>
      <ThemeProvider theme={getTheme(themeName)}>
        <GlobalStyle themeName={themeName} />
        {children}
      </ThemeProvider>
    </ThemeContext.Provider>
  );
};

// ThemeContext.Provider
// ThemeContext는 만든 context, provider로 뿌려줌.  value로 테마상태, 토글함수 넣어줌. (interface 따름)

// themeProvider
// theme 파일에서 state에 저장된 themeName으로 가져와서 themeprovider 설정
// 상태를 통해 themeName props로 넘겨주기
// styled-components 이용
// 없으면 하위 파일에서 theme 파일에서 맞는 테마 색상을 못찾음.

// 현재 테마에 따른 테마 뿌려줌? => theme 파일 알아서 찾아서?

// GlobalStyle
// global 파일에서 props에 따라 globalstyle 만들어서 export
// styled-components 에서 createGlobalStyle 이용

// ThemeSwitcher
// setThemeName 이용하여 themeName 상태 바꾸는 역할

 

 

global.ts

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

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

 

 

theme.ts

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

interface Theme {
  name: ThemeName;
  color: Record<ColorKey, string>;
}
// Record<>: 매핑으로 생각하면 된다.
// 모든 타입으로 생성됨.
// 인터페이스 구현시 ColorKey의 모든 값을 키값으로 만들어야한다.
// 이 유틸리티는 타입의 프로퍼티를 다른 타입에 매핑 시키는데 사용될 수 있습니다.

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

export const dark: Theme = {
  name: "dark",
  color: {
    primary: "coral",
    background: "midnightblue",
    secondary: "yellow",
    third: "blue",
  },
};

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

 

themeSwitcher.tsx

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

const ThemeSwitcher = () => {
  // context에서 가져옴
  const { themeName, toggleTheme } = useContext(ThemeContext);

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

export default ThemeSwitcher;

 

app.tsx

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

function App() {
  return (
    <>
      <BookStoreProvider>
        <ThemeSwitcher />
        <Layout children={<Home />} />
      </BookStoreProvider>
    </>
  );
}

export default App;