Add dark mode and theme switching functionality to the application.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 705f2157-ef97-4fbd-89e4-8c7f2ecaea90
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7ed01c5f-a82d-405a-b728-b2e3d127c60c/69154111-0a6b-4eae-ac42-425c5260b653.jpg
This commit is contained in:
alphaeusmote
2025-04-09 00:43:36 +00:00
parent 98159c0d30
commit b466886372
13 changed files with 1299 additions and 7 deletions
+40
View File
@@ -0,0 +1,40 @@
import * as React from "react";
import { Moon, Sun, Laptop } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { useTheme } from "@/hooks/use-theme";
export function ThemeToggle() {
const { theme, setTheme } = useTheme();
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="relative h-9 w-9 rounded-md">
<Sun className="h-5 w-5 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-5 w-5 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme("light")}>
<Sun className="mr-2 h-4 w-4" />
<span>Light</span>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("dark")}>
<Moon className="mr-2 h-4 w-4" />
<span>Dark</span>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("system")}>
<Laptop className="mr-2 h-4 w-4" />
<span>System</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}
+66
View File
@@ -0,0 +1,66 @@
import { createContext, useContext, useEffect, useState, ReactNode } from 'react';
type Theme = 'dark' | 'light' | 'system';
interface ThemeContextType {
theme: Theme;
setTheme: (theme: Theme) => void;
}
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
interface ThemeProviderProps {
children: ReactNode;
defaultTheme?: Theme;
storageKey?: string;
}
export function ThemeProvider({
children,
defaultTheme = 'system',
storageKey = 'ui-theme',
...props
}: ThemeProviderProps) {
const [theme, setTheme] = useState<Theme>(
() => (localStorage.getItem(storageKey) as Theme) || defaultTheme
);
useEffect(() => {
const root = window.document.documentElement;
root.classList.remove('light', 'dark');
if (theme === 'system') {
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light';
root.classList.add(systemTheme);
return;
}
root.classList.add(theme);
}, [theme]);
const value = {
theme,
setTheme: (theme: Theme) => {
localStorage.setItem(storageKey, theme);
setTheme(theme);
},
};
return (
<ThemeContext.Provider value={value} {...props}>
{children}
</ThemeContext.Provider>
);
}
export const useTheme = (): ThemeContextType => {
const context = useContext(ThemeContext);
if (context === undefined)
throw new Error('useTheme must be used within a ThemeProvider');
return context;
};
+26
View File
@@ -88,6 +88,32 @@
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--ring: 142.4 71.8% 29.2%;
--sidebar-background: 20 14.3% 4.1%;
--sidebar-foreground: 0 0% 95%;
--sidebar-primary: 221.2 83.2% 53.3%;
--sidebar-primary-foreground: 210 40% 98%;
--sidebar-accent: 240 3.7% 15.9%;
--sidebar-accent-foreground: 0 0% 98%;
--sidebar-border: 240 3.7% 15.9%;
--sidebar-ring: 142.4 71.8% 29.2%;
}
/* Smooth theme transition */
*,
::before,
::after {
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 300ms;
}
/* Exclude transitions for specific elements that shouldn't animate */
.no-transition,
.no-transition *,
.no-transition::before,
.no-transition::after {
transition: none !important;
}
* {
+7 -4
View File
@@ -32,6 +32,7 @@ import {
} from "lucide-react";
import { useMobile } from "@/hooks/use-mobile";
import { useToast } from "@/hooks/use-toast";
import { ThemeToggle } from "@/components/theme-toggle";
type MenuItem = {
title: string;
@@ -157,7 +158,7 @@ export function DashboardLayout({ children, title, description }: DashboardLayou
<div className="flex h-screen overflow-hidden bg-background">
{/* Sidebar */}
<div
className={`w-64 h-full bg-white shadow-md z-10 flex-shrink-0 transition-all duration-300 ease-in-out ${
className={`w-64 h-full dark:bg-sidebar bg-white shadow-md z-10 flex-shrink-0 transition-all duration-300 ease-in-out ${
sidebarOpen ? "" : "-ml-64"
} ${isMobile ? "absolute" : "relative"}`}
>
@@ -206,7 +207,7 @@ export function DashboardLayout({ children, title, description }: DashboardLayou
{/* Main Content */}
<div className="flex-1 flex flex-col overflow-hidden">
{/* Top App Bar */}
<header className="h-16 bg-white shadow-sm flex items-center justify-between px-4 z-10">
<header className="h-16 bg-white dark:bg-sidebar shadow-sm flex items-center justify-between px-4 z-10">
<div className="flex items-center">
<Button
variant="ghost"
@@ -219,6 +220,8 @@ export function DashboardLayout({ children, title, description }: DashboardLayou
</div>
<div className="flex items-center space-x-4">
<ThemeToggle />
<Button variant="ghost" size="icon">
<HelpCircle className="h-5 w-5" />
</Button>
@@ -265,8 +268,8 @@ export function DashboardLayout({ children, title, description }: DashboardLayou
{/* Main Content Area */}
<main className="flex-1 overflow-y-auto p-6 bg-background">
<div className="mb-6">
<h1 className="text-2xl font-medium text-gray-800">{title}</h1>
{description && <p className="text-gray-600">{description}</p>}
<h1 className="text-2xl font-medium text-gray-800 dark:text-gray-100">{title}</h1>
{description && <p className="text-gray-600 dark:text-gray-400">{description}</p>}
</div>
{children}
+6 -3
View File
@@ -3,12 +3,15 @@ import { QueryClientProvider } from "@tanstack/react-query";
import App from "./App";
import "./index.css";
import { AuthProvider } from "./hooks/use-auth";
import { ThemeProvider } from "./hooks/use-theme";
import { queryClient } from "./lib/queryClient";
createRoot(document.getElementById("root")!).render(
<QueryClientProvider client={queryClient}>
<AuthProvider>
<App />
</AuthProvider>
<ThemeProvider defaultTheme="system" storageKey="ad-management-theme">
<AuthProvider>
<App />
</AuthProvider>
</ThemeProvider>
</QueryClientProvider>
);