Keep sidebar controls in dedicated left rail

This commit is contained in:
2026-08-05 16:28:33 +02:00
parent 38dfb9fd54
commit 9b7b2f3c2e
7 changed files with 15 additions and 6 deletions
+1
View File
@@ -38,6 +38,7 @@ This project replaces the Excel/VBA workflow named `Catalog maker - 20` with a N
- 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.
- Multi-column panels must use a shared collapse pattern: collapsing a panel removes its grid track, lets the adjacent workspace expand, preserves an accessible expand/collapse control, and stacks predictably on mobile.
- When the left sidebar is closed on desktop, its open control keeps a dedicated narrow rail; it must not be positioned inside the secondary product-details panel. On mobile, the rail collapses and the control may overlay the stacked layout.
- Use Playwright to inspect the rendered result after every visual change at desktop and narrow viewport sizes.
## TypeScript workflow
+4 -2
View File
@@ -53,6 +53,7 @@ const darkContrastSelect = document.querySelector("#darkContrastSelect");
const catalogScreen = document.querySelector(".catalog-screen");
const secondaryPanelToggle = document.querySelector("#secondaryPanelToggle");
const mainWorkspaceToggle = document.querySelector("#mainWorkspaceToggle");
let sidebarUserClosed = false;
const nativeFetch = window.fetch.bind(window);
window.fetch = (input, init = {}) => {
const url = typeof input === "string" ? input : input.url;
@@ -225,7 +226,6 @@ lightContrastSelect.addEventListener("change", saveAppearance);
darkContrastSelect.addEventListener("change", saveAppearance);
loadDatabaseMode();
syncSidebarForViewport();
window.matchMedia("(min-width: 981px)").addEventListener("change", syncSidebarForViewport);
setTimeout(() => {
if (manufacturerSelect.value) {
loadMappingSources();
@@ -852,12 +852,13 @@ function toggleParserDrawer() {
parserDrawerToggle.querySelector(".drawer-chevron").textContent = isOpen ? "⌄" : "⌃";
}
function syncSidebarForViewport() {
if (window.matchMedia("(min-width: 981px)").matches)
if (window.matchMedia("(min-width: 981px)").matches && !sidebarUserClosed)
openSidebar();
else
closeSidebar();
}
function openSidebar() {
sidebarUserClosed = false;
sidebarDrawer.classList.add("is-open");
sidebarOverlay.hidden = false;
sidebarOpenButton.hidden = true;
@@ -866,6 +867,7 @@ function openSidebar() {
catalogScreen.classList.remove("sidebar-collapsed");
}
function closeSidebar() {
sidebarUserClosed = true;
sidebarDrawer.classList.remove("is-open");
sidebarOverlay.hidden = true;
sidebarOpenButton.hidden = false;
+4 -2
View File
@@ -53,6 +53,7 @@ const darkContrastSelect = document.querySelector("#darkContrastSelect");
const catalogScreen = document.querySelector(".catalog-screen");
const secondaryPanelToggle = document.querySelector("#secondaryPanelToggle");
const mainWorkspaceToggle = document.querySelector("#mainWorkspaceToggle");
let sidebarUserClosed = false;
const nativeFetch = window.fetch.bind(window);
window.fetch = (input, init = {}) => {
@@ -225,7 +226,6 @@ darkContrastSelect.addEventListener("change", saveAppearance);
loadDatabaseMode();
syncSidebarForViewport();
window.matchMedia("(min-width: 981px)").addEventListener("change", syncSidebarForViewport);
setTimeout(() => {
if (manufacturerSelect.value) {
@@ -940,11 +940,12 @@ function toggleParserDrawer() {
}
function syncSidebarForViewport() {
if (window.matchMedia("(min-width: 981px)").matches) openSidebar();
if (window.matchMedia("(min-width: 981px)").matches && !sidebarUserClosed) openSidebar();
else closeSidebar();
}
function openSidebar() {
sidebarUserClosed = false;
sidebarDrawer.classList.add("is-open");
sidebarOverlay.hidden = false;
sidebarOpenButton.hidden = true;
@@ -954,6 +955,7 @@ function openSidebar() {
}
function closeSidebar() {
sidebarUserClosed = true;
sidebarDrawer.classList.remove("is-open");
sidebarOverlay.hidden = true;
sidebarOpenButton.hidden = false;
+1 -1
View File
File diff suppressed because one or more lines are too long
+1
View File
@@ -24,6 +24,7 @@ const config = loadConfig();
const mimeTypes = new Map([
[".html", "text/html; charset=utf-8"],
[".css", "text/css; charset=utf-8"],
[".js", "text/javascript; charset=utf-8"],
[".ts", "text/javascript; charset=utf-8"],
[".json", "application/json; charset=utf-8"],
[".svg", "image/svg+xml"],
+2 -1
View File
@@ -42,7 +42,7 @@
@layer components {
.catalog-screen { @apply relative grid min-h-screen bg-catalog-surface pl-72; --secondary-track: minmax(240px,292px); --workspace-track: minmax(0,1fr); grid-template-columns: var(--secondary-track) var(--workspace-track); }
.catalog-screen.sidebar-collapsed { @apply pl-0; }
.catalog-screen.sidebar-collapsed { @apply pl-11; }
.catalog-screen.secondary-collapsed { --secondary-track: 44px; }
.catalog-screen.workspace-collapsed { --workspace-track: 44px; }
.sidebar-drawer { @apply fixed inset-y-0 left-0 z-40 flex w-72 min-h-screen flex-col gap-1.5 overflow-y-auto bg-slate-900 p-1.5 text-[11px] text-slate-200 shadow-xl transition-transform duration-200 ease-out; }
@@ -186,6 +186,7 @@
.picture-candidate span { @apply min-w-0 truncate; }
@media (max-width: 980px) {
.catalog-screen { @apply flex flex-col pl-0; }
.catalog-screen.sidebar-collapsed { @apply pl-0; }
.sidebar-drawer { @apply w-[min(18rem,calc(100vw-1rem))] min-h-0; }
.sidebar-drawer:not([aria-hidden="true"]) { @apply max-h-screen; }
.sidebar-drawer[aria-hidden="true"] { @apply -translate-x-full; }
+2
View File
@@ -61,6 +61,8 @@ test("workspace columns collapse and expand without leaving a gap", async ({ pag
await page.goto("/");
await page.locator("#sidebarCloseButton").click();
await expect(page.locator(".catalog-screen")).toHaveClass(/sidebar-collapsed/);
const secondaryPanel = await page.locator(".secondary-panel").boundingBox();
expect(secondaryPanel?.x).toBeGreaterThanOrEqual(44);
await page.locator("#secondaryPanelToggle").click();
await expect(page.locator(".catalog-screen")).toHaveClass(/secondary-collapsed/);