From fec12614f6148db07a59a239f7c3ee79ff2c8f1a Mon Sep 17 00:00:00 2001 From: Lenox Wiltshire Date: Mon, 22 Jun 2026 22:07:57 -0400 Subject: [PATCH 1/3] [feat]: implement resizable side panels with local storage Signed-off-by: Lenox Wiltshire --- assets/js/sidebar-load-width.js | 18 ++++ assets/js/sidebar-resizer.js | 113 ++++++++++++++++++++ assets/scss/_styles_project.scss | 86 +++++++++++++++ layouts/404.html | 12 ++- layouts/docs/baseof.html | 10 +- layouts/partials/load-sidebars-width.html | 3 + layouts/partials/sidebar-resize-script.html | 2 + layouts/release/baseof.html | 11 +- layouts/video/baseof.html | 17 ++- 9 files changed, 264 insertions(+), 8 deletions(-) create mode 100644 assets/js/sidebar-load-width.js create mode 100644 assets/js/sidebar-resizer.js create mode 100644 layouts/partials/load-sidebars-width.html create mode 100644 layouts/partials/sidebar-resize-script.html diff --git a/assets/js/sidebar-load-width.js b/assets/js/sidebar-load-width.js new file mode 100644 index 00000000000..84dd8c6f8bf --- /dev/null +++ b/assets/js/sidebar-load-width.js @@ -0,0 +1,18 @@ +(function () { + try { + var leftWidth = parseInt(localStorage.getItem("left-sidebar-width"), 10); + var tocWidth = parseInt(localStorage.getItem("toc-width"), 10); + var cssStyles = ""; + + if (!isNaN(leftWidth)) cssStyles += "--left-sidebar-width: " + leftWidth + "px;"; + if (!isNaN(tocWidth)) cssStyles += "--toc-width: " + tocWidth + "px;"; + + if (cssStyles) { + var styleTag = document.createElement("style"); + styleTag.innerHTML = ".td-resizable-grid .td-main > .row.flex-xl-nowrap {" + cssStyles + "}"; + document.head.appendChild(styleTag); + } + } catch (e) { + console.warn("LocalStorage blocked or unavailable:", e); + } +})(); \ No newline at end of file diff --git a/assets/js/sidebar-resizer.js b/assets/js/sidebar-resizer.js new file mode 100644 index 00000000000..d0102c4fc05 --- /dev/null +++ b/assets/js/sidebar-resizer.js @@ -0,0 +1,113 @@ +(function () { + document.addEventListener("DOMContentLoaded", () => { + const gridContainer = document.querySelector(".td-resizable-grid .td-main > .row.flex-xl-nowrap"); + const leftResizer = document.getElementById("left-resizer"); + const rightResizer = document.getElementById("right-resizer"); + + if (!gridContainer) return; + + const LEFT_SIDEBAR_KEY = "left-sidebar-width"; + const TOC_KEY = "toc-width"; + + const storage = { + get: (key) => { + try { return localStorage.getItem(key); } + catch (e) { return null; } + }, + set: (key, value) => { + try { localStorage.setItem(key, value); } + catch (e) { return null; } + } + }; + + function setupResizer(resizer, type) { + resizer.addEventListener("pointerdown", (e) => { + e.preventDefault(); + + resizer.setPointerCapture(e.pointerId); + resizer.classList.add("is-dragging"); + + document.body.style.cursor = "col-resize"; + document.body.style.userSelect = "none"; + + const startX = e.clientX; + const styles = window.getComputedStyle(gridContainer); + const gridColumns = styles.gridTemplateColumns ? styles.gridTemplateColumns.split(" ") : []; + + /* + grid-template-columns over in _styles_project.scss has the following structure: + + min(768px) + --left-sidebar-width --sidebar-resize-width 1fr + + min(1200px) + --left-sidebar-width --sidebar-resize-width 1fr --sidebar-resize-width --toc-width; + + so gridcolumns[0] targets the left sidebar and gridcolumns[4] targets the toc + */ + + let initialLeftSidebarWidth, initialTocWidth; + if (gridColumns.length >= 5 && gridColumns[0] !== "none") { + initialLeftSidebarWidth = parseInt(gridColumns[0], 10); + initialTocWidth = parseInt(gridColumns[4], 10); + } else { + initialLeftSidebarWidth = parseInt(styles.getPropertyValue('--left-sidebar-width'), 10); + initialTocWidth = parseInt(styles.getPropertyValue('--toc-width'), 10); + } + + const minWidth = parseInt(styles.getPropertyValue(type === "left" ? '--left-sidebar-min-width' : '--toc-min-width'), 10); + const maxWidth = parseInt(styles.getPropertyValue(type === "left" ? '--left-sidebar-max-width' : '--toc-max-width'), 10); + let currentWidth = type === "left" ? initialLeftSidebarWidth : initialTocWidth; + let ticking = false; + let animationFrameId = null; + + function onPointerMove(moveEvent) { + const deltaX = moveEvent.clientX - startX; + + if (type === "left") { + currentWidth = Math.max(minWidth, Math.min(maxWidth, initialLeftSidebarWidth + deltaX)); + } else if (type === "right") { + currentWidth = Math.max(minWidth, Math.min(maxWidth, initialTocWidth - deltaX)); + } + + if (!ticking) { + animationFrameId = window.requestAnimationFrame(() => { + const varName = type === "left" ? "--left-sidebar-width" : "--toc-width"; + gridContainer.style.setProperty(varName, `${currentWidth}px`); + ticking = false; + }); + ticking = true; + } + } + + function onPointerUp(upEvent) { + if (animationFrameId) { + window.cancelAnimationFrame(animationFrameId); + animationFrameId = null; + ticking = false; + } + + resizer.releasePointerCapture(upEvent.pointerId); + resizer.classList.remove("is-dragging"); + + document.body.style.cursor = ""; + document.body.style.userSelect = ""; + + const storageKey = type === "left" ? LEFT_SIDEBAR_KEY : TOC_KEY; + storage.set(storageKey, currentWidth); + + document.removeEventListener("pointermove", onPointerMove); + document.removeEventListener("pointerup", onPointerUp); + document.removeEventListener("pointercancel", onPointerUp); + } + + document.addEventListener("pointermove", onPointerMove); + document.addEventListener("pointerup", onPointerUp); + document.addEventListener("pointercancel", onPointerUp); + }); + } + + if (leftResizer) setupResizer(leftResizer, "left"); + if (rightResizer) setupResizer(rightResizer, "right"); + }); +})(); \ No newline at end of file diff --git a/assets/scss/_styles_project.scss b/assets/scss/_styles_project.scss index 5a34ccd09be..32514fda080 100644 --- a/assets/scss/_styles_project.scss +++ b/assets/scss/_styles_project.scss @@ -394,6 +394,92 @@ a:not([href]):not([class]):hover { } } +// Left sidebar & Right sidebar Resize +:root { + --left-sidebar-width: 240px; + --toc-width: 240px; + + --left-sidebar-min-width: 180px; + --left-sidebar-max-width: 500px; + + --toc-min-width: 180px; + --toc-max-width: 400px; + + --sidebar-resize-width: 5px; +} + +.layout-resizer { + width: var(--sidebar-resize-width); + cursor: col-resize; + background-color: rgba(0, 0, 0, 0.03); + transition: background-color 0.15s ease; + z-index: 1; //Must be lower than the sticky header +} + +.layout-resizer:hover, +.layout-resizer.is-dragging { + background-color: $primary; +} + +.td-resizable-grid .td-sidebar, +.td-resizable-grid .td-sidebar-toc, +.td-resizable-grid main[role="main"] { + width: auto; +} + +@media (min-width: 768px) { + .td-resizable-grid .td-main > .row.flex-xl-nowrap { + display: grid; + grid-template-columns: + var(--left-sidebar-width) + var(--sidebar-resize-width) + 1fr; + } + + .td-sidebar { + grid-column: 1; + grid-row: 1; + } + + #left-resizer { + grid-column: 2; + grid-row: 1; + padding-left: 0 !important; + padding-right: 0 !important; + box-sizing: border-box; + } + + main[role="main"] { + grid-column: 3; + grid-row: 1; + } +} + +@media (min-width: 1200px) { + .td-resizable-grid .td-main > .row.flex-xl-nowrap { + display: grid; + grid-template-columns: + var(--left-sidebar-width) + var(--sidebar-resize-width) + 1fr + var(--sidebar-resize-width) + var(--toc-width); + } + + #right-resizer { + grid-column: 4; + grid-row: 1; + padding-left: 0 !important; + padding-right: 0 !important; + box-sizing: border-box; + } + + .td-sidebar-toc { + grid-column: 5; + grid-row: 1; + } +} + // pageinfo .pageinfo { font-weight: $font-weight-medium; diff --git a/layouts/404.html b/layouts/404.html index 410580771b7..c8dfd36543e 100644 --- a/layouts/404.html +++ b/layouts/404.html @@ -7,17 +7,24 @@
{{ partial "navbar.html" . }}
-
+
+ +
+ +
+ + +
{{ partial "version-banner.html" . }} {{ if not .Site.Params.ui.breadcrumb_disable }}{{ partial "breadcrumb.html" . }}{{ end }} @@ -27,11 +34,14 @@

Not found

Please let us know about this issue and return to the home page.

{{ end }}
+ + {{ partial "load-sidebars-width.html" . }}
{{ partial "footer.html" . }}
{{ partial "scripts.html" . }} + {{ partial "sidebar-resize-script.html" }} diff --git a/layouts/docs/baseof.html b/layouts/docs/baseof.html index 3503963fb79..593a45afbd4 100644 --- a/layouts/docs/baseof.html +++ b/layouts/docs/baseof.html @@ -7,17 +7,22 @@
{{ partial "navbar.html" . }}
-
+
+ +
+ +
+
{{ partial "version-banner.html" . }} {{ block "main" . }}{{ end }}
+ + {{ partial "load-sidebars-width.html" . }}
{{ partial "footer.html" . }}
{{ partial "scripts.html" . }} + {{ partial "sidebar-resize-script.html" }} {{ partial "image-modal.html" . }} diff --git a/layouts/partials/load-sidebars-width.html b/layouts/partials/load-sidebars-width.html new file mode 100644 index 00000000000..4e206de7dd6 --- /dev/null +++ b/layouts/partials/load-sidebars-width.html @@ -0,0 +1,3 @@ + +{{ $resizerScript := resources.Get "js/sidebar-load-width.js" | minify | fingerprint }} + \ No newline at end of file diff --git a/layouts/partials/sidebar-resize-script.html b/layouts/partials/sidebar-resize-script.html new file mode 100644 index 00000000000..2b8b9836f3d --- /dev/null +++ b/layouts/partials/sidebar-resize-script.html @@ -0,0 +1,2 @@ +{{ $resizerScript := resources.Get "js/sidebar-resizer.js" | minify | fingerprint }} + \ No newline at end of file diff --git a/layouts/release/baseof.html b/layouts/release/baseof.html index 2145f978f24..66b5d9311e8 100644 --- a/layouts/release/baseof.html +++ b/layouts/release/baseof.html @@ -13,12 +13,17 @@ class="td-{{ .Kind }}{{ with .Page.Params.body_class }} {{ . }}{{ end }}" >
{{ partial "navbar.html" . }}
-
+
+ +
+ +
+
{{ partial "footer.html" . }}
- {{ partial "scripts.html" . }} {{ partial "image-modal.html" . }} + {{ partial "scripts.html" . }} {{ partial "sidebar-resize-script.html" }} {{ partial "image-modal.html" . }} \ No newline at end of file diff --git a/layouts/video/baseof.html b/layouts/video/baseof.html index 7f988f54859..c5e5f3d63a6 100644 --- a/layouts/video/baseof.html +++ b/layouts/video/baseof.html @@ -13,26 +13,35 @@ class="td-{{ .Kind }}{{ with .Page.Params.body_class }} {{ . }}{{ end }}" >
{{ partial "navbar.html" . }}
-
+
+ +
+ +
+ +
{{ partial "version-banner.html" . }} {{ if not .Site.Params.ui.breadcrumb_disable }}{{ partial "breadcrumb.html" . }}{{ end }} {{ block "main" . }}{{ end }}
+ + {{ partial "load-sidebars-width.html" . }}
{{ partial "footer.html" . }}
{{partial "video-category-navigation.html" .}} - {{ partial "scripts.html" . }} {{ partial "image-modal.html" . }} + {{ partial "scripts.html" . }} {{ partial "image-modal.html" . }} {{ partial "sidebar-resize-script.html" }} From 664d4788ab19e32465a40956319d1ab7d3a2e856 Mon Sep 17 00:00:00 2001 From: Lenox Wiltshire Date: Tue, 23 Jun 2026 01:42:29 -0400 Subject: [PATCH 2/3] [chore]: move script call to head Signed-off-by: Lenox Wiltshire --- layouts/404.html | 3 +-- layouts/docs/baseof.html | 3 +-- layouts/partials/load-sidebars-width.html | 1 - layouts/release/baseof.html | 3 +-- layouts/video/baseof.html | 3 +-- 5 files changed, 4 insertions(+), 9 deletions(-) diff --git a/layouts/404.html b/layouts/404.html index c8dfd36543e..56eeb26e34f 100644 --- a/layouts/404.html +++ b/layouts/404.html @@ -2,6 +2,7 @@ {{ partial "head.html" . }} + {{ partial "load-sidebars-width.html" . }}
@@ -34,8 +35,6 @@

Not found

Please let us know about this issue and return to the home page.

{{ end }} - - {{ partial "load-sidebars-width.html" . }}
{{ partial "footer.html" . }} diff --git a/layouts/docs/baseof.html b/layouts/docs/baseof.html index 593a45afbd4..f66c4a5f5be 100644 --- a/layouts/docs/baseof.html +++ b/layouts/docs/baseof.html @@ -3,6 +3,7 @@ {{ partial "head.html" . }} + {{ partial "load-sidebars-width.html" . }} @@ -37,8 +38,6 @@
{{ block "main" . }}{{ end }} - - {{ partial "load-sidebars-width.html" . }}
{{ partial "footer.html" . }} diff --git a/layouts/partials/load-sidebars-width.html b/layouts/partials/load-sidebars-width.html index 4e206de7dd6..5c782c5fee6 100644 --- a/layouts/partials/load-sidebars-width.html +++ b/layouts/partials/load-sidebars-width.html @@ -1,3 +1,2 @@ - {{ $resizerScript := resources.Get "js/sidebar-load-width.js" | minify | fingerprint }} \ No newline at end of file diff --git a/layouts/release/baseof.html b/layouts/release/baseof.html index 66b5d9311e8..b06e06d5a29 100644 --- a/layouts/release/baseof.html +++ b/layouts/release/baseof.html @@ -8,6 +8,7 @@ > {{ partial "head.html" . }} + {{ partial "load-sidebars-width.html" . }} - - {{ partial "load-sidebars-width.html" . }} {{ partial "footer.html" . }} diff --git a/layouts/video/baseof.html b/layouts/video/baseof.html index c5e5f3d63a6..5ab0781f86e 100644 --- a/layouts/video/baseof.html +++ b/layouts/video/baseof.html @@ -8,6 +8,7 @@ > {{ partial "head.html" . }} + {{ partial "load-sidebars-width.html" . }} - - {{ partial "load-sidebars-width.html" . }} {{ partial "footer.html" . }} From c1ee94140c5f0feea793b5d29dfd3c8faa446792 Mon Sep 17 00:00:00 2001 From: Lenox Wiltshire Date: Tue, 23 Jun 2026 08:58:06 -0400 Subject: [PATCH 3/3] [chore]: remove !important from scss rules Signed-off-by: Lenox Wiltshire --- assets/scss/_styles_project.scss | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/assets/scss/_styles_project.scss b/assets/scss/_styles_project.scss index 32514fda080..6766bab163f 100644 --- a/assets/scss/_styles_project.scss +++ b/assets/scss/_styles_project.scss @@ -444,8 +444,8 @@ a:not([href]):not([class]):hover { #left-resizer { grid-column: 2; grid-row: 1; - padding-left: 0 !important; - padding-right: 0 !important; + padding-left: 0; + padding-right: 0; box-sizing: border-box; } @@ -469,8 +469,8 @@ a:not([href]):not([class]):hover { #right-resizer { grid-column: 4; grid-row: 1; - padding-left: 0 !important; - padding-right: 0 !important; + padding-left: 0; + padding-right: 0; box-sizing: border-box; }