Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/components/layout/Header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { doSignOut } from '/src/firebase/auth'
import { useState } from 'react'
import styles from '../../styles/layout.module.css'
import logo from '../../assets/logo.svg'
import { DarkModeButton } from '../ui/ThemeToggle.jsx'

export default function Header() {
const [isSignInOpen, setIsSignInOpen] = useState(false)
Expand All @@ -30,6 +31,7 @@ export default function Header() {
<img src={logo} alt="DragMe Logo" className={styles.logo} />
</div>
<div className={styles.headerButtons}>
<DarkModeButton />
{!userLoggedIn ? (
<>
<SignInButton onClick={openSignIn} />
Expand Down
8 changes: 7 additions & 1 deletion src/components/ui/Button.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,11 @@ function SignInButton({ onClick }) {
<button onClick={onClick}>Sign Up</button>
)
}

function DarkModeButton({ onClick }) {
return (
<button onClick={onClick}>Dark Mode</button>
)
}

export { SignInButton, SignUpButton }
export { SignInButton, SignUpButton, DarkModeButton }
42 changes: 42 additions & 0 deletions src/components/ui/ThemeToggle.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { useTheme } from '../../context/ThemeContext';

export function DarkModeButton() {
const { isDarkMode, toggleDarkMode } = useTheme();

return (
<div
onClick={toggleDarkMode}
style={{
position: 'relative',
width: '60px',
height: '30px',
backgroundColor: isDarkMode ? '#4a5568' : '#e2e8f0',
borderRadius: '15px',
cursor: 'pointer',
transition: 'background-color 0.3s ease',
display: 'flex',
alignItems: 'center',
padding: '2px',
border: '2px solid var(--buttonBorder)'
}}
>
<div
style={{
width: '22px',
height: '22px',
backgroundColor: 'white',
borderRadius: '50%',
transform: isDarkMode ? 'translateX(30px)' : 'translateX(0px)',
transition: 'transform 0.3s ease',
boxShadow: '0 2px 4px rgba(0,0,0,0.2)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '12px'
}}
>
{isDarkMode ? '🌙' : '☀️'}
</div>
</div>
);
}
24 changes: 22 additions & 2 deletions src/context/ThemeContext.jsx
Original file line number Diff line number Diff line change
@@ -1,2 +1,22 @@
// ThemeContext - Manages the light/dark mode theme
// TODO: Implement theme context provider
import { createContext, useContext } from 'react';
import { useDarkMode } from '../hooks/useDarkMode';

const ThemeContext = createContext();

export function ThemeProvider({ children }) {
const { isDarkMode, toggleDarkMode } = useDarkMode();

return (
<ThemeContext.Provider value={{ isDarkMode, toggleDarkMode }}>
{children}
</ThemeContext.Provider>
);
}

export function useTheme() {
const context = useContext(ThemeContext);
if (context === undefined) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
}
48 changes: 46 additions & 2 deletions src/hooks/useDarkMode.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,46 @@
// useDarkMode hook - Manages dark mode state and local storage
// TODO: Implement dark mode hook with theme switching
import { useState, useEffect } from 'react';

export function useDarkMode() {
// Initialize state from localStorage or default to system preference
const [isDarkMode, setIsDarkMode] = useState(() => {
const saved = localStorage.getItem('darkMode');
if (saved !== null) {
return JSON.parse(saved);
}
// Default to system preference if no saved preference
return window.matchMedia('(prefers-color-scheme: dark)').matches;
});

// Update localStorage and document class when theme changes
useEffect(() => {
localStorage.setItem('darkMode', JSON.stringify(isDarkMode));

// Add or remove 'dark' class to document for CSS targeting
if (isDarkMode) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
}, [isDarkMode]);

// Listen for system theme changes
useEffect(() => {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');

const handleChange = (e) => {
// Only update if user hasn't set a preference
if (localStorage.getItem('darkMode') === null) {
setIsDarkMode(e.matches);
}
};

mediaQuery.addEventListener('change', handleChange);
return () => mediaQuery.removeEventListener('change', handleChange);
}, []);

const toggleDarkMode = () => {
setIsDarkMode(prev => !prev);
};

return { isDarkMode, toggleDarkMode };
}
42 changes: 25 additions & 17 deletions src/index.css
Original file line number Diff line number Diff line change
@@ -1,21 +1,39 @@
@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100..900;1,100..900&display=swap');

:root {
--text: #1a1a1a;
--background: #ffffff;
--buttonBackground: #f9f9f9;
--buttonBorder: #202020;
--inputBackground: #f9f9f9;
--inputBorder: #202020;
--googleButtonBackground: #f9f9f9;
--cardBackground: white;
--cardSpanBackground: white;
--spanTextColor: #6b7280;
--dividerColor: #e5e7eb;
font-family: 'Roboto', sans-serif;
}

/* Dark mode styles */
.dark {
--text: #f9f9f9;
--background: #202020;
--buttonBackground: #1a1a1a;
--buttonBorder: #d1d5db;;
--buttonBorder: #d1d5db;
--inputBackground: #1a1a1a;
--inputBorder: #202020;
--googleButtonBackground: #1a1a1a;
--cardBackground: #1a1a1a;
--cardSpanBackground: #1a1a1a;
--spanTextColor: #f9f9f9;
--dividerColor: #2a2b2d;
font-family: 'Roboto', sans-serif;
}
body{

body {
color: var(--text);
background-color: var(--background);
transition: color 0.3s ease, background-color 0.3s ease;
}

button {
Expand All @@ -27,21 +45,11 @@ button {
font-weight: 500;
font-family: inherit;
background-color: var(--buttonBackground);
border-color: var(--borderColor);
border-color: var(--buttonBorder);
cursor: pointer;
transition: all 0.3s ease;
}

@media (prefers-color-scheme: light) {
:root {
--text: #1a1a1a;
--buttonBorder: #202020;
--background: #ffffff;
--buttonBackground: #f9f9f9;
--inputBackground: #f9f9f9;
--cardSpanBackground: white;
--cardBackground: white;
--googleButtonBackground: #f9f9f9;
--spanTextColor: #6b7280;
--dividerColor: #e5e7eb;
}
button:hover {
opacity: 0.8;
}
9 changes: 6 additions & 3 deletions src/main.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'
import { AuthProvider } from './context/AuthContext'
import { ThemeProvider } from './context/ThemeContext'

createRoot(document.getElementById('root')).render(
<StrictMode>
<AuthProvider>
<App />
</AuthProvider>
<ThemeProvider>
<AuthProvider>
<App />
</AuthProvider>
</ThemeProvider>
</StrictMode>,
)
7 changes: 3 additions & 4 deletions src/styles/layout.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@
flex-shrink: 0;
}

@media (prefers-color-scheme: dark) {
.logo {
filter: brightness(0) invert(1);
}
/* Dark mode logo styling */
:global(.dark) .logo {
filter: brightness(0) invert(1);
}