diff --git a/agent.md b/agent.md
index b96b3ae..37c4d76 100644
--- a/agent.md
+++ b/agent.md
@@ -36,6 +36,7 @@ This project replaces the Excel/VBA workflow named `Catalog maker - 20` with a N
- Run `npm run css:build` after styling changes; `public/styles.css` is generated output.
- Prefer Tailwind utility classes and Tailwind-style component patterns for buttons, panels, dialogs and responsive layouts.
- Whenever a reusable pattern appears, prefer Tailwind component patterns (`@layer components` with `@apply`) over handwritten legacy CSS; keep one-off layout details as utility classes where practical.
+- Whenever possible, use Tailwind UI / Tailwind Plus application components and patterns for shells, multi-column layouts, sidebars, drawers, dialogs, navigation and forms; adapt them to this project's dense catalog workflow instead of inventing a separate visual pattern.
- Use Playwright to inspect the rendered result after every visual change at desktop and narrow viewport sizes.
## TypeScript workflow
diff --git a/public/app.js b/public/app.js
index 5c67de1..befc612 100644
--- a/public/app.js
+++ b/public/app.js
@@ -22,6 +22,10 @@ const findSourcesButton = document.querySelector("#findSourcesButton");
const getPicturesButton = document.querySelector("#getPicturesButton");
const parserDrawerToggle = document.querySelector("#parserDrawerToggle");
const parserDrawer = document.querySelector("#parserDrawer");
+const sidebarDrawer = document.querySelector("#sidebarDrawer");
+const sidebarOpenButton = document.querySelector("#sidebarOpenButton");
+const sidebarCloseButton = document.querySelector("#sidebarCloseButton");
+const sidebarOverlay = document.querySelector("#sidebarOverlay");
const sourceModal = document.querySelector("#sourceModal");
const sourceTitle = document.querySelector("#sourceTitle");
const sourceList = document.querySelector("#sourceList");
@@ -140,6 +144,13 @@ loadProductButton.addEventListener("click", loadProductInfoForCurrentProduct);
findSourcesButton.addEventListener("click", findSourcesForCurrentProduct);
getPicturesButton.addEventListener("click", getPicturesForCurrentProduct);
parserDrawerToggle.addEventListener("click", toggleParserDrawer);
+sidebarOpenButton.addEventListener("click", openSidebar);
+sidebarCloseButton.addEventListener("click", closeSidebar);
+sidebarOverlay.addEventListener("click", closeSidebar);
+document.addEventListener("keydown", (event) => {
+ if (event.key === "Escape")
+ closeSidebar();
+});
previousProductButton.addEventListener("click", () => {
if (!manufacturerSelect.value || currentPosition <= 1)
return;
@@ -183,6 +194,8 @@ for (const input of localDbInputs)
input.addEventListener("change", saveLocalDbSettings);
testLocalDbButton.addEventListener("click", testLocalDbConnection);
loadDatabaseMode();
+syncSidebarForViewport();
+window.matchMedia("(min-width: 981px)").addEventListener("change", syncSidebarForViewport);
setTimeout(() => {
if (manufacturerSelect.value) {
loadMappingSources();
@@ -756,6 +769,26 @@ function toggleParserDrawer() {
parserDrawerToggle.setAttribute("aria-expanded", String(!isOpen));
parserDrawerToggle.querySelector(".drawer-chevron").textContent = isOpen ? "⌄" : "⌃";
}
+function syncSidebarForViewport() {
+ if (window.matchMedia("(min-width: 981px)").matches)
+ openSidebar();
+ else
+ closeSidebar();
+}
+function openSidebar() {
+ sidebarDrawer.classList.add("is-open");
+ sidebarOverlay.hidden = false;
+ sidebarOpenButton.hidden = true;
+ sidebarOpenButton.setAttribute("aria-expanded", "true");
+ sidebarDrawer.setAttribute("aria-hidden", "false");
+}
+function closeSidebar() {
+ sidebarDrawer.classList.remove("is-open");
+ sidebarOverlay.hidden = true;
+ sidebarOpenButton.hidden = false;
+ sidebarOpenButton.setAttribute("aria-expanded", "false");
+ sidebarDrawer.setAttribute("aria-hidden", "true");
+}
function loadTheme() {
const isDark = localStorage.getItem("catalog-maker:theme") === "dark";
document.documentElement.classList.toggle("dark", isDark);
diff --git a/public/app.ts b/public/app.ts
index a25448f..1143f64 100644
--- a/public/app.ts
+++ b/public/app.ts
@@ -22,6 +22,10 @@ const findSourcesButton = document.querySelector("#findSourcesButton");
const getPicturesButton = document.querySelector("#getPicturesButton");
const parserDrawerToggle = document.querySelector("#parserDrawerToggle");
const parserDrawer = document.querySelector("#parserDrawer");
+const sidebarDrawer = document.querySelector("#sidebarDrawer");
+const sidebarOpenButton = document.querySelector("#sidebarOpenButton");
+const sidebarCloseButton = document.querySelector("#sidebarCloseButton");
+const sidebarOverlay = document.querySelector("#sidebarOverlay");
const sourceModal = document.querySelector("#sourceModal");
const sourceTitle = document.querySelector("#sourceTitle");
const sourceList = document.querySelector("#sourceList");
@@ -147,6 +151,12 @@ loadProductButton.addEventListener("click", loadProductInfoForCurrentProduct);
findSourcesButton.addEventListener("click", findSourcesForCurrentProduct);
getPicturesButton.addEventListener("click", getPicturesForCurrentProduct);
parserDrawerToggle.addEventListener("click", toggleParserDrawer);
+sidebarOpenButton.addEventListener("click", openSidebar);
+sidebarCloseButton.addEventListener("click", closeSidebar);
+sidebarOverlay.addEventListener("click", closeSidebar);
+document.addEventListener("keydown", (event) => {
+ if (event.key === "Escape") closeSidebar();
+});
previousProductButton.addEventListener("click", () => {
if (!manufacturerSelect.value || currentPosition <= 1) return;
@@ -185,6 +195,8 @@ for (const input of localDbInputs) input.addEventListener("change", saveLocalDbS
testLocalDbButton.addEventListener("click", testLocalDbConnection);
loadDatabaseMode();
+syncSidebarForViewport();
+window.matchMedia("(min-width: 981px)").addEventListener("change", syncSidebarForViewport);
setTimeout(() => {
if (manufacturerSelect.value) {
@@ -837,6 +849,27 @@ function toggleParserDrawer() {
parserDrawerToggle.querySelector(".drawer-chevron").textContent = isOpen ? "⌄" : "⌃";
}
+function syncSidebarForViewport() {
+ if (window.matchMedia("(min-width: 981px)").matches) openSidebar();
+ else closeSidebar();
+}
+
+function openSidebar() {
+ sidebarDrawer.classList.add("is-open");
+ sidebarOverlay.hidden = false;
+ sidebarOpenButton.hidden = true;
+ sidebarOpenButton.setAttribute("aria-expanded", "true");
+ sidebarDrawer.setAttribute("aria-hidden", "false");
+}
+
+function closeSidebar() {
+ sidebarDrawer.classList.remove("is-open");
+ sidebarOverlay.hidden = true;
+ sidebarOpenButton.hidden = false;
+ sidebarOpenButton.setAttribute("aria-expanded", "false");
+ sidebarDrawer.setAttribute("aria-hidden", "true");
+}
+
function loadTheme() {
const isDark = localStorage.getItem("catalog-maker:theme") === "dark";
document.documentElement.classList.toggle("dark", isDark);
@@ -1177,4 +1210,4 @@ async function loadProductInfoForCurrentProduct() {
}
}
-export {};
\ No newline at end of file
+export {};
diff --git a/public/icons.svg b/public/icons.svg
index 518008f..d818abb 100644
--- a/public/icons.svg
+++ b/public/icons.svg
@@ -12,4 +12,5 @@