-
Notifications
You must be signed in to change notification settings - Fork 0
Implement theme toggle functionality and update styles for light/dark… #30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,60 @@ | ||||||
| // components/ThemeToggle.tsx | ||||||
| import { useEffect, useState } from 'react'; | ||||||
| import { getTheme, setTheme, applyTheme, watchSystemTheme, getEffectiveTheme, type ThemeMode } from '../lib/theme'; | ||||||
|
|
||||||
| export default function ThemeToggle() { | ||||||
| const [theme, setThemeState] = useState<ThemeMode>('system'); | ||||||
| const [effectiveTheme, setEffectiveTheme] = useState<'light' | 'dark'>('light'); | ||||||
|
|
||||||
| // Inicjalizacja przy montowaniu komponentu | ||||||
|
lifeoverthinker marked this conversation as resolved.
|
||||||
| useEffect(() => { | ||||||
| const current = getTheme(); | ||||||
| setThemeState(current); | ||||||
| setEffectiveTheme(getEffectiveTheme()); | ||||||
|
|
||||||
| // Jeśli system, nasłuchuj zmian | ||||||
|
||||||
| // Jeśli system, nasłuchuj zmian | |
| // If system, listen for changes |
Copilot
AI
Nov 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment is in Polish. Consider translating to English for consistency: "Handle theme change".
| // Obsługi zmiany motywu | |
| // Handle theme change |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| // lib/theme.ts | ||
| export type ThemeMode = 'light' | 'dark' | 'system'; | ||
|
|
||
| const STORAGE_KEY = 'theme-preference'; | ||
|
|
||
| export function getTheme(): ThemeMode { | ||
| if (typeof window === 'undefined') return 'system'; | ||
|
|
||
| const stored = localStorage.getItem(STORAGE_KEY) as ThemeMode | null; | ||
| if (stored && ['light', 'dark', 'system'].includes(stored)) { | ||
| return stored; | ||
| } | ||
| return 'system'; | ||
| } | ||
|
|
||
| export function setTheme(theme: ThemeMode): void { | ||
| if (typeof window === 'undefined') return; | ||
|
|
||
| localStorage.setItem(STORAGE_KEY, theme); | ||
| applyTheme(theme); | ||
| } | ||
|
|
||
| export function applyTheme(theme: ThemeMode): void { | ||
| if (typeof document === 'undefined') return; | ||
|
|
||
| const html = document.documentElement; | ||
| const isDark = theme === 'dark' || | ||
| (theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches); | ||
|
|
||
| if (isDark) { | ||
| html.setAttribute('data-theme', 'dark'); | ||
| } else { | ||
| html.setAttribute('data-theme', 'light'); | ||
| } | ||
| } | ||
|
|
||
| export function watchSystemTheme(callback: (isDark: boolean) => void): () => void { | ||
| if (typeof window === 'undefined') return () => {}; | ||
|
|
||
| const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); | ||
| const handler = (e: MediaQueryListEvent) => { | ||
| callback(e.matches); | ||
| }; | ||
|
|
||
| mediaQuery.addEventListener('change', handler); | ||
| return () => mediaQuery.removeEventListener('change', handler); | ||
| } | ||
|
|
||
| export function getEffectiveTheme(): 'light' | 'dark' { | ||
| const theme = getTheme(); | ||
| if (theme === 'dark') return 'dark'; | ||
| if (theme === 'light') return 'light'; | ||
|
|
||
| if (typeof window !== 'undefined') { | ||
| return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; | ||
| } | ||
|
|
||
| return 'light'; | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,15 @@ | ||||||
| // pages/_app.tsx | ||||||
| import type { AppProps } from 'next/app'; | ||||||
| import { useEffect } from 'react'; | ||||||
| import { applyTheme, getTheme } from '../lib/theme'; | ||||||
| import '../styles/globals.css'; | ||||||
|
|
||||||
| export default function App({ Component, pageProps }: AppProps) { | ||||||
| // Zastosuj motyw przy załadowaniu aplikacji | ||||||
|
||||||
| // Zastosuj motyw przy załadowaniu aplikacji | |
| // Apply theme on application load |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,3 +1,4 @@ | ||||||
| /* styles/Home.module.css */ | ||||||
| .page { | ||||||
| min-height: 100vh; | ||||||
| display: flex; | ||||||
|
|
@@ -7,6 +8,7 @@ | |||||
| color: var(--foreground, #171717); | ||||||
| font-family: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial; | ||||||
| padding: 0; | ||||||
| transition: background-color 0.3s ease, color 0.3s ease; | ||||||
| } | ||||||
|
|
||||||
| /* Header */ | ||||||
|
|
@@ -18,6 +20,7 @@ | |||||
| background: var(--background); | ||||||
| border-bottom: 1px solid rgba(15,23,42,0.04); | ||||||
| backdrop-filter: blur(6px); | ||||||
| transition: background-color 0.3s ease; | ||||||
| } | ||||||
|
|
||||||
| .navInner { | ||||||
|
|
@@ -56,6 +59,12 @@ | |||||
| padding: 8px; | ||||||
| border-radius: 8px; | ||||||
| cursor: pointer; | ||||||
| color: var(--foreground); | ||||||
| transition: all 0.3s ease; | ||||||
| } | ||||||
|
|
||||||
| .iconButton:hover { | ||||||
| background: var(--card-bg); | ||||||
| } | ||||||
|
|
||||||
| /* Search */ | ||||||
|
|
@@ -70,8 +79,15 @@ | |||||
| border-radius: 12px; | ||||||
| border: 1px solid rgba(15,23,42,0.06); | ||||||
| background: var(--background); | ||||||
| color: var(--foreground); | ||||||
| box-shadow: 0 6px 16px rgba(2,6,23,0.04); | ||||||
| outline: none; | ||||||
| transition: all 0.3s ease; | ||||||
| } | ||||||
|
|
||||||
| .searchInput:focus { | ||||||
| border-color: #4f46e5; | ||||||
| box-shadow: 0 6px 20px rgba(79, 70, 229, 0.15); | ||||||
| } | ||||||
|
|
||||||
| /* Container */ | ||||||
|
|
@@ -117,6 +133,11 @@ | |||||
| border-radius: 12px; | ||||||
| background: linear-gradient(180deg, #fbfdff 0%, #eef6ff 100%); | ||||||
| box-shadow: 0 8px 30px rgba(2,6,23,0.04); | ||||||
| transition: background 0.3s ease; | ||||||
| } | ||||||
|
|
||||||
| [data-theme='dark'] .hero { | ||||||
| background: linear-gradient(180deg, #1a1a1a 0%, #1a1a1a 100%); | ||||||
| } | ||||||
|
|
||||||
| .heroLeft { | ||||||
|
|
@@ -147,6 +168,12 @@ | |||||
| font-weight: 600; | ||||||
| text-decoration: none; | ||||||
| box-shadow: 0 8px 22px rgba(255,122,24,0.16); | ||||||
| transition: all 0.3s ease; | ||||||
| } | ||||||
|
|
||||||
| .ctaLink:hover { | ||||||
| transform: translateY(-2px); | ||||||
| box-shadow: 0 10px 28px rgba(255,122,24,0.24); | ||||||
| } | ||||||
|
|
||||||
| .ctaLinkInline { | ||||||
|
|
@@ -157,16 +184,35 @@ | |||||
| color: white; | ||||||
| font-weight: 600; | ||||||
| text-decoration: none; | ||||||
| border: none; | ||||||
| cursor: pointer; | ||||||
| transition: all 0.3s ease; | ||||||
| } | ||||||
|
|
||||||
| /* Lang/Secondary link (zachowane pod klasą .secondary jeśli potrzebne później) */ | ||||||
| .ctaLinkInline:hover { | ||||||
| transform: translateY(-1px); | ||||||
| } | ||||||
|
|
||||||
| .ctaLinkInline:disabled { | ||||||
| opacity: 0.6; | ||||||
| cursor: not-allowed; | ||||||
| } | ||||||
|
|
||||||
| /* Secondary link */ | ||||||
| .secondary { | ||||||
| padding: 8px 10px; | ||||||
| border-radius: 10px; | ||||||
| background: transparent; | ||||||
| color: #334155; | ||||||
|
lifeoverthinker marked this conversation as resolved.
|
||||||
| text-decoration: none; | ||||||
| border: 1px solid rgba(15,23,42,0.06); | ||||||
| transition: all 0.3s ease; | ||||||
| } | ||||||
|
|
||||||
| .secondary:hover { | ||||||
| background: var(--card-bg); | ||||||
| border-color: #4f46e5; | ||||||
| color: #4f46e5; | ||||||
|
lifeoverthinker marked this conversation as resolved.
|
||||||
| } | ||||||
|
|
||||||
| /* Text */ | ||||||
|
|
@@ -192,6 +238,12 @@ | |||||
| box-shadow: 0 10px 30px rgba(2,6,23,0.06); | ||||||
| border: 1px solid #eef6ff; | ||||||
| background: linear-gradient(180deg, #ffffff 0%, #fbfdff 100%); | ||||||
| transition: all 0.3s ease; | ||||||
| } | ||||||
|
|
||||||
| [data-theme='dark'] .device { | ||||||
| border: 1px solid #333333; | ||||||
| background: linear-gradient(180deg, #1a1a1a 0%, #0a0a0a 100%); | ||||||
| } | ||||||
|
|
||||||
| .deviceHeader { | ||||||
|
|
@@ -201,12 +253,18 @@ | |||||
|
|
||||||
| .deviceBody { | ||||||
| padding: 18px; | ||||||
| background: var(--background); | ||||||
| } | ||||||
|
|
||||||
| .noteMock { | ||||||
| background: #fbfdff; | ||||||
|
||||||
| background: #fbfdff; | |
| background: var(--card-bg); |
Uh oh!
There was an error while loading. Please reload this page.