Add persistent dark theme toggle
This commit is contained in:
@@ -24,6 +24,7 @@ const sourceTitle = document.querySelector("#sourceTitle");
|
|||||||
const sourceList = document.querySelector("#sourceList");
|
const sourceList = document.querySelector("#sourceList");
|
||||||
const sourceCloseButton = document.querySelector("#sourceCloseButton");
|
const sourceCloseButton = document.querySelector("#sourceCloseButton");
|
||||||
const settingsButton = document.querySelector("#settingsButton");
|
const settingsButton = document.querySelector("#settingsButton");
|
||||||
|
const themeToggle = document.querySelector("#themeToggle");
|
||||||
const settingsModal = document.querySelector("#settingsModal");
|
const settingsModal = document.querySelector("#settingsModal");
|
||||||
const settingsCloseButton = document.querySelector("#settingsCloseButton");
|
const settingsCloseButton = document.querySelector("#settingsCloseButton");
|
||||||
const databaseModeNote = document.querySelector("#databaseModeNote");
|
const databaseModeNote = document.querySelector("#databaseModeNote");
|
||||||
@@ -120,6 +121,7 @@ const productFieldMap = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
await loadStatus();
|
await loadStatus();
|
||||||
|
loadTheme();
|
||||||
renderFields();
|
renderFields();
|
||||||
renderTableHeader();
|
renderTableHeader();
|
||||||
setTableWidth();
|
setTableWidth();
|
||||||
@@ -167,6 +169,7 @@ sourceModal.addEventListener("click", (event) => {
|
|||||||
if (event.target === sourceModal) closeSourceCandidates();
|
if (event.target === sourceModal) closeSourceCandidates();
|
||||||
});
|
});
|
||||||
settingsButton.addEventListener("click", openSettings);
|
settingsButton.addEventListener("click", openSettings);
|
||||||
|
themeToggle.addEventListener("click", toggleTheme);
|
||||||
settingsCloseButton.addEventListener("click", closeSettings);
|
settingsCloseButton.addEventListener("click", closeSettings);
|
||||||
settingsModal.addEventListener("click", (event) => {
|
settingsModal.addEventListener("click", (event) => {
|
||||||
if (event.target === settingsModal) closeSettings();
|
if (event.target === settingsModal) closeSettings();
|
||||||
@@ -823,6 +826,19 @@ function openSettings() {
|
|||||||
settingsModal.hidden = false;
|
settingsModal.hidden = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loadTheme() {
|
||||||
|
const isDark = localStorage.getItem("catalog-maker:theme") === "dark";
|
||||||
|
document.documentElement.classList.toggle("dark", isDark);
|
||||||
|
themeToggle.textContent = isDark ? "Light theme" : "Dark theme";
|
||||||
|
themeToggle.setAttribute("aria-pressed", String(isDark));
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleTheme() {
|
||||||
|
const isDark = !document.documentElement.classList.contains("dark");
|
||||||
|
localStorage.setItem("catalog-maker:theme", isDark ? "dark" : "light");
|
||||||
|
loadTheme();
|
||||||
|
}
|
||||||
|
|
||||||
function closeSettings() {
|
function closeSettings() {
|
||||||
settingsModal.hidden = true;
|
settingsModal.hidden = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,7 @@
|
|||||||
<button class="action-button primary" type="button">Show menu (20)</button>
|
<button class="action-button primary" type="button">Show menu (20)</button>
|
||||||
<button class="action-button" type="button">Scraper - 03</button>
|
<button class="action-button" type="button">Scraper - 03</button>
|
||||||
<button class="action-button" type="button">BG remover - 04</button>
|
<button class="action-button" type="button">BG remover - 04</button>
|
||||||
|
<button class="action-button" id="themeToggle" type="button" aria-pressed="false">Dark theme</button>
|
||||||
<button class="action-button" id="settingsButton" type="button">Settings</button>
|
<button class="action-button" id="settingsButton" type="button">Settings</button>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -21,6 +21,10 @@
|
|||||||
color-scheme: light;
|
color-scheme: light;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html.dark {
|
||||||
|
color-scheme: dark;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
@@ -142,4 +146,42 @@
|
|||||||
.product-panel { @apply pt-0; }
|
.product-panel { @apply pt-0; }
|
||||||
.table-wrap { @apply max-h-[70vh]; }
|
.table-wrap { @apply max-h-[70vh]; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html.dark body { @apply bg-slate-950 text-slate-100; }
|
||||||
|
html.dark .control-panel { @apply bg-slate-950; }
|
||||||
|
html.dark .product-panel { @apply bg-slate-800; }
|
||||||
|
html.dark .data-panel { @apply bg-slate-950; }
|
||||||
|
html.dark .field-label { @apply border-slate-700 bg-slate-800 text-slate-200; }
|
||||||
|
html.dark .field-value { @apply border-slate-700 bg-slate-900 text-slate-100; }
|
||||||
|
html.dark .variant-table th { @apply border-slate-700 bg-slate-800 text-slate-100; }
|
||||||
|
html.dark .variant-table td { @apply border-slate-700 bg-slate-900 text-slate-200; }
|
||||||
|
html.dark .variant-table tr:nth-child(even) td { @apply bg-slate-800; }
|
||||||
|
html.dark .variant-table tr:hover td { @apply bg-slate-700; }
|
||||||
|
html.dark .arrow-button,
|
||||||
|
html.dark .action-button,
|
||||||
|
html.dark .input-like { @apply border-slate-600 bg-slate-800 text-slate-100; }
|
||||||
|
html.dark .arrow-button:hover:not(:disabled),
|
||||||
|
html.dark .action-button:hover:not(:disabled) { @apply border-slate-400 bg-slate-700; }
|
||||||
|
html.dark #scanInput { @apply bg-slate-700 placeholder:text-slate-400; }
|
||||||
|
html.dark .settings-dialog,
|
||||||
|
html.dark .suggested-dialog,
|
||||||
|
html.dark .source-dialog { @apply border-slate-700 bg-slate-900; }
|
||||||
|
html.dark .suggested-header { @apply border-slate-700 bg-slate-800; }
|
||||||
|
html.dark .suggested-header h2,
|
||||||
|
html.dark .settings-group legend,
|
||||||
|
html.dark .settings-option,
|
||||||
|
html.dark .settings-field { @apply text-slate-100; }
|
||||||
|
html.dark .settings-group { @apply border-slate-700; }
|
||||||
|
html.dark .settings-field input { @apply border-slate-600 bg-slate-800 text-slate-100; }
|
||||||
|
html.dark .modal-close,
|
||||||
|
html.dark .settings-test-button,
|
||||||
|
html.dark .suggested-pick,
|
||||||
|
html.dark .source-row-action,
|
||||||
|
html.dark .source-picture-action { @apply border-slate-600 bg-slate-800 text-slate-100 hover:bg-slate-700; }
|
||||||
|
html.dark .source-section { @apply border-slate-700; }
|
||||||
|
html.dark .source-section-header { @apply border-slate-700 bg-slate-800 text-slate-100; }
|
||||||
|
html.dark .source-row { @apply border-slate-700 bg-slate-900 text-slate-100 hover:bg-slate-800; }
|
||||||
|
html.dark .suggested-row,
|
||||||
|
html.dark .picture-candidate { @apply border-slate-700; }
|
||||||
|
html.dark .suggested-row:nth-child(even) { @apply bg-slate-800; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,3 +30,15 @@ test("catalog maker remains usable on a narrow viewport", async ({ page }) => {
|
|||||||
await expect(page.locator("#settingsButton")).toBeVisible();
|
await expect(page.locator("#settingsButton")).toBeVisible();
|
||||||
await page.screenshot({ path: "test-results/catalog-maker-mobile.png", fullPage: true });
|
await page.screenshot({ path: "test-results/catalog-maker-mobile.png", fullPage: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("theme toggle switches and persists dark mode", async ({ page }) => {
|
||||||
|
await page.goto("/");
|
||||||
|
await expect(page.locator("html")).not.toHaveClass(/dark/);
|
||||||
|
|
||||||
|
await page.locator("#themeToggle").click();
|
||||||
|
await expect(page.locator("html")).toHaveClass(/dark/);
|
||||||
|
await expect(page.locator("#themeToggle")).toHaveText("Light theme");
|
||||||
|
|
||||||
|
await page.reload();
|
||||||
|
await expect(page.locator("html")).toHaveClass(/dark/);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user