ํ‹ฐ์Šคํ† ๋ฆฌ ๋ทฐ

๋ฆฌ์•กํŠธ๋กœ ๋‹คํฌ๋ชจ๋“œ ๊ตฌํ˜„ํ•˜๊ธฐ

๋‹คํฌ๋ชจ๋“œ๋Š” ๋‹ค์–‘ํ•œ ๋ธŒ๋žœ๋“œ๋“ค๊ณผ ์›น ์‚ฌ์ดํŠธ์—์„œ ์ด์ œ๋Š” ๊ฑฐ์˜ ํ•„์ˆ˜๋ผ๊ณ  ๋ด๋„ ๋  ์ •๋„๋กœ ํ”ํ•˜๊ฒŒ ์ ์šฉ๋˜๋Š” ๊ธฐ๋Šฅ ์ค‘ ํ•˜๋‚˜์ด๋‹ค.
๋‚˜ ์—ญ์‹œ ํœด๋Œ€ํฐ, ๋…ธํŠธ๋ถ, ์ฑ„ํŒ… ์–ดํ”Œ ๋“ฑ์—์„œ ๋‹คํฌ๋ชจ๋“œ๋ฅผ ๋งค์šฐ ์• ์šฉํ•˜๊ณ  ์žˆ๊ณ  ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž๋ผ๋ฉด ์ด์ œ๋Š” ํ•„์ˆ˜ ๊ธฐ๋Šฅ์ด ๋œ ๋‹คํฌ๋ชจ๋“œ๋ฅผ ๊ตฌํ˜„ํ•  ์ค„ ์•Œ์•„์•ผํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•ด์„œ ์ด๋ฒˆ ๊ฐœ์ธ ํ”„๋กœ์ ํŠธ ๊ธฐํš์— ์ถ”๊ฐ€ํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.

๐Ÿค“ ์ง„ํ–‰ ์ˆœ์„œ

  1. ๋ผ์ดํŠธ๋ชจ๋“œ, ๋‹คํฌ๋ชจ๋“œ ๋””์ž์ธ
  2. ๋ผ์ดํŠธ/๋‹คํฌ theme.js ์„ธํŒ…
  3. ๋‹คํฌ๋ชจ๋“œ ๊ตฌํ˜„
    • ํ† ๊ธ€๋ฒ„ํŠผ
    • ์ƒํƒœ ๊ด€๋ฆฌ
    • ๋ชจ๋“œ ์œ ์ง€ ๊ธฐ๋Šฅ

๐ŸŽจ ๋””์ž์ธ (feat. Figma)

์ „์ฒด์ ์ธ ๋ผ์ดํŠธ/๋‹คํฌ ๋ชจ๋“œ์˜ ๋””์ž์ธ์€ ์œ„์™€ ๊ฐ™์ด ๋งŒ๋“ค์–ด๋ณด์•˜๋‹ค.
๊ฐ ์ปดํฌ๋„ŒํŠธ๋“ค์€ ํˆฌ๋ช…๋„๋ฅผ ์ฃผ๊ณ  ์ „์ฒด์ ์ธ ์ƒ‰๊ฐ์€ ํŒŒ๋ž€๋น›์ด ๋ˆ๋‹ค. ๋‹คํฌ๋ชจ๋“œ์ผ ๋•Œ์—๋Š” ์™„์ „ํžˆ ๊ฒ€์€์ƒ‰์ด ์•„๋‹ˆ๋ผ ์•„์ฃผ ์–ด๋‘์šด ๋‚จ์ƒ‰๊ณ„์—ด๋กœ ๊ณจ๋ž๋‹ค.
๋‹คํฌ๋ชจ๋“œ์˜ ๊ฒฝ์šฐ, ํฐํŠธ ์ปฌ๋Ÿฌ๋ฅผ #FFFFFF๋กœ ํ•˜๋‹ˆ๊นŒ ๋ˆˆ์ด ์•„ํŒŒ์„œ ์•ฝ๊ฐ„ ํšŒ์ƒ‰๋น›์ด ๋“ค์–ด๊ฐ€๊ฒŒ #FAFAFA๋กœ ๋‚˜์ค‘์— ์ˆ˜์ •ํ–ˆ๋‹ค.

๊ท€์—ฌ์šด ๋‹คํฌ๋ชจ๋“œ ํ† ๊ธ€๋ฒ„ํŠผ๋„ ๊ฐ„๋‹จํžˆ ๋””์ž์ธํ–ˆ๋‹ค!
๋””์ž์ธ์ด๋ผ๊ณ  ํ•˜๊ธฐ๋„ ๋ฏผ๋งํ•˜์ง€๋งŒ..๐Ÿ˜… ๊ฐ ์ปดํฌ๋„ŒํŠธ์— ์ค€ ํˆฌ๋ช…๋„๋ฅผ ํ† ๊ธ€๋ฒ„ํŠผ์—๋„ ๋˜‘๊ฐ™์ด ์ค˜์„œ ํ†ต์ผ๊ฐ์„ ์ฃผ์—ˆ๋‹ค. ๊ตฌ๊ธ€๋งํ•ด๋ณด๋‹ˆ ๋‹ค๋ฅธ ๋ถ„๋“ค์€ ๋˜๊ฒŒ ๋ฉ‹์žˆ๊ฒŒ ์ž˜ ๋งŒ๋“œ์…จ๋˜๋ฐ ๋‚œ ์ผ๋‹จ ์ดˆ์ดˆ๊ฐ„๋‹จ์œผ๋กœใ…Žใ…Ž..


โš™๏ธ theme.js ์„ธํŒ…


export const lightTheme = {
  fontSizes,
  colors: {
    colorMain: "#ffffff4d",
    colorBg: "#F7F7FA",
    colorMainFont: "#000000",
    colorSkyBlue: "#E6F4FE",
    colorBlue: "#0057BC",
    colorDisabled: "#C4C4C4",
    colorGray: "#D9D9D9",
    colorDarkGray: "#a5a4a4",
    colorWhite: "#FFFFFF",
    colorRed: "#FC585A",
    colorDiRed: "#FE9090",
    colorShadow: "0 3px 6px rgba(0, 0, 0, .16)",
    colorDarkShadow: "0 3px 6px rgba(0, 0, 0, .5)",
  },
};

export const darkTheme = {
  fontSizes,
  colors: {
    colorMain: "#585D6E4d",
    colorBg: "#1B1D25",
    colorMainFont: "#FAFAFA",
    colorSkyBlue: "#E6F4FE",
    colorBlue: "#3190ff",
    colorDisabled: "#C4C4C4",
    colorGray: "#D9D9D9",
    colorDarkGray: "#a5a4a4",
    colorWhite: "#FFFFFF",
    colorRed: "#FC585A",
    colorDiRed: "#FE9090",
    colorShadow: "0 4px 7px rgb(68 68 68 / .8)",
    colorDarkShadow: "0 3px 6px rgba(255, 255, 255, .5)",
  },
};

๋ผ์ดํŠธ๋ชจ๋“œ์™€ ๋‹คํฌ๋ชจ๋“œ์— ๊ฐ๊ฐ ์‚ฌ์šฉํ•  theme์„ ์„ค์ •ํ•ด์ฃผ์—ˆ๋‹ค.

theme ์ ์šฉ

App.js

const [themeMode, setThemeMode] = useState(lightTheme);
const theme = themeMode === "lightTheme" ? lightTheme : darkTheme;

return (
   <>
     <ThemeProvider theme={theme}>
       <Globalstyle />
       <Routes>
         <Route
           path="/home"
           element={<Home themeMode={themeMode} />}
          />
         <Route path="/signup" element={<Signup />} />
         <Route path="/" element={<Login />} />
       </Routes>
     </ThemeProvider>
   </>
);

ThemeProvider๋ฅผ ์ตœ์ƒ์œ„ ํŒŒ์ผ์ธ App.js์— ์ ์šฉ์‹œํ‚จ๋‹ค.
์ด๋•Œ, props๋กœ theme๋ฅผ ์ „๋‹ฌ๋ฐ›๋Š”๋ฐ theme๋Š” themeMode๊ฐ€ ๋ผ์ดํŠธ๋ฉด ๋ผ์ดํŠธ๋ชจ๋“œ ์ปฌ๋Ÿฌ์นฉ์„ ํ• ๋‹น๋ฐ›๊ณ  ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ๋‹คํฌ๋ชจ๋“œ ์ปฌ๋Ÿฌ์นฉ์„ ํ• ๋‹น ๋ฐ›๋Š”๋‹ค. (์ปฌ๋Ÿฌ์นฉ : ์œ„์—์„œ ์„ธํŒ…ํ•ด์ค€ theme.js)

์ตœ์ƒ์œ„ ํŒŒ์ผ์ธ App.js์— ThemeProvider๋ฅผ ์ ์šฉ์‹œํ‚ค๋ฉด ์ด์ œ ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

ํ† ๊ธ€ ๋ฒ„ํŠผ ๊ตฌํ˜„

import React from "react";
import { ThemeModeWrapper } from "./styled";

export const ThemeModeButton = ({ toggleTheme, themeMode }) => {
  return (
    <ThemeModeWrapper onClick={toggleTheme}>
      {themeMode === "lightTheme" ? "๐ŸŒ" : "๐ŸŒš"}
    </ThemeModeWrapper>
  );
};

// styled.jsx
import { css, styled } from "styled-components";

export const ThemeModeWrapper = styled.button`
  position: absolute;
  top: 0;
  right: 0;

  width: 80px;
  margin: 10px;
  border: none;
  border-radius: 10px;

  ${({ theme }) => {
    return css`
      background-color: ${(props) => props.theme.colors.colorMain};
      box-shadow: ${(props) => props.theme.colors.colorShadow};
    `;
  }}
`;

๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด onClick ์ด๋ฒคํŠธ๋ฅผ ํ†ตํ•ด toggleTheme ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋˜๋„๋ก ํ–ˆ๋‹ค.
๊ทธ๋ฆฌ๊ณ  ํ˜„์žฌ themeMode๋„ props๋กœ ์ „๋‹ฌ๋ฐ›์•„ ๋ผ์ดํŠธ๋ชจ๋“œ์ด๋ฉด ๐ŸŒ์„ ํ‘œ์‹œํ•˜๊ณ , ๋‹คํฌ๋ชจ๋“œ์ด๋ฉด ๐ŸŒš๋ฅผ ํ‘œ์‹œํ•˜๋„๋ก ํ•˜๊ณ  ๋ฒ„ํŠผ์˜ ๋ฐฐ๊ฒฝ๊ณผ ๊ทธ๋ฆผ์ž ์Šคํƒ€์ผ๋„ ์•ž์„œ ์ •์˜ํ–ˆ๋˜ theme์— ๋งž๊ฒŒ ๋ณ€๊ฒฝ๋˜๋„๋ก ์ž‘์„ฑํ–ˆ๋‹ค.

ํ† ๊ธ€ ๋ฒ„ํŠผ ํ•จ์ˆ˜ ๊ตฌํ˜„ - ์ƒํƒœ๊ด€๋ฆฌ

const theme = themeMode === "lightTheme" ? lightTheme : darkTheme;

const toggleTheme = () => {
  if (themeMode === "lightTheme") {
    setThemeMode("darkTheme");
    window.localStorage.setItem("theme", "darkTheme");
  } else {
    setThemeMode("lightTheme");
    window.localStorage.setItem("theme", "lightTheme");
  }
};

ํ† ๊ธ€๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ๋™์ž‘ํ•  toggleTheme ํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„ํ–ˆ๋‹ค.
ํ˜„์žฌ themeMode๊ฐ€ "lightTheme"์ธ์ง€ "darkTheme"์ธ์ง€ ํŒ๋ณ„ํ•˜๊ณ , ๊ทธ์— ๋”ฐ๋ผ ์ƒํƒœ๊ฐ’์„ ์—…๋ฐ์ดํŠธํ•˜๊ณ  ๋กœ์ปฌ ์Šคํ† ๋ฆฌ์ง€์—๋„ ์ €์žฅํ•œ๋‹ค.

๋‹คํฌ๋ชจ๋“œ ์œ ์ง€ - localStorage

const localThemeMode = window.localStorage.getItem("theme" || "lightTheme");
const [themeMode, setThemeMode] = useState(localThemeMode);

๊ทธ๋ฆฌ๊ณ  ๊ธฐ์กด์—๋Š” themeMode์˜ ์ดˆ๊นƒ๊ฐ’์ด "lightTheme"์˜€๋Š”๋ฐ ๋กœ์ปฌ์Šคํ† ๋ฆฌ์ง€์— ์ €์žฅ๋œ ๊ฐ’์„ ๊ฐ€์ ธ์™€์„œ themeMode์˜ ์ดˆ๊นƒ๊ฐ’์œผ๋กœ ๋„ฃ์–ด์ฃผ์—ˆ๋‹ค. ์ด๋ ‡๊ฒŒํ•˜๋ฉด ๋‹คํฌ๋ชจ๋“œ๋ฅผ ํ•˜๊ณ  ์ƒˆ๋กœ๊ณ ์นจ์„ ํ–ˆ์„ ๋•Œ์—๋„ ๋กœ์ปฌ์— ์ €์žฅ๋œ ๊ฐ’์„ ๊ฐ€์ ธ์™€์„œ ํ˜„์žฌ themeMode์˜ ์ƒํƒœ๊ฐ’์œผ๋กœ ๋„ฃ์–ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ๊ทธ ๊ธฐ๋Šฅ์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.


โœจ ๊ฒฐ๊ณผ


๐Ÿง ๋ฆฌํŒฉํ† ๋ง์„ ํ•œ๋‹ค๋ฉด?

โœ”๏ธ context, redux, recoil ๋“ฑ์„ ์ด์šฉํ•ด ์ƒํƒœ๊ด€๋ฆฌํ•ด๋ณด๊ธฐ
โœ”๏ธ ์ปค์Šคํ…€ ํ›… ๋งŒ๋“ค์–ด๋ณด๊ธฐ

๋Œ“๊ธ€