Basic dark mode implemented
Some checks failed
Deploy Volleyball Dev / deploy (push) Has been cancelled
Some checks failed
Deploy Volleyball Dev / deploy (push) Has been cancelled
This commit is contained in:
parent
50253f20fb
commit
f35bae79bf
@ -3,7 +3,7 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { Menu, X, Volleyball, ChevronDown, Users } from "lucide-react";
|
import { Menu, X, Volleyball, ChevronDown, Users } from "lucide-react";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
import { useAuth } from '@/context/AuthContext';
|
import { useAuth } from '@/context/AuthContext';
|
||||||
import axios from 'axios';
|
import ThemeToggle from './ThemeToggle';
|
||||||
|
|
||||||
|
|
||||||
type Team = {
|
type Team = {
|
||||||
@ -93,6 +93,10 @@ const Navbar = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
|
<ThemeToggle />
|
||||||
|
|
||||||
|
{/* Account Menu */}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="relative ml-4"
|
className="relative ml-4"
|
||||||
onMouseEnter={() => {
|
onMouseEnter={() => {
|
||||||
|
|||||||
22
src/components/ThemeToggle.tsx
Normal file
22
src/components/ThemeToggle.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { useTheme } from "@/context/ThemeContext";
|
||||||
|
import { Sun, Moon } from "lucide-react";
|
||||||
|
|
||||||
|
const ThemeToggle = () => {
|
||||||
|
const { theme, toggleTheme } = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={toggleTheme}
|
||||||
|
className="p-2 rounded-full bg-white dark:bg-gray-800 shadow-md hover:scale-105 transition flex items-center justify-center"
|
||||||
|
aria-label="Toggle Theme"
|
||||||
|
>
|
||||||
|
{theme === "dark" ? (
|
||||||
|
<Sun className="h-5 w-5 text-yellow-400" />
|
||||||
|
) : (
|
||||||
|
<Moon className="h-5 w-5 text-gray-600" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ThemeToggle;
|
||||||
40
src/context/ThemeContext.tsx
Normal file
40
src/context/ThemeContext.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { createContext, useContext, useEffect, useState } from "react";
|
||||||
|
|
||||||
|
type Theme = "light" | "dark";
|
||||||
|
|
||||||
|
interface ThemeContextType {
|
||||||
|
theme: Theme;
|
||||||
|
toggleTheme: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ThemeContext = createContext<ThemeContextType>({
|
||||||
|
theme: "light",
|
||||||
|
toggleTheme: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ThemeProvider = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
const [theme, setTheme] = useState<Theme>("light");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const stored = localStorage.getItem("theme") as Theme | null;
|
||||||
|
const system = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
||||||
|
const initial = stored || system;
|
||||||
|
setTheme(initial);
|
||||||
|
document.documentElement.classList.toggle("dark", initial === "dark");
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const toggleTheme = () => {
|
||||||
|
const newTheme = theme === "dark" ? "light" : "dark";
|
||||||
|
setTheme(newTheme);
|
||||||
|
localStorage.setItem("theme", newTheme);
|
||||||
|
document.documentElement.classList.toggle("dark", newTheme === "dark");
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ThemeContext.Provider value={{ theme, toggleTheme }}>
|
||||||
|
{children}
|
||||||
|
</ThemeContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useTheme = () => useContext(ThemeContext);
|
||||||
@ -1,12 +1,23 @@
|
|||||||
|
import { ReactNode, useEffect } from "react";
|
||||||
import Navbar from "@/components/Navbar";
|
import Navbar from "@/components/Navbar";
|
||||||
import Footer from "@/components/Footer";
|
import Footer from "@/components/Footer";
|
||||||
import { ReactNode } from "react";
|
import AOS from "aos";
|
||||||
|
import "aos/dist/aos.css";
|
||||||
|
|
||||||
type LayoutProps = {
|
type LayoutProps = {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Layout = ({ children }: LayoutProps) => {
|
const Layout = ({ children }: LayoutProps) => {
|
||||||
|
useEffect(() => {
|
||||||
|
AOS.init({
|
||||||
|
duration: 800, // Dauer der Animationen
|
||||||
|
once: true, // nur einmal pro Element
|
||||||
|
offset: 100, // wie weit gescrollt werden muss
|
||||||
|
easing: "ease-out-cubic",
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-white flex flex-col">
|
<div className="min-h-screen bg-white flex flex-col">
|
||||||
<Navbar />
|
<Navbar />
|
||||||
|
|||||||
@ -33,10 +33,13 @@ import Impressum from "./pages/Impressum";
|
|||||||
import Satzung from "./pages/Satzung";
|
import Satzung from "./pages/Satzung";
|
||||||
import Beitraege from "./pages/Beitraege";
|
import Beitraege from "./pages/Beitraege";
|
||||||
|
|
||||||
|
import { ThemeProvider } from "@/context/ThemeContext";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
|
<ThemeProvider>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
<Routes>
|
<Routes>
|
||||||
@ -89,5 +92,6 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
|
|||||||
</Routes>
|
</Routes>
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
|
</ThemeProvider>
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
);
|
);
|
||||||
Loading…
Reference in New Issue
Block a user