CSS Styling and Dark theme

homewiz·2025년 5월 15일

React & typescript

목록 보기
12/18

intro


tailwind를 이용 하여 디자인을 적용 해보자

Style

GNB.tsx

import React, { useEffect, useState } from "react";
import { NavLink } from "react-router-dom";

const menu = [
  {
    label: "📊 DASHBOARD",
    path: "/"
  },
  {
    label: "👥 Category2",
    path: "/category2",
    children: [
      { label: "page1", path: "/users/login" },
      { label: "", path: "/users/betting" },
      { label: "결제 분석", path: "/users/payment" },
      { label: "퍼널 분석", path: "/users/funnel" }
    ]
  },
  {
    label: "📁 이벤트 로그",
    path: "/events"
  },
  {
    label: "⚙️ 시스템 설정",
    path: "/system"
  },
  {
    label: "🧠 고급 분석",
    path: "/advanced"
  }
];

export default function GNB() {
  const [isDark, setIsDark] = useState(() => document.documentElement.classList.contains("dark"));

  const toggleDark = () => {
    const newVal = !isDark;
    document.documentElement.classList.toggle("dark", newVal);
    localStorage.setItem("theme", newVal ? "dark" : "light");
    setIsDark(newVal);
  };

  useEffect(() => {
    const saved = localStorage.getItem("theme");
    if (saved === "dark") {
      document.documentElement.classList.add("dark");
      setIsDark(true);
    }
  }, []);

  return (
    <header className="relative z-50 flex items-center justify-between w-full h-16 px-8 text-gray-900 bg-white border-b border-gray-200 shadow dark:bg-gray-900 dark:border-gray-700 dark:text-white">
      <nav className="flex items-center space-x-6">
        <span className="text-lg font-bold tracking-tight">🎯 유저 분석툴</span>
        {menu.map((item, idx) =>
          item.children ? (
            <div key={idx} className="relative group">
              <span className="px-3 py-2 rounded cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800">{item.label}</span>
              <div className="absolute top-full left-0 mt-1 bg-white dark:bg-gray-800 shadow-lg rounded border dark:border-gray-700 opacity-0 group-hover:opacity-100 pointer-events-none group-hover:pointer-events-auto transition-opacity z-10 min-w-[160px]">
                {item.children.map((child, i) => (
                  <NavLink key={i} to={child.path} className={({ isActive }) => `block px-4 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-700 ${isActive ? "font-semibold" : ""}`}>
                    {child.label}
                  </NavLink>
                ))}
              </div>
            </div>
          ) : (
            <NavLink key={idx} to={item.path} className={({ isActive }) => `px-3 py-2 rounded hover:bg-gray-100 dark:hover:bg-gray-800 ${isActive ? "font-semibold" : ""}`}>
              {item.label}
            </NavLink>
          )
        )}
      </nav>

      <div className="flex items-center space-x-4 text-sm">
        <button onClick={toggleDark} className="px-3 py-1 text-black bg-gray-200 rounded dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 dark:text-white">
          {isDark ? "🌙 다크" : "☀️ 라이트"}
        </button>
        <span className="text-gray-500 dark:text-gray-300">👤 운영자</span>
        <button className="px-3 py-1 text-white bg-red-500 rounded hover:bg-red-600">로그아웃</button>
      </div>
    </header>
  );
}

App.tsx

import React from "react";

import GNB from "@/components/Navigations/GNB";
import Routes from "@/config/Routes";
import { BrowserRouter as Router } from "react-router-dom";

const App = () => {
  return (
    <div className="flex flex-row justify-center min-h-screen">
      <div className="flex flex-col flex-1 ">
        <Router>
          <GNB />
          <div className="flex flex-1 w-full text-black transition-colors duration-300 bg-white dark:bg-gray-900 dark:text-gray-100">
            <Routes />
          </div>
        </Router>
      </div>
    </div>
  );
};

export default App;

SideNavBar.tsx

./src/components/Navigations/SideNavBar.tsx

import React from "react";
import { NavLink } from "react-router-dom";

const MenuData = [
  { label: "page1", path: "/category4/page1" },
  { label: "page2", path: "/category4/page2" },
  { label: "page3", path: "/category4/page3" },
  { label: "page4", path: "/category4/page4" }
];

export default function SideNavBar() {
  return (
    <aside className="w-48 border-r border-gray-200 dark:border-gray-700">
      <nav className="">
        {MenuData.map((item, idx) => (
          <NavLink key={idx} to={item.path} className={({ isActive }) => `block px-3 py-2 rounded hover:bg-gray-100 dark:hover:bg-gray-800 ${isActive ? "font-bold bg-gray-100 dark:bg-gray-800 dark:text-white" : ""}`}>
            {item.label}
          </NavLink>
        ))}
      </nav>
    </aside>
  );
}

Category4

./src/pages/Category4/index.tsx

import React from "react";

import SideNavBar from "@/components/Navigations/SideNavBar";
import PageLayout from "@/components/PageLayout";

import { Outlet } from "react-router-dom";

const Category4 = () => {
  return (
    <PageLayout className="flex-row p-0">
      <SideNavBar />
      <Outlet />
    </PageLayout>
  );
};

export default Category4;

Dark theme

tailwind.config.js

// tailwind.config.js
module.exports = {
  darkMode: "class", // or "media"
  content: [
    "./src/**/*.{js,ts,jsx,tsx}", // 👈 필수
    "./public/index.html"
  ],
  theme: {
    extend: {}
  },
  plugins: []
};

tailwind.css

./src/styles/tailwind.css

@import "tailwindcss";

@custom-variant dark (&:where(.dark, .dark *));

0개의 댓글