diff --git a/javascript/dwa-starter-vanillajs-vite/components.js b/javascript/dwa-starter-vanillajs-vite/components.js index bce8bfec..ef0e46e8 100644 --- a/javascript/dwa-starter-vanillajs-vite/components.js +++ b/javascript/dwa-starter-vanillajs-vite/components.js @@ -1,57 +1,71 @@ -// components.js -function AboutPage() { - // Create the main container +// Create the theme toggle button +function ThemeToggleButton() { + const button = document.createElement('button'); + button.setAttribute('aria-label', 'Toggle dark mode'); + button.textContent = 'Toggle Theme'; + button.classList.add('p-2', 'rounded', 'border', 'bg-gray-200', 'dark:bg-gray-700'); + + // Add click event to toggle dark mode + button.addEventListener('click', () => { + document.documentElement.classList.toggle('dark'); + const isDarkMode = document.documentElement.classList.contains('dark'); + localStorage.setItem('theme', isDarkMode ? 'dark' : 'light'); + }); + + return button; + } + + // Append the theme toggle button to the page + function addThemeToggleToPage() { + const toggleButton = ThemeToggleButton(); + document.body.prepend(toggleButton); // Add the toggle button to the body + } + + // Create About page + function AboutPage() { const container = document.createElement('div'); container.classList.add('space-y-4', 'p-6', 'text-center'); - - // Create the main title + const title = document.createElement('h1'); title.textContent = 'DWA Starter Vanilla'; title.classList.add('text-3xl', 'font-bold', 'mb-4'); - - // Create the first paragraph + const para1 = document.createElement('p'); para1.textContent = "Decentralized Web App: it's a Web5 Progressive Web App."; para1.classList.add('text-lg'); - - // Create the subtitle + const subtitle = document.createElement('h2'); subtitle.textContent = 'Why PWA?'; subtitle.classList.add('text-2xl', 'font-semibold', 'mt-4'); - - // Create the second paragraph + const para2 = document.createElement('p'); para2.textContent = 'It\'s a perfect match with Web5 DWNs since a PWA can work offline and DWN has a synced local storage.'; para2.classList.add('text-lg'); - - // Append elements to the container + container.appendChild(title); container.appendChild(para1); container.appendChild(subtitle); container.appendChild(para2); - - // Return the container element + return container; } -export function Home() { + export function Home() { document.getElementById('app').innerHTML = `

Home

`; -} - -export function About() { + } + + export function About() { const app = document.getElementById('app'); - app.innerHTML = ''; // Clear the current content - - // Create and append the About page - const aboutPage = AboutPage(); // Call the AboutPage function to get the component + app.innerHTML = ''; + const aboutPage = AboutPage(); app.appendChild(aboutPage); -} - -export function Settings() { + } + + export function Settings() { document.getElementById('app').innerHTML = `

Settings

`; -} - -export function NotFound() { + } + + export function NotFound() { document.getElementById('app').innerHTML = `

404 - Page Not Found

`; -} - + } + \ No newline at end of file diff --git a/javascript/dwa-starter-vanillajs-vite/main.js b/javascript/dwa-starter-vanillajs-vite/main.js index acaf8102..3ea209ae 100644 --- a/javascript/dwa-starter-vanillajs-vite/main.js +++ b/javascript/dwa-starter-vanillajs-vite/main.js @@ -1,37 +1,49 @@ -// main.js +// Function to check system preference and localStorage for theme preference +function applyTheme() { + const prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)").matches; + const currentTheme = localStorage.getItem("theme") || (prefersDarkScheme ? "dark" : "light"); -import { Home, About, Settings, NotFound } from './components.js'; + // Toggle dark mode based on the stored or system preference + document.documentElement.classList.toggle('dark', currentTheme === 'dark'); +} -// Define routes and their corresponding components -const routes = { - '/': Home, - '/about': About, - '/settings': Settings, -}; +// Function to add the theme toggle functionality to the page +function addThemeToggleToPage() { + const toggleButton = document.querySelector("#theme-toggle"); -// Function to handle navigation -function navigateTo(url) { - history.pushState(null, null, url); - router(); -} + // Listen for the toggle button click + toggleButton.addEventListener("click", () => { + const isDarkMode = document.documentElement.classList.contains('dark'); + const newTheme = isDarkMode ? 'light' : 'dark'; -// Router function to render components based on the current URL -function router() { - const path = window.location.pathname; - const route = routes[path] || NotFound; - route(); + // Toggle the theme and save to localStorage + document.documentElement.classList.toggle('dark', newTheme === 'dark'); + localStorage.setItem("theme", newTheme); + }); } -// Event delegation for link clicks -document.addEventListener('click', (e) => { - if (e.target.matches('[data-link]')) { - e.preventDefault(); - navigateTo(e.target.href); - } +// Apply the theme on page load +window.addEventListener("DOMContentLoaded", () => { + applyTheme(); + addThemeToggleToPage(); }); -// Listen to popstate event (back/forward navigation) -window.addEventListener('popstate', router); +// Router logic +const routes = { + '/': () => `

Home

`, + '/about': () => `

DWA Starter Vanilla

`, + '/settings': () => `

Settings

`, + '*': () => `

404 - Page Not Found

`, +}; + +function handleRoute() { + const path = window.location.pathname; + const route = routes[path] || routes['*']; + document.getElementById('app').innerHTML = route(); +} + +// Handle back/forward navigation +window.addEventListener('popstate', handleRoute); -// Initial call to router to render the correct component on page load -document.addEventListener('DOMContentLoaded', router); +// Initial page load +handleRoute(); diff --git a/javascript/dwa-starter-vanillajs-vite/style.css b/javascript/dwa-starter-vanillajs-vite/style.css index 9e71cd2a..ad298177 100644 --- a/javascript/dwa-starter-vanillajs-vite/style.css +++ b/javascript/dwa-starter-vanillajs-vite/style.css @@ -1,4 +1,3 @@ -/* styles.css */ @tailwind base; @tailwind components; @tailwind utilities; @@ -38,3 +37,16 @@ nav a:hover { #app { padding: 20px; } + +/* Dark mode styles */ +.dark nav { + background-color: #222; +} + +.dark nav a { + color: #bbb; +} + +.dark nav a:hover { + background-color: #444; +} diff --git a/javascript/dwa-starter-vanillajs-vite/tailwind.config.js b/javascript/dwa-starter-vanillajs-vite/tailwind.config.js index 7172b0e3..95de6492 100644 --- a/javascript/dwa-starter-vanillajs-vite/tailwind.config.js +++ b/javascript/dwa-starter-vanillajs-vite/tailwind.config.js @@ -1,5 +1,6 @@ /** @type {import('tailwindcss').Config} */ module.exports = { + darkMode: 'class', content: [ "./index.html", "./components.js", diff --git a/javascript/dwa-starter-vanillajs-vite/tests/main.spec.js b/javascript/dwa-starter-vanillajs-vite/tests/main.spec.js index 97b26262..86073fc2 100644 --- a/javascript/dwa-starter-vanillajs-vite/tests/main.spec.js +++ b/javascript/dwa-starter-vanillajs-vite/tests/main.spec.js @@ -1,26 +1,72 @@ -// tests/router.spec.js import { test, expect } from '@playwright/test'; test.describe('Vanilla Router', () => { - // Before all tests, start a local server if necessary - test('should navigate to Home', async ({ page }) => { - await page.goto('/'); + await page.goto('http://localhost:5173/'); expect(await page.textContent('h1')).toBe('Home'); }); test('should navigate to About', async ({ page }) => { - await page.goto('/about'); - expect(await page.textContent('h1')).toBe('DWA Starter Vanilla'); + await page.goto('http://localhost:5173/about'); + expect(await page.textContent('h1')).toBe('DWA Starter Vanilla'); }); test('should navigate to Settings', async ({ page }) => { - await page.goto('/settings'); + await page.goto('http://localhost:5173/settings'); expect(await page.textContent('h1')).toBe('Settings'); }); test('should show Not Found for undefined routes', async ({ page }) => { - await page.goto('/undefined-route'); + await page.goto('http://localhost:5173/undefined-route'); expect(await page.textContent('h1')).toBe('404 - Page Not Found'); }); }); + +test.describe('Theme Toggle Functionality', () => { + test('should toggle dark mode and remember preference', async ({ page }) => { + await page.goto('http://localhost:5173/'); + + // Check initial state + const initialIsDarkMode = await page.evaluate(() => document.documentElement.classList.contains('dark')); + + // Toggle the theme + await page.click('#theme-toggle'); + + // Verify that the theme was toggled + const isDarkModeAfterToggle = await page.evaluate(() => document.documentElement.classList.contains('dark')); + expect(isDarkModeAfterToggle).toBe(!initialIsDarkMode); + + // Reload the page to check if preference is remembered + await page.reload(); + const isDarkModeAfterReload = await page.evaluate(() => document.documentElement.classList.contains('dark')); + expect(isDarkModeAfterReload).toBe(isDarkModeAfterToggle); + }); + + test('should load with correct theme based on system preference when no localStorage is set', async ({ page }) => { + // Clear localStorage + await page.evaluate(() => localStorage.removeItem('theme')); + + await page.goto('http://localhost:5173/'); + + // Get system preference + const prefersDarkMode = await page.evaluate(() => window.matchMedia('(prefers-color-scheme: dark)').matches); + + // Check if the page loads with the correct theme based on system preference + const isDarkMode = await page.evaluate(() => document.documentElement.classList.contains('dark')); + expect(isDarkMode).toBe(prefersDarkMode); + }); + + test('should persist theme across different routes', async ({ page }) => { + await page.goto('http://localhost:5173/'); + + // Toggle to dark mode + await page.click('#theme-toggle'); + const isDarkMode = await page.evaluate(() => document.documentElement.classList.contains('dark')); + expect(isDarkMode).toBe(true); + + // Navigate to a different route + await page.goto('http://localhost:5173/about'); + const isDarkModeOnNewRoute = await page.evaluate(() => document.documentElement.classList.contains('dark')); + expect(isDarkModeOnNewRoute).toBe(true); + }); +});