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);
+ });
+});