mirror of
https://github.com/freedbygrace/ActiveDirectoryManager.git
synced 2026-08-06 09:07:41 +00:00
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:
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
* {
|
||||
|
||||
@@ -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
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user