From 895bfa1933ec8cbf70c10704a5254099f8f9cde5 Mon Sep 17 00:00:00 2001 From: Rudra2637 Date: Fri, 15 May 2026 01:18:39 +0530 Subject: [PATCH 01/14] feat(layouts): add resizable side panels with localStorage persistence Signed-off-by: Rudra2637 --- assets/js/resizable-panels.js | 259 +++++++++++++++++++++++++++++ assets/scss/_resizable-panels.scss | 138 +++++++++++++++ assets/scss/_styles_project.scss | 1 + layouts/partials/scripts.html | 16 ++ 4 files changed, 414 insertions(+) create mode 100644 assets/js/resizable-panels.js create mode 100644 assets/scss/_resizable-panels.scss create mode 100644 layouts/partials/scripts.html diff --git a/assets/js/resizable-panels.js b/assets/js/resizable-panels.js new file mode 100644 index 00000000000..388e12c0b7c --- /dev/null +++ b/assets/js/resizable-panels.js @@ -0,0 +1,259 @@ +/** + * Resizable Panels Feature + * Allows users to adjust the width of side panels (left sidebar and right TOC) + * Preferences are saved to localStorage and restored on page load + * Includes reset functionality to restore default widths + */ + +(function() { + 'use strict'; + + const STORAGE_KEY = 'layer5-docs-panel-widths'; + const DEFAULT_WIDTHS = { + sidebar: 2, // col-xl-2 = ~16.66% + toc: 2, // col-xl-2 = ~16.66% + main: 8 // col-xl-8 = ~66.66% + }; + + // CSS class shortcuts for Bootstrap grid columns + const COL_CLASSES = { + 'col-1': 8.33, + 'col-2': 16.66, + 'col-3': 25, + 'col-4': 33.33, + 'col-5': 41.66, + 'col-6': 50, + 'col-7': 58.33, + 'col-8': 66.66, + 'col-9': 75, + 'col-10': 83.33, + 'col-11': 91.66, + 'col-12': 100 + }; + + class ResizablePanels { + constructor() { + this.sidebar = document.querySelector('.td-sidebar'); + this.toc = document.querySelector('.td-sidebar-toc'); + this.main = document.querySelector('main[role="main"]'); + this.row = document.querySelector('.row.flex-xl-nowrap'); + + if (!this.row || !this.sidebar || !this.main) { + console.warn('Resizable panels: Required elements not found'); + return; + } + + this.isResizing = false; + this.currentResizeTarget = null; + this.startX = 0; + this.startWidth = 0; + + this.init(); + } + + init() { + // Load saved widths from localStorage + this.loadSavedWidths(); + + // Create resize handles + this.createResizeHandles(); + + // Add event listeners + this.addEventListeners(); + + // Add reset button + this.addResetButton(); + } + + loadSavedWidths() { + try { + const saved = localStorage.getItem(STORAGE_KEY); + if (saved) { + const widths = JSON.parse(saved); + this.applyWidths(widths); + } + } catch (error) { + console.error('Error loading saved panel widths:', error); + } + } + + createResizeHandles() { + // Create resize handle between sidebar and main content + const sidebarHandle = document.createElement('div'); + sidebarHandle.className = 'resizable-panel-handle resizable-panel-handle--right'; + sidebarHandle.setAttribute('data-resize-target', 'sidebar'); + sidebarHandle.setAttribute('title', 'Drag to resize sidebar'); + this.sidebar.appendChild(sidebarHandle); + + // Create resize handle for TOC (if it exists) + if (this.toc) { + const tocHandle = document.createElement('div'); + tocHandle.className = 'resizable-panel-handle resizable-panel-handle--left'; + tocHandle.setAttribute('data-resize-target', 'toc'); + tocHandle.setAttribute('title', 'Drag to resize table of contents'); + this.toc.appendChild(tocHandle); + } + } + + addEventListeners() { + document.addEventListener('mousedown', (e) => this.onMouseDown(e)); + document.addEventListener('mousemove', (e) => this.onMouseMove(e)); + document.addEventListener('mouseup', (e) => this.onMouseUp(e)); + } + + onMouseDown(e) { + if (!e.target.classList.contains('resizable-panel-handle')) { + return; + } + + this.isResizing = true; + this.currentResizeTarget = e.target.getAttribute('data-resize-target'); + this.startX = e.clientX; + + // Store current widths for delta calculation + this.startWidths = this.getCurrentWidths(); + + // Add active state + e.target.classList.add('resizable-panel-handle--active'); + document.body.style.userSelect = 'none'; + document.body.style.cursor = 'col-resize'; + } + + onMouseMove(e) { + if (!this.isResizing) return; + + const delta = e.clientX - this.startX; + const adjustment = delta / window.innerWidth; // Convert pixels to percentage-like ratio + + let newWidths = { ...this.startWidths }; + + if (this.currentResizeTarget === 'sidebar') { + // Resizing left sidebar + const sidebarPercent = (this.startWidths.sidebar * 100) / 12; // Convert col units to percentage + const mainPercent = (this.startWidths.main * 100) / 12; + + const newSidebarPercent = sidebarPercent + (adjustment * 100); + const newMainPercent = mainPercent - (adjustment * 100); + + // Constrain widths: min 1 col, max 5 cols for sidebar; min 4 cols for main + if (newSidebarPercent >= 8.33 && newSidebarPercent <= 41.66 && newMainPercent >= 33.33) { + newWidths.sidebar = Math.round((newSidebarPercent / 100) * 12); + newWidths.main = Math.round((newMainPercent / 100) * 12); + } + } else if (this.currentResizeTarget === 'toc') { + // Resizing right TOC panel + const tocPercent = (this.startWidths.toc * 100) / 12; + const mainPercent = (this.startWidths.main * 100) / 12; + + const newTocPercent = tocPercent - (adjustment * 100); + const newMainPercent = mainPercent + (adjustment * 100); + + // Constrain widths: min 1 col, max 5 cols for toc; min 4 cols for main + if (newTocPercent >= 8.33 && newTocPercent <= 41.66 && newMainPercent >= 33.33) { + newWidths.toc = Math.round((newTocPercent / 100) * 12); + newWidths.main = Math.round((newMainPercent / 100) * 12); + } + } + + this.applyWidths(newWidths); + } + + onMouseUp(e) { + if (!this.isResizing) return; + + this.isResizing = false; + const handle = document.querySelector('.resizable-panel-handle--active'); + if (handle) { + handle.classList.remove('resizable-panel-handle--active'); + } + + document.body.style.userSelect = ''; + document.body.style.cursor = ''; + + // Save widths to localStorage + this.savePanelWidths(); + } + + applyWidths(widths) { + const { sidebar, toc, main } = widths; + + // Update sidebar + this.removeBootstrapColClasses(this.sidebar); + this.sidebar.classList.add(`col-xl-${sidebar}`); + + // Update main + this.removeBootstrapColClasses(this.main); + this.main.classList.add(`col-xl-${main}`); + + // Update TOC if it exists + if (this.toc) { + this.removeBootstrapColClasses(this.toc); + this.toc.classList.add(`col-xl-${toc}`); + } + } + + getCurrentWidths() { + const getColNumber = (element) => { + const classes = element.className.split(' '); + const colClass = classes.find(c => c.match(/col-xl-\d+/)); + return colClass ? parseInt(colClass.split('-')[2]) : null; + }; + + return { + sidebar: getColNumber(this.sidebar) || DEFAULT_WIDTHS.sidebar, + toc: this.toc ? (getColNumber(this.toc) || DEFAULT_WIDTHS.toc) : DEFAULT_WIDTHS.toc, + main: getColNumber(this.main) || DEFAULT_WIDTHS.main + }; + } + + removeBootstrapColClasses(element) { + const classes = element.className.split(' ').filter(c => !c.match(/col-xl-\d+/)); + element.className = classes.join(' ').trim(); + } + + savePanelWidths() { + try { + const widths = this.getCurrentWidths(); + localStorage.setItem(STORAGE_KEY, JSON.stringify(widths)); + } catch (error) { + console.error('Error saving panel widths:', error); + } + } + + addResetButton() { + // Find the feature-info-container or page-header to add reset button + const pageHeader = document.querySelector('.page-header'); + if (!pageHeader) return; + + const resetButton = document.createElement('button'); + resetButton.id = 'reset-panel-widths'; + resetButton.className = 'btn btn-sm btn-outline-secondary ms-2'; + resetButton.innerHTML = ' Reset Layout'; + resetButton.setAttribute('title', 'Reset panel widths to default'); + + resetButton.addEventListener('click', () => this.resetPanelWidths()); + + // Find a good place to insert the button + const featureContainer = pageHeader.querySelector('.feature-info-container'); + if (featureContainer) { + featureContainer.insertAdjacentElement('beforeend', resetButton); + } else { + pageHeader.insertAdjacentElement('beforeend', resetButton); + } + } + + resetPanelWidths() { + this.applyWidths(DEFAULT_WIDTHS); + this.savePanelWidths(); + } + } + + // Initialize when DOM is ready + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => { + new ResizablePanels(); + }); + } else { + new ResizablePanels(); + } +})(); diff --git a/assets/scss/_resizable-panels.scss b/assets/scss/_resizable-panels.scss new file mode 100644 index 00000000000..e3248d7856d --- /dev/null +++ b/assets/scss/_resizable-panels.scss @@ -0,0 +1,138 @@ +/** + * Resizable Panels Styling + * Styles for the resize handles and drag interactions + */ + +// Resize handle styling +.resizable-panel-handle { + position: absolute; + background-color: transparent; + transition: background-color 0.2s ease; + z-index: 100; + + &--right { + // Right edge handle (between sidebar and main) + right: -3px; + top: 0; + bottom: 0; + width: 6px; + cursor: col-resize; + + &:hover, + &--active { + background-color: rgba(0, 211, 169, 0.3); + + &::after { + opacity: 1; + } + } + + &::after { + content: ''; + position: absolute; + right: 0; + top: 50%; + transform: translateY(-50%); + width: 2px; + height: 40px; + background-color: #00d3a9; + opacity: 0; + transition: opacity 0.2s ease; + border-radius: 1px; + } + } + + &--left { + // Left edge handle (between main and TOC) + left: -3px; + top: 0; + bottom: 0; + width: 6px; + cursor: col-resize; + + &:hover, + &--active { + background-color: rgba(0, 211, 169, 0.3); + + &::after { + opacity: 1; + } + } + + &::after { + content: ''; + position: absolute; + left: 0; + top: 50%; + transform: translateY(-50%); + width: 2px; + height: 40px; + background-color: #00d3a9; + opacity: 0; + transition: opacity 0.2s ease; + border-radius: 1px; + } + } + + &--active { + background-color: rgba(0, 211, 169, 0.5); + } +} + +// Reset button styling +#reset-panel-widths { + white-space: nowrap; + font-size: 0.875rem; + + i { + margin-right: 0.25rem; + } + + &:hover { + background-color: #00d3a9; + color: #fff; + } +} + +// Make panels relatively positioned for absolute handle positioning +.td-sidebar, +.td-sidebar-toc { + position: relative; +} + +// Smooth transitions for width changes +.td-sidebar, +.td-sidebar-toc, +main[role="main"] { + transition: flex: 0.3s ease; +} + +// Ensure row is flex container for proper layout +.row.flex-xl-nowrap { + display: flex; + flex-wrap: nowrap; + + > aside, + > main { + overflow-x: hidden; + } +} + +// Mobile/Tablet adjustments - disable resize handles on smaller screens +@media (max-width: 1199.98px) { + .resizable-panel-handle { + display: none; + } + + #reset-panel-widths { + display: none; + } +} + +// Print styles - hide resize handles +@media print { + .resizable-panel-handle, + #reset-panel-widths { + display: none !important; + } +} diff --git a/assets/scss/_styles_project.scss b/assets/scss/_styles_project.scss index 00ca1b8770b..84a003f5632 100644 --- a/assets/scss/_styles_project.scss +++ b/assets/scss/_styles_project.scss @@ -15,6 +15,7 @@ @import "elements_project"; @import "summary.scss"; @import "_kanvas-corner-popup.scss"; +@import "_resizable-panels.scss"; .navbar-dark { min-height: 5rem; diff --git a/layouts/partials/scripts.html b/layouts/partials/scripts.html new file mode 100644 index 00000000000..409b2634f53 --- /dev/null +++ b/layouts/partials/scripts.html @@ -0,0 +1,16 @@ + + +{{ $kanvasTransition := resources.Get "js/kanvas-architectural-transition.js" -}} +{{ if $kanvasTransition }} + +{{ end -}} + +{{ $kanvasPopup := resources.Get "js/kanvas-corner-popup.js" -}} +{{ if $kanvasPopup }} + +{{ end -}} + +{{ $offlineSearch := resources.Get "js/offline-search.js" -}} +{{ if $offlineSearch }} + +{{ end -}} From 53e9296bafa5d24eef6dc912a287ca7b1cab2a7a Mon Sep 17 00:00:00 2001 From: Rudra2637 Date: Fri, 15 May 2026 01:29:23 +0530 Subject: [PATCH 02/14] fix(scss): correct transition property syntax in resizable-panels Signed-off-by: Rudra2637 --- assets/scss/_resizable-panels.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/scss/_resizable-panels.scss b/assets/scss/_resizable-panels.scss index e3248d7856d..ae3e6167d5b 100644 --- a/assets/scss/_resizable-panels.scss +++ b/assets/scss/_resizable-panels.scss @@ -104,7 +104,7 @@ .td-sidebar, .td-sidebar-toc, main[role="main"] { - transition: flex: 0.3s ease; + transition: flex-basis 0.3s ease; } // Ensure row is flex container for proper layout From 8dfa71e4ba2a389e6a0d6972bd762d4a7f3378bc Mon Sep 17 00:00:00 2001 From: Rudra2637 Date: Fri, 15 May 2026 10:10:02 +0530 Subject: [PATCH 03/14] fix(ui): add bottom padding to sidebar for better spacing Signed-off-by: Rudra2637 --- assets/scss/_resizable-panels.scss | 33 +++++++++++++++++++----------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/assets/scss/_resizable-panels.scss b/assets/scss/_resizable-panels.scss index ae3e6167d5b..3311227f9a2 100644 --- a/assets/scss/_resizable-panels.scss +++ b/assets/scss/_resizable-panels.scss @@ -17,10 +17,11 @@ bottom: 0; width: 6px; cursor: col-resize; + background-color: rgba(0, 211, 169, 0.15); &:hover, &--active { - background-color: rgba(0, 211, 169, 0.3); + background-color: rgba(0, 211, 169, 0.4); &::after { opacity: 1; @@ -30,15 +31,16 @@ &::after { content: ''; position: absolute; - right: 0; + right: 1px; top: 50%; transform: translateY(-50%); - width: 2px; - height: 40px; + width: 3px; + height: 50px; background-color: #00d3a9; - opacity: 0; + opacity: 0.3; transition: opacity 0.2s ease; - border-radius: 1px; + border-radius: 2px; + box-shadow: 0 0 4px rgba(0, 211, 169, 0.5); } } @@ -49,10 +51,11 @@ bottom: 0; width: 6px; cursor: col-resize; + background-color: rgba(0, 211, 169, 0.15); &:hover, &--active { - background-color: rgba(0, 211, 169, 0.3); + background-color: rgba(0, 211, 169, 0.4); &::after { opacity: 1; @@ -62,15 +65,16 @@ &::after { content: ''; position: absolute; - left: 0; + left: 1px; top: 50%; transform: translateY(-50%); - width: 2px; - height: 40px; + width: 3px; + height: 50px; background-color: #00d3a9; - opacity: 0; + opacity: 0.3; transition: opacity 0.2s ease; - border-radius: 1px; + border-radius: 2px; + box-shadow: 0 0 4px rgba(0, 211, 169, 0.5); } } @@ -100,6 +104,11 @@ position: relative; } +// Ensure sidebar nav has proper scrolling with resizable panels +.td-sidebar-nav { + padding-bottom: 2rem; +} + // Smooth transitions for width changes .td-sidebar, .td-sidebar-toc, From a7040abbfd42409b0bfd43df3083d0264654b220 Mon Sep 17 00:00:00 2001 From: Rudra2637 Date: Fri, 15 May 2026 23:05:51 +0530 Subject: [PATCH 04/14] fix(sidebar): adjust section title spacing Signed-off-by: Rudra2637 --- assets/js/resizable-panels.js | 259 ----------------------------- assets/scss/_resizable-panels.scss | 147 ---------------- assets/scss/_styles_project.scss | 2 +- layouts/partials/scripts.html | 16 -- 4 files changed, 1 insertion(+), 423 deletions(-) delete mode 100644 assets/js/resizable-panels.js delete mode 100644 assets/scss/_resizable-panels.scss delete mode 100644 layouts/partials/scripts.html diff --git a/assets/js/resizable-panels.js b/assets/js/resizable-panels.js deleted file mode 100644 index 388e12c0b7c..00000000000 --- a/assets/js/resizable-panels.js +++ /dev/null @@ -1,259 +0,0 @@ -/** - * Resizable Panels Feature - * Allows users to adjust the width of side panels (left sidebar and right TOC) - * Preferences are saved to localStorage and restored on page load - * Includes reset functionality to restore default widths - */ - -(function() { - 'use strict'; - - const STORAGE_KEY = 'layer5-docs-panel-widths'; - const DEFAULT_WIDTHS = { - sidebar: 2, // col-xl-2 = ~16.66% - toc: 2, // col-xl-2 = ~16.66% - main: 8 // col-xl-8 = ~66.66% - }; - - // CSS class shortcuts for Bootstrap grid columns - const COL_CLASSES = { - 'col-1': 8.33, - 'col-2': 16.66, - 'col-3': 25, - 'col-4': 33.33, - 'col-5': 41.66, - 'col-6': 50, - 'col-7': 58.33, - 'col-8': 66.66, - 'col-9': 75, - 'col-10': 83.33, - 'col-11': 91.66, - 'col-12': 100 - }; - - class ResizablePanels { - constructor() { - this.sidebar = document.querySelector('.td-sidebar'); - this.toc = document.querySelector('.td-sidebar-toc'); - this.main = document.querySelector('main[role="main"]'); - this.row = document.querySelector('.row.flex-xl-nowrap'); - - if (!this.row || !this.sidebar || !this.main) { - console.warn('Resizable panels: Required elements not found'); - return; - } - - this.isResizing = false; - this.currentResizeTarget = null; - this.startX = 0; - this.startWidth = 0; - - this.init(); - } - - init() { - // Load saved widths from localStorage - this.loadSavedWidths(); - - // Create resize handles - this.createResizeHandles(); - - // Add event listeners - this.addEventListeners(); - - // Add reset button - this.addResetButton(); - } - - loadSavedWidths() { - try { - const saved = localStorage.getItem(STORAGE_KEY); - if (saved) { - const widths = JSON.parse(saved); - this.applyWidths(widths); - } - } catch (error) { - console.error('Error loading saved panel widths:', error); - } - } - - createResizeHandles() { - // Create resize handle between sidebar and main content - const sidebarHandle = document.createElement('div'); - sidebarHandle.className = 'resizable-panel-handle resizable-panel-handle--right'; - sidebarHandle.setAttribute('data-resize-target', 'sidebar'); - sidebarHandle.setAttribute('title', 'Drag to resize sidebar'); - this.sidebar.appendChild(sidebarHandle); - - // Create resize handle for TOC (if it exists) - if (this.toc) { - const tocHandle = document.createElement('div'); - tocHandle.className = 'resizable-panel-handle resizable-panel-handle--left'; - tocHandle.setAttribute('data-resize-target', 'toc'); - tocHandle.setAttribute('title', 'Drag to resize table of contents'); - this.toc.appendChild(tocHandle); - } - } - - addEventListeners() { - document.addEventListener('mousedown', (e) => this.onMouseDown(e)); - document.addEventListener('mousemove', (e) => this.onMouseMove(e)); - document.addEventListener('mouseup', (e) => this.onMouseUp(e)); - } - - onMouseDown(e) { - if (!e.target.classList.contains('resizable-panel-handle')) { - return; - } - - this.isResizing = true; - this.currentResizeTarget = e.target.getAttribute('data-resize-target'); - this.startX = e.clientX; - - // Store current widths for delta calculation - this.startWidths = this.getCurrentWidths(); - - // Add active state - e.target.classList.add('resizable-panel-handle--active'); - document.body.style.userSelect = 'none'; - document.body.style.cursor = 'col-resize'; - } - - onMouseMove(e) { - if (!this.isResizing) return; - - const delta = e.clientX - this.startX; - const adjustment = delta / window.innerWidth; // Convert pixels to percentage-like ratio - - let newWidths = { ...this.startWidths }; - - if (this.currentResizeTarget === 'sidebar') { - // Resizing left sidebar - const sidebarPercent = (this.startWidths.sidebar * 100) / 12; // Convert col units to percentage - const mainPercent = (this.startWidths.main * 100) / 12; - - const newSidebarPercent = sidebarPercent + (adjustment * 100); - const newMainPercent = mainPercent - (adjustment * 100); - - // Constrain widths: min 1 col, max 5 cols for sidebar; min 4 cols for main - if (newSidebarPercent >= 8.33 && newSidebarPercent <= 41.66 && newMainPercent >= 33.33) { - newWidths.sidebar = Math.round((newSidebarPercent / 100) * 12); - newWidths.main = Math.round((newMainPercent / 100) * 12); - } - } else if (this.currentResizeTarget === 'toc') { - // Resizing right TOC panel - const tocPercent = (this.startWidths.toc * 100) / 12; - const mainPercent = (this.startWidths.main * 100) / 12; - - const newTocPercent = tocPercent - (adjustment * 100); - const newMainPercent = mainPercent + (adjustment * 100); - - // Constrain widths: min 1 col, max 5 cols for toc; min 4 cols for main - if (newTocPercent >= 8.33 && newTocPercent <= 41.66 && newMainPercent >= 33.33) { - newWidths.toc = Math.round((newTocPercent / 100) * 12); - newWidths.main = Math.round((newMainPercent / 100) * 12); - } - } - - this.applyWidths(newWidths); - } - - onMouseUp(e) { - if (!this.isResizing) return; - - this.isResizing = false; - const handle = document.querySelector('.resizable-panel-handle--active'); - if (handle) { - handle.classList.remove('resizable-panel-handle--active'); - } - - document.body.style.userSelect = ''; - document.body.style.cursor = ''; - - // Save widths to localStorage - this.savePanelWidths(); - } - - applyWidths(widths) { - const { sidebar, toc, main } = widths; - - // Update sidebar - this.removeBootstrapColClasses(this.sidebar); - this.sidebar.classList.add(`col-xl-${sidebar}`); - - // Update main - this.removeBootstrapColClasses(this.main); - this.main.classList.add(`col-xl-${main}`); - - // Update TOC if it exists - if (this.toc) { - this.removeBootstrapColClasses(this.toc); - this.toc.classList.add(`col-xl-${toc}`); - } - } - - getCurrentWidths() { - const getColNumber = (element) => { - const classes = element.className.split(' '); - const colClass = classes.find(c => c.match(/col-xl-\d+/)); - return colClass ? parseInt(colClass.split('-')[2]) : null; - }; - - return { - sidebar: getColNumber(this.sidebar) || DEFAULT_WIDTHS.sidebar, - toc: this.toc ? (getColNumber(this.toc) || DEFAULT_WIDTHS.toc) : DEFAULT_WIDTHS.toc, - main: getColNumber(this.main) || DEFAULT_WIDTHS.main - }; - } - - removeBootstrapColClasses(element) { - const classes = element.className.split(' ').filter(c => !c.match(/col-xl-\d+/)); - element.className = classes.join(' ').trim(); - } - - savePanelWidths() { - try { - const widths = this.getCurrentWidths(); - localStorage.setItem(STORAGE_KEY, JSON.stringify(widths)); - } catch (error) { - console.error('Error saving panel widths:', error); - } - } - - addResetButton() { - // Find the feature-info-container or page-header to add reset button - const pageHeader = document.querySelector('.page-header'); - if (!pageHeader) return; - - const resetButton = document.createElement('button'); - resetButton.id = 'reset-panel-widths'; - resetButton.className = 'btn btn-sm btn-outline-secondary ms-2'; - resetButton.innerHTML = ' Reset Layout'; - resetButton.setAttribute('title', 'Reset panel widths to default'); - - resetButton.addEventListener('click', () => this.resetPanelWidths()); - - // Find a good place to insert the button - const featureContainer = pageHeader.querySelector('.feature-info-container'); - if (featureContainer) { - featureContainer.insertAdjacentElement('beforeend', resetButton); - } else { - pageHeader.insertAdjacentElement('beforeend', resetButton); - } - } - - resetPanelWidths() { - this.applyWidths(DEFAULT_WIDTHS); - this.savePanelWidths(); - } - } - - // Initialize when DOM is ready - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', () => { - new ResizablePanels(); - }); - } else { - new ResizablePanels(); - } -})(); diff --git a/assets/scss/_resizable-panels.scss b/assets/scss/_resizable-panels.scss deleted file mode 100644 index 3311227f9a2..00000000000 --- a/assets/scss/_resizable-panels.scss +++ /dev/null @@ -1,147 +0,0 @@ -/** - * Resizable Panels Styling - * Styles for the resize handles and drag interactions - */ - -// Resize handle styling -.resizable-panel-handle { - position: absolute; - background-color: transparent; - transition: background-color 0.2s ease; - z-index: 100; - - &--right { - // Right edge handle (between sidebar and main) - right: -3px; - top: 0; - bottom: 0; - width: 6px; - cursor: col-resize; - background-color: rgba(0, 211, 169, 0.15); - - &:hover, - &--active { - background-color: rgba(0, 211, 169, 0.4); - - &::after { - opacity: 1; - } - } - - &::after { - content: ''; - position: absolute; - right: 1px; - top: 50%; - transform: translateY(-50%); - width: 3px; - height: 50px; - background-color: #00d3a9; - opacity: 0.3; - transition: opacity 0.2s ease; - border-radius: 2px; - box-shadow: 0 0 4px rgba(0, 211, 169, 0.5); - } - } - - &--left { - // Left edge handle (between main and TOC) - left: -3px; - top: 0; - bottom: 0; - width: 6px; - cursor: col-resize; - background-color: rgba(0, 211, 169, 0.15); - - &:hover, - &--active { - background-color: rgba(0, 211, 169, 0.4); - - &::after { - opacity: 1; - } - } - - &::after { - content: ''; - position: absolute; - left: 1px; - top: 50%; - transform: translateY(-50%); - width: 3px; - height: 50px; - background-color: #00d3a9; - opacity: 0.3; - transition: opacity 0.2s ease; - border-radius: 2px; - box-shadow: 0 0 4px rgba(0, 211, 169, 0.5); - } - } - - &--active { - background-color: rgba(0, 211, 169, 0.5); - } -} - -// Reset button styling -#reset-panel-widths { - white-space: nowrap; - font-size: 0.875rem; - - i { - margin-right: 0.25rem; - } - - &:hover { - background-color: #00d3a9; - color: #fff; - } -} - -// Make panels relatively positioned for absolute handle positioning -.td-sidebar, -.td-sidebar-toc { - position: relative; -} - -// Ensure sidebar nav has proper scrolling with resizable panels -.td-sidebar-nav { - padding-bottom: 2rem; -} - -// Smooth transitions for width changes -.td-sidebar, -.td-sidebar-toc, -main[role="main"] { - transition: flex-basis 0.3s ease; -} - -// Ensure row is flex container for proper layout -.row.flex-xl-nowrap { - display: flex; - flex-wrap: nowrap; - - > aside, - > main { - overflow-x: hidden; - } -} - -// Mobile/Tablet adjustments - disable resize handles on smaller screens -@media (max-width: 1199.98px) { - .resizable-panel-handle { - display: none; - } - - #reset-panel-widths { - display: none; - } -} - -// Print styles - hide resize handles -@media print { - .resizable-panel-handle, - #reset-panel-widths { - display: none !important; - } -} diff --git a/assets/scss/_styles_project.scss b/assets/scss/_styles_project.scss index 84a003f5632..2b7e6112a37 100644 --- a/assets/scss/_styles_project.scss +++ b/assets/scss/_styles_project.scss @@ -15,7 +15,6 @@ @import "elements_project"; @import "summary.scss"; @import "_kanvas-corner-popup.scss"; -@import "_resizable-panels.scss"; .navbar-dark { min-height: 5rem; @@ -273,6 +272,7 @@ a:not([href]):not([class]):hover { color: $gray-400; font-weight: $font-weight-bold; } + margin-top: 0.5rem; } .td-sidebar-link { diff --git a/layouts/partials/scripts.html b/layouts/partials/scripts.html deleted file mode 100644 index 409b2634f53..00000000000 --- a/layouts/partials/scripts.html +++ /dev/null @@ -1,16 +0,0 @@ - - -{{ $kanvasTransition := resources.Get "js/kanvas-architectural-transition.js" -}} -{{ if $kanvasTransition }} - -{{ end -}} - -{{ $kanvasPopup := resources.Get "js/kanvas-corner-popup.js" -}} -{{ if $kanvasPopup }} - -{{ end -}} - -{{ $offlineSearch := resources.Get "js/offline-search.js" -}} -{{ if $offlineSearch }} - -{{ end -}} From 36714595c40057c2affdf70027ee52e6ffa07403 Mon Sep 17 00:00:00 2001 From: Rudra2637 Date: Thu, 21 May 2026 17:09:16 +0530 Subject: [PATCH 05/14] restore resizable side panels with localStorage persistence Signed-off-by: Rudra2637 --- assets/scss/_resizable-panels.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/scss/_resizable-panels.scss b/assets/scss/_resizable-panels.scss index e3248d7856d..35dcdc829d4 100644 --- a/assets/scss/_resizable-panels.scss +++ b/assets/scss/_resizable-panels.scss @@ -104,7 +104,7 @@ .td-sidebar, .td-sidebar-toc, main[role="main"] { - transition: flex: 0.3s ease; + transition: width 0.3s ease; } // Ensure row is flex container for proper layout From 2e8475c82466d18a1b8c1953b3dbad3db692a659 Mon Sep 17 00:00:00 2001 From: Rudra2637 Date: Thu, 21 May 2026 17:19:25 +0530 Subject: [PATCH 06/14] restore resizable side panels - recreate missing JS and template files Signed-off-by: Rudra2637 --- assets/js/resizable-panels.js | 259 +++++++++++++++++++++++++++++++ assets/scss/_styles_project.scss | 1 + layouts/partials/scripts.html | 16 ++ 3 files changed, 276 insertions(+) create mode 100644 assets/js/resizable-panels.js create mode 100644 layouts/partials/scripts.html diff --git a/assets/js/resizable-panels.js b/assets/js/resizable-panels.js new file mode 100644 index 00000000000..388e12c0b7c --- /dev/null +++ b/assets/js/resizable-panels.js @@ -0,0 +1,259 @@ +/** + * Resizable Panels Feature + * Allows users to adjust the width of side panels (left sidebar and right TOC) + * Preferences are saved to localStorage and restored on page load + * Includes reset functionality to restore default widths + */ + +(function() { + 'use strict'; + + const STORAGE_KEY = 'layer5-docs-panel-widths'; + const DEFAULT_WIDTHS = { + sidebar: 2, // col-xl-2 = ~16.66% + toc: 2, // col-xl-2 = ~16.66% + main: 8 // col-xl-8 = ~66.66% + }; + + // CSS class shortcuts for Bootstrap grid columns + const COL_CLASSES = { + 'col-1': 8.33, + 'col-2': 16.66, + 'col-3': 25, + 'col-4': 33.33, + 'col-5': 41.66, + 'col-6': 50, + 'col-7': 58.33, + 'col-8': 66.66, + 'col-9': 75, + 'col-10': 83.33, + 'col-11': 91.66, + 'col-12': 100 + }; + + class ResizablePanels { + constructor() { + this.sidebar = document.querySelector('.td-sidebar'); + this.toc = document.querySelector('.td-sidebar-toc'); + this.main = document.querySelector('main[role="main"]'); + this.row = document.querySelector('.row.flex-xl-nowrap'); + + if (!this.row || !this.sidebar || !this.main) { + console.warn('Resizable panels: Required elements not found'); + return; + } + + this.isResizing = false; + this.currentResizeTarget = null; + this.startX = 0; + this.startWidth = 0; + + this.init(); + } + + init() { + // Load saved widths from localStorage + this.loadSavedWidths(); + + // Create resize handles + this.createResizeHandles(); + + // Add event listeners + this.addEventListeners(); + + // Add reset button + this.addResetButton(); + } + + loadSavedWidths() { + try { + const saved = localStorage.getItem(STORAGE_KEY); + if (saved) { + const widths = JSON.parse(saved); + this.applyWidths(widths); + } + } catch (error) { + console.error('Error loading saved panel widths:', error); + } + } + + createResizeHandles() { + // Create resize handle between sidebar and main content + const sidebarHandle = document.createElement('div'); + sidebarHandle.className = 'resizable-panel-handle resizable-panel-handle--right'; + sidebarHandle.setAttribute('data-resize-target', 'sidebar'); + sidebarHandle.setAttribute('title', 'Drag to resize sidebar'); + this.sidebar.appendChild(sidebarHandle); + + // Create resize handle for TOC (if it exists) + if (this.toc) { + const tocHandle = document.createElement('div'); + tocHandle.className = 'resizable-panel-handle resizable-panel-handle--left'; + tocHandle.setAttribute('data-resize-target', 'toc'); + tocHandle.setAttribute('title', 'Drag to resize table of contents'); + this.toc.appendChild(tocHandle); + } + } + + addEventListeners() { + document.addEventListener('mousedown', (e) => this.onMouseDown(e)); + document.addEventListener('mousemove', (e) => this.onMouseMove(e)); + document.addEventListener('mouseup', (e) => this.onMouseUp(e)); + } + + onMouseDown(e) { + if (!e.target.classList.contains('resizable-panel-handle')) { + return; + } + + this.isResizing = true; + this.currentResizeTarget = e.target.getAttribute('data-resize-target'); + this.startX = e.clientX; + + // Store current widths for delta calculation + this.startWidths = this.getCurrentWidths(); + + // Add active state + e.target.classList.add('resizable-panel-handle--active'); + document.body.style.userSelect = 'none'; + document.body.style.cursor = 'col-resize'; + } + + onMouseMove(e) { + if (!this.isResizing) return; + + const delta = e.clientX - this.startX; + const adjustment = delta / window.innerWidth; // Convert pixels to percentage-like ratio + + let newWidths = { ...this.startWidths }; + + if (this.currentResizeTarget === 'sidebar') { + // Resizing left sidebar + const sidebarPercent = (this.startWidths.sidebar * 100) / 12; // Convert col units to percentage + const mainPercent = (this.startWidths.main * 100) / 12; + + const newSidebarPercent = sidebarPercent + (adjustment * 100); + const newMainPercent = mainPercent - (adjustment * 100); + + // Constrain widths: min 1 col, max 5 cols for sidebar; min 4 cols for main + if (newSidebarPercent >= 8.33 && newSidebarPercent <= 41.66 && newMainPercent >= 33.33) { + newWidths.sidebar = Math.round((newSidebarPercent / 100) * 12); + newWidths.main = Math.round((newMainPercent / 100) * 12); + } + } else if (this.currentResizeTarget === 'toc') { + // Resizing right TOC panel + const tocPercent = (this.startWidths.toc * 100) / 12; + const mainPercent = (this.startWidths.main * 100) / 12; + + const newTocPercent = tocPercent - (adjustment * 100); + const newMainPercent = mainPercent + (adjustment * 100); + + // Constrain widths: min 1 col, max 5 cols for toc; min 4 cols for main + if (newTocPercent >= 8.33 && newTocPercent <= 41.66 && newMainPercent >= 33.33) { + newWidths.toc = Math.round((newTocPercent / 100) * 12); + newWidths.main = Math.round((newMainPercent / 100) * 12); + } + } + + this.applyWidths(newWidths); + } + + onMouseUp(e) { + if (!this.isResizing) return; + + this.isResizing = false; + const handle = document.querySelector('.resizable-panel-handle--active'); + if (handle) { + handle.classList.remove('resizable-panel-handle--active'); + } + + document.body.style.userSelect = ''; + document.body.style.cursor = ''; + + // Save widths to localStorage + this.savePanelWidths(); + } + + applyWidths(widths) { + const { sidebar, toc, main } = widths; + + // Update sidebar + this.removeBootstrapColClasses(this.sidebar); + this.sidebar.classList.add(`col-xl-${sidebar}`); + + // Update main + this.removeBootstrapColClasses(this.main); + this.main.classList.add(`col-xl-${main}`); + + // Update TOC if it exists + if (this.toc) { + this.removeBootstrapColClasses(this.toc); + this.toc.classList.add(`col-xl-${toc}`); + } + } + + getCurrentWidths() { + const getColNumber = (element) => { + const classes = element.className.split(' '); + const colClass = classes.find(c => c.match(/col-xl-\d+/)); + return colClass ? parseInt(colClass.split('-')[2]) : null; + }; + + return { + sidebar: getColNumber(this.sidebar) || DEFAULT_WIDTHS.sidebar, + toc: this.toc ? (getColNumber(this.toc) || DEFAULT_WIDTHS.toc) : DEFAULT_WIDTHS.toc, + main: getColNumber(this.main) || DEFAULT_WIDTHS.main + }; + } + + removeBootstrapColClasses(element) { + const classes = element.className.split(' ').filter(c => !c.match(/col-xl-\d+/)); + element.className = classes.join(' ').trim(); + } + + savePanelWidths() { + try { + const widths = this.getCurrentWidths(); + localStorage.setItem(STORAGE_KEY, JSON.stringify(widths)); + } catch (error) { + console.error('Error saving panel widths:', error); + } + } + + addResetButton() { + // Find the feature-info-container or page-header to add reset button + const pageHeader = document.querySelector('.page-header'); + if (!pageHeader) return; + + const resetButton = document.createElement('button'); + resetButton.id = 'reset-panel-widths'; + resetButton.className = 'btn btn-sm btn-outline-secondary ms-2'; + resetButton.innerHTML = ' Reset Layout'; + resetButton.setAttribute('title', 'Reset panel widths to default'); + + resetButton.addEventListener('click', () => this.resetPanelWidths()); + + // Find a good place to insert the button + const featureContainer = pageHeader.querySelector('.feature-info-container'); + if (featureContainer) { + featureContainer.insertAdjacentElement('beforeend', resetButton); + } else { + pageHeader.insertAdjacentElement('beforeend', resetButton); + } + } + + resetPanelWidths() { + this.applyWidths(DEFAULT_WIDTHS); + this.savePanelWidths(); + } + } + + // Initialize when DOM is ready + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => { + new ResizablePanels(); + }); + } else { + new ResizablePanels(); + } +})(); diff --git a/assets/scss/_styles_project.scss b/assets/scss/_styles_project.scss index 2b7e6112a37..44038655971 100644 --- a/assets/scss/_styles_project.scss +++ b/assets/scss/_styles_project.scss @@ -15,6 +15,7 @@ @import "elements_project"; @import "summary.scss"; @import "_kanvas-corner-popup.scss"; +@import "_resizable-panels.scss"; .navbar-dark { min-height: 5rem; diff --git a/layouts/partials/scripts.html b/layouts/partials/scripts.html new file mode 100644 index 00000000000..409b2634f53 --- /dev/null +++ b/layouts/partials/scripts.html @@ -0,0 +1,16 @@ + + +{{ $kanvasTransition := resources.Get "js/kanvas-architectural-transition.js" -}} +{{ if $kanvasTransition }} + +{{ end -}} + +{{ $kanvasPopup := resources.Get "js/kanvas-corner-popup.js" -}} +{{ if $kanvasPopup }} + +{{ end -}} + +{{ $offlineSearch := resources.Get "js/offline-search.js" -}} +{{ if $offlineSearch }} + +{{ end -}} From 59647a8a4d9ff169e7ff429c256277fcdee41610 Mon Sep 17 00:00:00 2001 From: Rudra2637 Date: Thu, 21 May 2026 19:21:17 +0530 Subject: [PATCH 07/14] Fixed Ui Signed-off-by: Rudra2637 --- assets/js/resizable-panels.js | 421 ++++++++++++++++------------- assets/scss/_resizable-panels.scss | 200 +++++++------- 2 files changed, 341 insertions(+), 280 deletions(-) diff --git a/assets/js/resizable-panels.js b/assets/js/resizable-panels.js index 388e12c0b7c..c8c034d6268 100644 --- a/assets/js/resizable-panels.js +++ b/assets/js/resizable-panels.js @@ -1,259 +1,316 @@ /** - * Resizable Panels Feature - * Allows users to adjust the width of side panels (left sidebar and right TOC) - * Preferences are saved to localStorage and restored on page load - * Includes reset functionality to restore default widths + * Resizable docs panels. + * + * Lets readers adjust the left navigation and right TOC widths, persists those + * preferences in localStorage, and provides a reset control. */ - -(function() { +(function () { 'use strict'; const STORAGE_KEY = 'layer5-docs-panel-widths'; + const DESKTOP_QUERY = '(min-width: 1200px)'; + const STEP = 1; const DEFAULT_WIDTHS = { - sidebar: 2, // col-xl-2 = ~16.66% - toc: 2, // col-xl-2 = ~16.66% - main: 8 // col-xl-8 = ~66.66% + sidebar: 16.6667, + toc: 16.6667, }; - - // CSS class shortcuts for Bootstrap grid columns - const COL_CLASSES = { - 'col-1': 8.33, - 'col-2': 16.66, - 'col-3': 25, - 'col-4': 33.33, - 'col-5': 41.66, - 'col-6': 50, - 'col-7': 58.33, - 'col-8': 66.66, - 'col-9': 75, - 'col-10': 83.33, - 'col-11': 91.66, - 'col-12': 100 + const LIMITS = { + sidebar: { min: 12, max: 32 }, + toc: { min: 10, max: 28 }, + main: { min: 42 }, }; class ResizablePanels { - constructor() { - this.sidebar = document.querySelector('.td-sidebar'); - this.toc = document.querySelector('.td-sidebar-toc'); - this.main = document.querySelector('main[role="main"]'); - this.row = document.querySelector('.row.flex-xl-nowrap'); - - if (!this.row || !this.sidebar || !this.main) { - console.warn('Resizable panels: Required elements not found'); + constructor(row) { + this.row = row; + this.sidebar = row.querySelector('.td-sidebar'); + this.main = row.querySelector('main[role="main"]'); + this.toc = row.querySelector('.td-sidebar-toc'); + this.mediaQuery = window.matchMedia(DESKTOP_QUERY); + this.activeHandle = null; + this.startX = 0; + this.startWidths = null; + this.widths = this.getStoredWidths(); + + if (!this.sidebar || !this.main) { return; } - this.isResizing = false; - this.currentResizeTarget = null; - this.startX = 0; - this.startWidth = 0; - this.init(); } init() { - // Load saved widths from localStorage - this.loadSavedWidths(); - - // Create resize handles - this.createResizeHandles(); - - // Add event listeners - this.addEventListeners(); - - // Add reset button - this.addResetButton(); + this.row.classList.add('resizable-panels-ready'); + this.applyWidths(this.widths); + this.createHandles(); + this.createResetButton(); + this.bindEvents(); } - loadSavedWidths() { - try { - const saved = localStorage.getItem(STORAGE_KEY); - if (saved) { - const widths = JSON.parse(saved); - this.applyWidths(widths); + bindEvents() { + document.addEventListener('pointermove', (event) => + this.onPointerMove(event), + ); + document.addEventListener('pointerup', () => this.stopResize()); + document.addEventListener('pointercancel', () => this.stopResize()); + + this.mediaQuery.addEventListener('change', () => { + if (this.mediaQuery.matches) { + this.applyWidths(this.widths); } - } catch (error) { - console.error('Error loading saved panel widths:', error); - } + }); } - createResizeHandles() { - // Create resize handle between sidebar and main content - const sidebarHandle = document.createElement('div'); - sidebarHandle.className = 'resizable-panel-handle resizable-panel-handle--right'; - sidebarHandle.setAttribute('data-resize-target', 'sidebar'); - sidebarHandle.setAttribute('title', 'Drag to resize sidebar'); - this.sidebar.appendChild(sidebarHandle); + createHandles() { + this.sidebarHandle = this.createHandle( + 'sidebar', + 'Resize navigation sidebar', + ); + this.sidebar.appendChild(this.sidebarHandle); - // Create resize handle for TOC (if it exists) if (this.toc) { - const tocHandle = document.createElement('div'); - tocHandle.className = 'resizable-panel-handle resizable-panel-handle--left'; - tocHandle.setAttribute('data-resize-target', 'toc'); - tocHandle.setAttribute('title', 'Drag to resize table of contents'); - this.toc.appendChild(tocHandle); + this.tocHandle = this.createHandle('toc', 'Resize table of contents'); + this.toc.appendChild(this.tocHandle); } } - addEventListeners() { - document.addEventListener('mousedown', (e) => this.onMouseDown(e)); - document.addEventListener('mousemove', (e) => this.onMouseMove(e)); - document.addEventListener('mouseup', (e) => this.onMouseUp(e)); + createHandle(target, label) { + const handle = document.createElement('div'); + handle.className = `resizable-panel-handle resizable-panel-handle--${target}`; + handle.dataset.resizeTarget = target; + handle.tabIndex = 0; + handle.setAttribute('aria-label', label); + handle.setAttribute('aria-orientation', 'vertical'); + handle.setAttribute('role', 'separator'); + handle.title = label; + + handle.addEventListener('pointerdown', (event) => + this.startResize(event, handle), + ); + handle.addEventListener('keydown', (event) => + this.onHandleKeydown(event, target), + ); + + return handle; } - onMouseDown(e) { - if (!e.target.classList.contains('resizable-panel-handle')) { - return; - } + createResetButton() { + const resetButton = document.createElement('button'); + resetButton.type = 'button'; + resetButton.id = 'reset-panel-widths'; + resetButton.className = 'resizable-panel-reset'; + resetButton.innerHTML = + 'Reset layout'; + resetButton.title = 'Reset panel widths to default'; + resetButton.addEventListener('click', () => this.reset()); - this.isResizing = true; - this.currentResizeTarget = e.target.getAttribute('data-resize-target'); - this.startX = e.clientX; + this.sidebar.appendChild(resetButton); + } - // Store current widths for delta calculation - this.startWidths = this.getCurrentWidths(); + startResize(event, handle) { + if (!this.mediaQuery.matches) { + return; + } - // Add active state - e.target.classList.add('resizable-panel-handle--active'); - document.body.style.userSelect = 'none'; - document.body.style.cursor = 'col-resize'; + event.preventDefault(); + this.activeHandle = handle; + this.startX = event.clientX; + this.startWidths = { ...this.widths }; + handle.classList.add('resizable-panel-handle--active'); + handle.setPointerCapture(event.pointerId); + document.body.classList.add('resizable-panels-dragging'); } - onMouseMove(e) { - if (!this.isResizing) return; + onPointerMove(event) { + if (!this.activeHandle || !this.startWidths) { + return; + } + + const rowWidth = this.row.getBoundingClientRect().width; + if (!rowWidth) { + return; + } - const delta = e.clientX - this.startX; - const adjustment = delta / window.innerWidth; // Convert pixels to percentage-like ratio + const target = this.activeHandle.dataset.resizeTarget; + const delta = ((event.clientX - this.startX) / rowWidth) * 100; + const nextWidths = { ...this.startWidths }; - let newWidths = { ...this.startWidths }; + if (target === 'sidebar') { + nextWidths.sidebar = this.startWidths.sidebar + delta; + } - if (this.currentResizeTarget === 'sidebar') { - // Resizing left sidebar - const sidebarPercent = (this.startWidths.sidebar * 100) / 12; // Convert col units to percentage - const mainPercent = (this.startWidths.main * 100) / 12; + if (target === 'toc') { + nextWidths.toc = this.startWidths.toc - delta; + } - const newSidebarPercent = sidebarPercent + (adjustment * 100); - const newMainPercent = mainPercent - (adjustment * 100); + this.widths = this.normalizeWidths(nextWidths); + this.applyWidths(this.widths); + } - // Constrain widths: min 1 col, max 5 cols for sidebar; min 4 cols for main - if (newSidebarPercent >= 8.33 && newSidebarPercent <= 41.66 && newMainPercent >= 33.33) { - newWidths.sidebar = Math.round((newSidebarPercent / 100) * 12); - newWidths.main = Math.round((newMainPercent / 100) * 12); - } - } else if (this.currentResizeTarget === 'toc') { - // Resizing right TOC panel - const tocPercent = (this.startWidths.toc * 100) / 12; - const mainPercent = (this.startWidths.main * 100) / 12; - - const newTocPercent = tocPercent - (adjustment * 100); - const newMainPercent = mainPercent + (adjustment * 100); - - // Constrain widths: min 1 col, max 5 cols for toc; min 4 cols for main - if (newTocPercent >= 8.33 && newTocPercent <= 41.66 && newMainPercent >= 33.33) { - newWidths.toc = Math.round((newTocPercent / 100) * 12); - newWidths.main = Math.round((newMainPercent / 100) * 12); - } + stopResize() { + if (!this.activeHandle) { + return; } - this.applyWidths(newWidths); + this.activeHandle.classList.remove('resizable-panel-handle--active'); + this.activeHandle = null; + this.startWidths = null; + document.body.classList.remove('resizable-panels-dragging'); + this.saveWidths(); } - onMouseUp(e) { - if (!this.isResizing) return; + onHandleKeydown(event, target) { + if (!this.mediaQuery.matches) { + return; + } - this.isResizing = false; - const handle = document.querySelector('.resizable-panel-handle--active'); - if (handle) { - handle.classList.remove('resizable-panel-handle--active'); + const keys = ['ArrowLeft', 'ArrowRight', 'Home', 'End']; + if (!keys.includes(event.key)) { + return; } - document.body.style.userSelect = ''; - document.body.style.cursor = ''; + event.preventDefault(); + const nextWidths = { ...this.widths }; + const direction = event.key === 'ArrowRight' ? 1 : -1; + + if (event.key === 'Home') { + nextWidths[target] = LIMITS[target].min; + } else if (event.key === 'End') { + nextWidths[target] = LIMITS[target].max; + } else if (target === 'toc') { + nextWidths.toc -= direction * STEP; + } else { + nextWidths.sidebar += direction * STEP; + } - // Save widths to localStorage - this.savePanelWidths(); + this.widths = this.normalizeWidths(nextWidths); + this.applyWidths(this.widths); + this.saveWidths(); } applyWidths(widths) { - const { sidebar, toc, main } = widths; - - // Update sidebar - this.removeBootstrapColClasses(this.sidebar); - this.sidebar.classList.add(`col-xl-${sidebar}`); + const normalized = this.normalizeWidths(widths); + const mainWidth = 100 - normalized.sidebar - normalized.toc; + + this.row.style.setProperty( + '--docs-sidebar-width', + `${normalized.sidebar}%`, + ); + this.row.style.setProperty('--docs-toc-width', `${normalized.toc}%`); + this.row.style.setProperty('--docs-main-width', `${mainWidth}%`); + this.updateHandleValues(normalized); + } - // Update main - this.removeBootstrapColClasses(this.main); - this.main.classList.add(`col-xl-${main}`); + updateHandleValues(widths) { + if (this.sidebarHandle) { + this.sidebarHandle.setAttribute('aria-valuemin', LIMITS.sidebar.min); + this.sidebarHandle.setAttribute('aria-valuemax', LIMITS.sidebar.max); + this.sidebarHandle.setAttribute( + 'aria-valuenow', + Math.round(widths.sidebar), + ); + } - // Update TOC if it exists - if (this.toc) { - this.removeBootstrapColClasses(this.toc); - this.toc.classList.add(`col-xl-${toc}`); + if (this.tocHandle) { + this.tocHandle.setAttribute('aria-valuemin', LIMITS.toc.min); + this.tocHandle.setAttribute('aria-valuemax', LIMITS.toc.max); + this.tocHandle.setAttribute('aria-valuenow', Math.round(widths.toc)); } } - getCurrentWidths() { - const getColNumber = (element) => { - const classes = element.className.split(' '); - const colClass = classes.find(c => c.match(/col-xl-\d+/)); - return colClass ? parseInt(colClass.split('-')[2]) : null; - }; - - return { - sidebar: getColNumber(this.sidebar) || DEFAULT_WIDTHS.sidebar, - toc: this.toc ? (getColNumber(this.toc) || DEFAULT_WIDTHS.toc) : DEFAULT_WIDTHS.toc, - main: getColNumber(this.main) || DEFAULT_WIDTHS.main - }; + reset() { + this.widths = { ...DEFAULT_WIDTHS }; + this.applyWidths(this.widths); + localStorage.removeItem(STORAGE_KEY); } - removeBootstrapColClasses(element) { - const classes = element.className.split(' ').filter(c => !c.match(/col-xl-\d+/)); - element.className = classes.join(' ').trim(); + getStoredWidths() { + try { + const saved = JSON.parse(localStorage.getItem(STORAGE_KEY)); + if ( + saved && + (saved.sidebar <= 12 || saved.toc <= 12 || saved.main <= 12) + ) { + return this.normalizeWidths({ + sidebar: saved.sidebar + ? (saved.sidebar / 12) * 100 + : DEFAULT_WIDTHS.sidebar, + toc: saved.toc ? (saved.toc / 12) * 100 : DEFAULT_WIDTHS.toc, + }); + } + + return this.normalizeWidths(saved || DEFAULT_WIDTHS); + } catch (error) { + return { ...DEFAULT_WIDTHS }; + } } - savePanelWidths() { + saveWidths() { try { - const widths = this.getCurrentWidths(); - localStorage.setItem(STORAGE_KEY, JSON.stringify(widths)); + localStorage.setItem(STORAGE_KEY, JSON.stringify(this.widths)); } catch (error) { - console.error('Error saving panel widths:', error); + // Ignore storage failures so resizing still works in private modes. } } - addResetButton() { - // Find the feature-info-container or page-header to add reset button - const pageHeader = document.querySelector('.page-header'); - if (!pageHeader) return; + normalizeWidths(widths) { + const next = { + sidebar: this.clamp( + Number(widths && widths.sidebar), + LIMITS.sidebar.min, + LIMITS.sidebar.max, + DEFAULT_WIDTHS.sidebar, + ), + toc: this.toc + ? this.clamp( + Number(widths && widths.toc), + LIMITS.toc.min, + LIMITS.toc.max, + DEFAULT_WIDTHS.toc, + ) + : 0, + }; - const resetButton = document.createElement('button'); - resetButton.id = 'reset-panel-widths'; - resetButton.className = 'btn btn-sm btn-outline-secondary ms-2'; - resetButton.innerHTML = ' Reset Layout'; - resetButton.setAttribute('title', 'Reset panel widths to default'); + const availableForPanels = 100 - LIMITS.main.min; + const panelTotal = next.sidebar + next.toc; - resetButton.addEventListener('click', () => this.resetPanelWidths()); + if (panelTotal > availableForPanels) { + const overflow = panelTotal - availableForPanels; - // Find a good place to insert the button - const featureContainer = pageHeader.querySelector('.feature-info-container'); - if (featureContainer) { - featureContainer.insertAdjacentElement('beforeend', resetButton); - } else { - pageHeader.insertAdjacentElement('beforeend', resetButton); + if (next.sidebar >= next.toc) { + next.sidebar = Math.max(LIMITS.sidebar.min, next.sidebar - overflow); + } else { + next.toc = Math.max(LIMITS.toc.min, next.toc - overflow); + } } + + return { + sidebar: Number(next.sidebar.toFixed(4)), + toc: Number(next.toc.toFixed(4)), + }; } - resetPanelWidths() { - this.applyWidths(DEFAULT_WIDTHS); - this.savePanelWidths(); + clamp(value, min, max, fallback) { + if (!Number.isFinite(value)) { + return fallback; + } + + return Math.min(max, Math.max(min, value)); } } - // Initialize when DOM is ready - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', () => { - new ResizablePanels(); + function initResizablePanels() { + document.querySelectorAll('.row.flex-xl-nowrap').forEach((row) => { + if (!row.dataset.resizablePanelsInitialized) { + row.dataset.resizablePanelsInitialized = 'true'; + new ResizablePanels(row); + } }); + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initResizablePanels); } else { - new ResizablePanels(); + initResizablePanels(); } })(); diff --git a/assets/scss/_resizable-panels.scss b/assets/scss/_resizable-panels.scss index 35dcdc829d4..b4fcca95820 100644 --- a/assets/scss/_resizable-panels.scss +++ b/assets/scss/_resizable-panels.scss @@ -1,138 +1,142 @@ /** - * Resizable Panels Styling - * Styles for the resize handles and drag interactions + * Resizable docs side panels. + * + * Desktop widths are driven by CSS variables set from resizable-panels.js. + * Smaller breakpoints keep the existing Bootstrap column behavior. */ -// Resize handle styling -.resizable-panel-handle { - position: absolute; - background-color: transparent; - transition: background-color 0.2s ease; - z-index: 100; +.resizable-panel-handle, +.resizable-panel-reset { + display: none; +} - &--right { - // Right edge handle (between sidebar and main) - right: -3px; - top: 0; - bottom: 0; - width: 6px; - cursor: col-resize; +@media (min-width: 1200px) { + .row.flex-xl-nowrap.resizable-panels-ready { + --docs-sidebar-width: 16.6667%; + --docs-main-width: 66.6666%; + --docs-toc-width: 16.6667%; - &:hover, - &--active { - background-color: rgba(0, 211, 169, 0.3); + display: flex; + flex-wrap: nowrap; + } - &::after { - opacity: 1; - } - } + .row.flex-xl-nowrap.resizable-panels-ready > .td-sidebar { + flex: 0 0 var(--docs-sidebar-width); + max-width: var(--docs-sidebar-width); + order: 1; + } + + .row.flex-xl-nowrap.resizable-panels-ready > main[role='main'] { + flex: 0 0 var(--docs-main-width); + max-width: var(--docs-main-width); + min-width: 0; + order: 2; + } + + .row.flex-xl-nowrap.resizable-panels-ready > .td-sidebar-toc { + flex: 0 0 var(--docs-toc-width); + max-width: var(--docs-toc-width); + order: 3; + } + + .td-sidebar, + .td-sidebar-toc { + position: sticky; + } + + .resizable-panel-handle { + appearance: none; + background: transparent; + border: 0; + bottom: 0; + cursor: col-resize; + display: block; + padding: 0; + position: absolute; + top: 0; + width: 12px; + z-index: 20; &::after { + background-color: #00d3a9; + border-radius: 1px; content: ''; + height: 44px; + opacity: 0; position: absolute; - right: 0; top: 50%; transform: translateY(-50%); + transition: opacity 0.16s ease; width: 2px; - height: 40px; - background-color: #00d3a9; - opacity: 0; - transition: opacity 0.2s ease; - border-radius: 1px; } - } - - &--left { - // Left edge handle (between main and TOC) - left: -3px; - top: 0; - bottom: 0; - width: 6px; - cursor: col-resize; &:hover, + &:focus-visible, &--active { - background-color: rgba(0, 211, 169, 0.3); + background-color: rgba(0, 211, 169, 0.16); + outline: none; &::after { opacity: 1; } } - &::after { - content: ''; - position: absolute; - left: 0; - top: 50%; - transform: translateY(-50%); - width: 2px; - height: 40px; - background-color: #00d3a9; - opacity: 0; - transition: opacity 0.2s ease; - border-radius: 1px; - } - } + &--sidebar { + right: -6px; - &--active { - background-color: rgba(0, 211, 169, 0.5); - } -} + &::after { + right: 5px; + } + } -// Reset button styling -#reset-panel-widths { - white-space: nowrap; - font-size: 0.875rem; + &--toc { + left: -6px; - i { - margin-right: 0.25rem; - } - - &:hover { - background-color: #00d3a9; - color: #fff; + &::after { + left: 5px; + } + } } -} - -// Make panels relatively positioned for absolute handle positioning -.td-sidebar, -.td-sidebar-toc { - position: relative; -} - -// Smooth transitions for width changes -.td-sidebar, -.td-sidebar-toc, -main[role="main"] { - transition: width 0.3s ease; -} -// Ensure row is flex container for proper layout -.row.flex-xl-nowrap { - display: flex; - flex-wrap: nowrap; + .resizable-panel-reset { + align-items: center; + background: rgba(0, 0, 0, 0.25); + border: 1px solid rgba(0, 211, 169, 0.45); + border-radius: 4px; + color: #ccc; + cursor: pointer; + display: inline-flex; + font-size: 0.8125rem; + gap: 0.35rem; + line-height: 1.2; + margin: 1rem 0 0; + padding: 0.35rem 0.55rem; + white-space: nowrap; - > aside, - > main { - overflow-x: hidden; + &:hover, + &:focus-visible { + background: #00d3a9; + border-color: #00d3a9; + color: #000; + outline: none; + } } -} -// Mobile/Tablet adjustments - disable resize handles on smaller screens -@media (max-width: 1199.98px) { - .resizable-panel-handle { - display: none; - } + .resizable-panels-dragging { + cursor: col-resize; + user-select: none; - #reset-panel-widths { - display: none; + iframe, + img, + a { + pointer-events: none; + } } } -// Print styles - hide resize handles @media print { .resizable-panel-handle, - #reset-panel-widths { + .resizable-panel-reset { display: none !important; } } From ed5d256c9a67e52dc37cf43e83c6598b98c9d271 Mon Sep 17 00:00:00 2001 From: Rudra2637 Date: Thu, 21 May 2026 20:01:41 +0530 Subject: [PATCH 08/14] Fix resizable panel handle loading Signed-off-by: Rudra2637 --- assets/scss/_resizable-panels.scss | 16 ++++++++-------- layouts/partials/scripts.html | 6 +++++- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/assets/scss/_resizable-panels.scss b/assets/scss/_resizable-panels.scss index b4fcca95820..06e7ac03995 100644 --- a/assets/scss/_resizable-panels.scss +++ b/assets/scss/_resizable-panels.scss @@ -46,7 +46,7 @@ .resizable-panel-handle { appearance: none; - background: transparent; + background: rgba(0, 211, 169, 0.12); border: 0; bottom: 0; cursor: col-resize; @@ -54,15 +54,15 @@ padding: 0; position: absolute; top: 0; - width: 12px; - z-index: 20; + width: 14px; + z-index: 50; &::after { background-color: #00d3a9; border-radius: 1px; content: ''; height: 44px; - opacity: 0; + opacity: 0.75; position: absolute; top: 50%; transform: translateY(-50%); @@ -82,18 +82,18 @@ } &--sidebar { - right: -6px; + right: 0; &::after { - right: 5px; + right: 6px; } } &--toc { - left: -6px; + left: 0; &::after { - left: 5px; + left: 6px; } } } diff --git a/layouts/partials/scripts.html b/layouts/partials/scripts.html index 409b2634f53..7d764480d74 100644 --- a/layouts/partials/scripts.html +++ b/layouts/partials/scripts.html @@ -1,5 +1,9 @@ - +{{ $resizablePanels := resources.Get "js/resizable-panels.js" -}} +{{ if $resizablePanels }} + +{{ end -}} + {{ $kanvasTransition := resources.Get "js/kanvas-architectural-transition.js" -}} {{ if $kanvasTransition }} From c260d3642bb40156da12c357e406c044587c974e Mon Sep 17 00:00:00 2001 From: Rudra2637 Date: Thu, 21 May 2026 21:06:16 +0530 Subject: [PATCH 09/14] Fix sidebar resize handle visibility Signed-off-by: Rudra2637 --- assets/js/resizable-panels.js | 18 +++++++++++---- assets/scss/_resizable-panels.scss | 35 ++++++++++++++++++++---------- 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/assets/js/resizable-panels.js b/assets/js/resizable-panels.js index c8c034d6268..c35f71463ba 100644 --- a/assets/js/resizable-panels.js +++ b/assets/js/resizable-panels.js @@ -8,7 +8,7 @@ 'use strict'; const STORAGE_KEY = 'layer5-docs-panel-widths'; - const DESKTOP_QUERY = '(min-width: 1200px)'; + const RESIZABLE_QUERY = '(min-width: 768px)'; const STEP = 1; const DEFAULT_WIDTHS = { sidebar: 16.6667, @@ -26,7 +26,7 @@ this.sidebar = row.querySelector('.td-sidebar'); this.main = row.querySelector('main[role="main"]'); this.toc = row.querySelector('.td-sidebar-toc'); - this.mediaQuery = window.matchMedia(DESKTOP_QUERY); + this.mediaQuery = window.matchMedia(RESIZABLE_QUERY); this.activeHandle = null; this.startX = 0; this.startWidths = null; @@ -54,11 +54,17 @@ document.addEventListener('pointerup', () => this.stopResize()); document.addEventListener('pointercancel', () => this.stopResize()); - this.mediaQuery.addEventListener('change', () => { + const onBreakpointChange = () => { if (this.mediaQuery.matches) { this.applyWidths(this.widths); } - }); + }; + + if (this.mediaQuery.addEventListener) { + this.mediaQuery.addEventListener('change', onBreakpointChange); + } else { + this.mediaQuery.addListener(onBreakpointChange); + } } createHandles() { @@ -198,6 +204,10 @@ ); this.row.style.setProperty('--docs-toc-width', `${normalized.toc}%`); this.row.style.setProperty('--docs-main-width', `${mainWidth}%`); + this.row.style.setProperty( + '--docs-main-without-toc-width', + `${100 - normalized.sidebar}%`, + ); this.updateHandleValues(normalized); } diff --git a/assets/scss/_resizable-panels.scss b/assets/scss/_resizable-panels.scss index 06e7ac03995..5035493b12b 100644 --- a/assets/scss/_resizable-panels.scss +++ b/assets/scss/_resizable-panels.scss @@ -1,8 +1,8 @@ /** * Resizable docs side panels. * - * Desktop widths are driven by CSS variables set from resizable-panels.js. - * Smaller breakpoints keep the existing Bootstrap column behavior. + * Docs widths are driven by CSS variables set from resizable-panels.js. + * Mobile keeps the existing Bootstrap column behavior. */ .resizable-panel-handle, @@ -10,10 +10,11 @@ display: none; } -@media (min-width: 1200px) { +@media (min-width: 768px) { .row.flex-xl-nowrap.resizable-panels-ready { --docs-sidebar-width: 16.6667%; --docs-main-width: 66.6666%; + --docs-main-without-toc-width: 83.3333%; --docs-toc-width: 16.6667%; display: flex; @@ -27,18 +28,12 @@ } .row.flex-xl-nowrap.resizable-panels-ready > main[role='main'] { - flex: 0 0 var(--docs-main-width); - max-width: var(--docs-main-width); + flex: 0 0 var(--docs-main-without-toc-width); + max-width: var(--docs-main-without-toc-width); min-width: 0; order: 2; } - .row.flex-xl-nowrap.resizable-panels-ready > .td-sidebar-toc { - flex: 0 0 var(--docs-toc-width); - max-width: var(--docs-toc-width); - order: 3; - } - .td-sidebar, .td-sidebar-toc { position: sticky; @@ -90,6 +85,7 @@ } &--toc { + display: none; left: 0; &::after { @@ -134,6 +130,23 @@ } } +@media (min-width: 1200px) { + .row.flex-xl-nowrap.resizable-panels-ready > main[role='main'] { + flex-basis: var(--docs-main-width); + max-width: var(--docs-main-width); + } + + .row.flex-xl-nowrap.resizable-panels-ready > .td-sidebar-toc { + flex: 0 0 var(--docs-toc-width); + max-width: var(--docs-toc-width); + order: 3; + } + + .resizable-panel-handle--toc { + display: block; + } +} + @media print { .resizable-panel-handle, .resizable-panel-reset { From 5fd3cc81da5620b595b53227b8aba2a375ab9d4e Mon Sep 17 00:00:00 2001 From: Rudra2637 Date: Thu, 21 May 2026 22:46:43 +0530 Subject: [PATCH 10/14] Fixed Ui Signed-off-by: Rudra2637 --- assets/scss/_resizable-panels.scss | 39 +++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/assets/scss/_resizable-panels.scss b/assets/scss/_resizable-panels.scss index 5035493b12b..7d58fb23047 100644 --- a/assets/scss/_resizable-panels.scss +++ b/assets/scss/_resizable-panels.scss @@ -41,7 +41,7 @@ .resizable-panel-handle { appearance: none; - background: rgba(0, 211, 169, 0.12); + background: transparent; border: 0; bottom: 0; cursor: col-resize; @@ -49,28 +49,41 @@ padding: 0; position: absolute; top: 0; - width: 14px; + width: 12px; z-index: 50; + &::before { + background-color: rgba(0, 211, 169, 0.22); + bottom: 0; + content: ''; + opacity: 0; + position: absolute; + top: 0; + transition: opacity 0.16s ease; + width: 1px; + } + &::after { background-color: #00d3a9; - border-radius: 1px; + border-radius: 999px; + box-shadow: 0 0 0 3px rgba(0, 211, 169, 0.12); content: ''; - height: 44px; - opacity: 0.75; + height: 56px; + opacity: 0.45; position: absolute; top: 50%; transform: translateY(-50%); transition: opacity 0.16s ease; - width: 2px; + width: 4px; } &:hover, &:focus-visible, &--active { - background-color: rgba(0, 211, 169, 0.16); + background: rgba(0, 211, 169, 0.06); outline: none; + &::before, &::after { opacity: 1; } @@ -79,8 +92,12 @@ &--sidebar { right: 0; + &::before { + right: 0; + } + &::after { - right: 6px; + right: 4px; } } @@ -88,8 +105,12 @@ display: none; left: 0; + &::before { + left: 0; + } + &::after { - left: 6px; + left: 4px; } } } From 617019ff1827a53f46368731666a2605fe15cbdb Mon Sep 17 00:00:00 2001 From: Rudra2637 Date: Thu, 21 May 2026 22:59:01 +0530 Subject: [PATCH 11/14] Clarify sidebar resize handle UI Signed-off-by: Rudra2637 --- assets/scss/_resizable-panels.scss | 68 ++++++++++++------------------ 1 file changed, 28 insertions(+), 40 deletions(-) diff --git a/assets/scss/_resizable-panels.scss b/assets/scss/_resizable-panels.scss index 7d58fb23047..3fc8834e3db 100644 --- a/assets/scss/_resizable-panels.scss +++ b/assets/scss/_resizable-panels.scss @@ -37,42 +37,36 @@ .td-sidebar, .td-sidebar-toc { position: sticky; + scrollbar-color: rgba(255, 255, 255, 0.28) transparent; } .resizable-panel-handle { appearance: none; - background: transparent; - border: 0; - bottom: 0; + background: rgba(4, 10, 10, 0.92); + border: 1px solid rgba(0, 211, 169, 0.55); + border-radius: 999px; + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.28); cursor: col-resize; display: block; + height: 64px; padding: 0; position: absolute; - top: 0; - width: 12px; + top: 50%; + transform: translateY(-50%); + width: 18px; z-index: 50; - &::before { - background-color: rgba(0, 211, 169, 0.22); - bottom: 0; - content: ''; - opacity: 0; - position: absolute; - top: 0; - transition: opacity 0.16s ease; - width: 1px; - } - &::after { - background-color: #00d3a9; + background-image: radial-gradient(circle, #00d3a9 1.5px, transparent 2px); + background-size: 4px 8px; border-radius: 999px; - box-shadow: 0 0 0 3px rgba(0, 211, 169, 0.12); content: ''; - height: 56px; - opacity: 0.45; + height: 30px; + left: 50%; + opacity: 0.8; position: absolute; top: 50%; - transform: translateY(-50%); + transform: translate(-50%, -50%); transition: opacity 0.16s ease; width: 4px; } @@ -80,39 +74,33 @@ &:hover, &:focus-visible, &--active { - background: rgba(0, 211, 169, 0.06); + background: rgba(0, 38, 34, 0.96); + border-color: #00d3a9; outline: none; - &::before, &::after { opacity: 1; } } &--sidebar { - right: 0; - - &::before { - right: 0; - } - - &::after { - right: 4px; - } + right: 10px; } &--toc { display: none; - left: 0; + left: 10px; + } + } - &::before { - left: 0; - } + .td-sidebar::-webkit-scrollbar-thumb, + .td-sidebar-toc::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.28); + } - &::after { - left: 4px; - } - } + .td-sidebar::-webkit-scrollbar-thumb:hover, + .td-sidebar-toc::-webkit-scrollbar-thumb:hover { + background: rgba(255, 255, 255, 0.42); } .resizable-panel-reset { From c2eb6d25ab694b23c81338d8fa96c0fa9ab81755 Mon Sep 17 00:00:00 2001 From: Rudra2637 Date: Thu, 21 May 2026 23:07:22 +0530 Subject: [PATCH 12/14] Clarify sidebar resize handle UI Signed-off-by: Rudra2637 --- assets/scss/_resizable-panels.scss | 196 +++++++++++++++++++---------- 1 file changed, 128 insertions(+), 68 deletions(-) diff --git a/assets/scss/_resizable-panels.scss b/assets/scss/_resizable-panels.scss index 3fc8834e3db..04f8c591a6c 100644 --- a/assets/scss/_resizable-panels.scss +++ b/assets/scss/_resizable-panels.scss @@ -19,90 +19,140 @@ display: flex; flex-wrap: nowrap; + width: 100%; } + /** + * LEFT SIDEBAR + */ .row.flex-xl-nowrap.resizable-panels-ready > .td-sidebar { flex: 0 0 var(--docs-sidebar-width); max-width: var(--docs-sidebar-width); + min-width: 220px; + position: relative; order: 1; } + /** + * MAIN CONTENT + */ .row.flex-xl-nowrap.resizable-panels-ready > main[role='main'] { - flex: 0 0 var(--docs-main-without-toc-width); + flex: 1 1 auto; max-width: var(--docs-main-without-toc-width); min-width: 0; + overflow-x: hidden; + position: relative; order: 2; } + /** + * TOC SIDEBAR + */ + .row.flex-xl-nowrap.resizable-panels-ready > .td-sidebar-toc { + position: relative; + } + + /** + * EXISTING SIDEBAR STYLING + */ .td-sidebar, .td-sidebar-toc { position: sticky; - scrollbar-color: rgba(255, 255, 255, 0.28) transparent; + scrollbar-color: rgba(255, 255, 255, 0.3) transparent; } + /** + * RESIZE HANDLE + */ .resizable-panel-handle { appearance: none; - background: rgba(4, 10, 10, 0.92); - border: 1px solid rgba(0, 211, 169, 0.55); - border-radius: 999px; - box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.28); + background: transparent; + border: 0; cursor: col-resize; display: block; - height: 64px; padding: 0; position: absolute; + top: 0; + bottom: 0; + width: 4px; + z-index: 20; + } + + /** + * HANDLE VISUAL INDICATOR + */ + .resizable-panel-handle::after { + content: ''; + position: absolute; top: 50%; transform: translateY(-50%); - width: 18px; - z-index: 50; - - &::after { - background-image: radial-gradient(circle, #00d3a9 1.5px, transparent 2px); - background-size: 4px 8px; - border-radius: 999px; - content: ''; - height: 30px; - left: 50%; - opacity: 0.8; - position: absolute; - top: 50%; - transform: translate(-50%, -50%); - transition: opacity 0.16s ease; - width: 4px; - } - - &:hover, - &:focus-visible, - &--active { - background: rgba(0, 38, 34, 0.96); - border-color: #00d3a9; - outline: none; - - &::after { - opacity: 1; - } - } - - &--sidebar { - right: 10px; - } - - &--toc { - display: none; - left: 10px; - } - } - - .td-sidebar::-webkit-scrollbar-thumb, - .td-sidebar-toc::-webkit-scrollbar-thumb { - background: rgba(255, 255, 255, 0.28); - } - - .td-sidebar::-webkit-scrollbar-thumb:hover, - .td-sidebar-toc::-webkit-scrollbar-thumb:hover { - background: rgba(255, 255, 255, 0.42); + width: 2px; + height: 48px; + border-radius: 999px; + background-color: rgba(0, 211, 169, 0.9); + opacity: 0; + transition: opacity 0.16s ease; + } + + /** + * SHOW HANDLE ON INTERACTION + */ + .resizable-panel-handle:hover::after, + .resizable-panel-handle:focus-visible::after, + .resizable-panel-handle--active::after { + opacity: 1; + } + + /** + * LEFT SIDEBAR HANDLE + */ + .resizable-panel-handle--sidebar { + right: 0; + } + + .resizable-panel-handle--sidebar::after { + right: 1px; + } + + /** + * TOC HANDLE + */ + .resizable-panel-handle--toc { + display: none; + left: 0; + } + + .resizable-panel-handle--toc::after { + left: 1px; + } + + /** + * CUSTOM SCROLLBARS + */ + .row.flex-xl-nowrap.resizable-panels-ready + > .td-sidebar::-webkit-scrollbar, + .row.flex-xl-nowrap.resizable-panels-ready + > .td-sidebar-toc::-webkit-scrollbar { + width: 8px; + } + + .row.flex-xl-nowrap.resizable-panels-ready + > .td-sidebar::-webkit-scrollbar-thumb, + .row.flex-xl-nowrap.resizable-panels-ready + > .td-sidebar-toc::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.3) !important; + } + + .row.flex-xl-nowrap.resizable-panels-ready + > .td-sidebar::-webkit-scrollbar-thumb:hover, + .row.flex-xl-nowrap.resizable-panels-ready + > .td-sidebar-toc::-webkit-scrollbar-thumb:hover { + background: rgba(255, 255, 255, 0.45) !important; } + /** + * RESET BUTTON + */ .resizable-panel-reset { align-items: center; background: rgba(0, 0, 0, 0.25); @@ -117,28 +167,34 @@ margin: 1rem 0 0; padding: 0.35rem 0.55rem; white-space: nowrap; + } - &:hover, - &:focus-visible { - background: #00d3a9; - border-color: #00d3a9; - color: #000; - outline: none; - } + .resizable-panel-reset:hover, + .resizable-panel-reset:focus-visible { + background: #00d3a9; + border-color: #00d3a9; + color: #000; + outline: none; } + /** + * DRAGGING STATE + */ .resizable-panels-dragging { cursor: col-resize; user-select: none; + } - iframe, - img, - a { - pointer-events: none; - } + .resizable-panels-dragging iframe, + .resizable-panels-dragging img, + .resizable-panels-dragging a { + pointer-events: none; } } +/** + * LARGE SCREENS + */ @media (min-width: 1200px) { .row.flex-xl-nowrap.resizable-panels-ready > main[role='main'] { flex-basis: var(--docs-main-width); @@ -148,6 +204,7 @@ .row.flex-xl-nowrap.resizable-panels-ready > .td-sidebar-toc { flex: 0 0 var(--docs-toc-width); max-width: var(--docs-toc-width); + min-width: 220px; order: 3; } @@ -156,9 +213,12 @@ } } +/** + * PRINT MODE + */ @media print { .resizable-panel-handle, .resizable-panel-reset { display: none !important; } -} +} \ No newline at end of file From 8ca58aecd43d7c6113ba1d0b659387aa45765ca6 Mon Sep 17 00:00:00 2001 From: Rudra2637 Date: Thu, 21 May 2026 23:17:30 +0530 Subject: [PATCH 13/14] fix(docs): refine resizable panel layout and prevent content clipping Signed-off-by: Rudra2637 --- assets/scss/_resizable-panels.scss | 69 +++++++++++++++++++++++++----- 1 file changed, 58 insertions(+), 11 deletions(-) diff --git a/assets/scss/_resizable-panels.scss b/assets/scss/_resizable-panels.scss index 04f8c591a6c..9c8044cf161 100644 --- a/assets/scss/_resizable-panels.scss +++ b/assets/scss/_resizable-panels.scss @@ -20,6 +20,7 @@ display: flex; flex-wrap: nowrap; width: 100%; + min-width: 0; } /** @@ -27,9 +28,12 @@ */ .row.flex-xl-nowrap.resizable-panels-ready > .td-sidebar { flex: 0 0 var(--docs-sidebar-width); - max-width: var(--docs-sidebar-width); - min-width: 220px; + width: var(--docs-sidebar-width); + max-width: 420px; + min-width: 240px; position: relative; + overflow-x: hidden; + padding-right: 12px; order: 1; } @@ -38,18 +42,32 @@ */ .row.flex-xl-nowrap.resizable-panels-ready > main[role='main'] { flex: 1 1 auto; + flex-basis: var(--docs-main-without-toc-width); max-width: var(--docs-main-without-toc-width); - min-width: 0; - overflow-x: hidden; + min-width: 500px; + overflow-x: auto; position: relative; order: 2; } + /** + * Better readable content width + */ + main[role='main'] article { + max-width: 900px; + } + /** * TOC SIDEBAR */ .row.flex-xl-nowrap.resizable-panels-ready > .td-sidebar-toc { + width: var(--docs-toc-width); + max-width: 320px; + min-width: 220px; position: relative; + overflow-x: hidden; + padding-left: 12px; + order: 3; } /** @@ -61,6 +79,21 @@ scrollbar-color: rgba(255, 255, 255, 0.3) transparent; } + /** + * Prevent text clipping + */ + .td-sidebar nav, + .td-sidebar-toc nav { + overflow-wrap: break-word; + word-break: break-word; + } + + .td-sidebar a, + .td-sidebar-toc a { + display: block; + max-width: 100%; + } + /** * RESIZE HANDLE */ @@ -79,7 +112,19 @@ } /** - * HANDLE VISUAL INDICATOR + * Subtle divider line + */ + .resizable-panel-handle::before { + content: ''; + position: absolute; + top: 0; + bottom: 0; + width: 1px; + background: rgba(255, 255, 255, 0.08); + } + + /** + * Handle visual indicator */ .resizable-panel-handle::after { content: ''; @@ -87,9 +132,9 @@ top: 50%; transform: translateY(-50%); width: 2px; - height: 48px; + height: 32px; border-radius: 999px; - background-color: rgba(0, 211, 169, 0.9); + background-color: rgba(0, 211, 169, 0.55); opacity: 0; transition: opacity 0.16s ease; } @@ -110,6 +155,7 @@ right: 0; } + .resizable-panel-handle--sidebar::before, .resizable-panel-handle--sidebar::after { right: 1px; } @@ -122,6 +168,7 @@ left: 0; } + .resizable-panel-handle--toc::before, .resizable-panel-handle--toc::after { left: 1px; } @@ -141,6 +188,7 @@ .row.flex-xl-nowrap.resizable-panels-ready > .td-sidebar-toc::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.3) !important; + border-radius: 999px; } .row.flex-xl-nowrap.resizable-panels-ready @@ -155,6 +203,8 @@ */ .resizable-panel-reset { align-items: center; + justify-content: center; + width: 100%; background: rgba(0, 0, 0, 0.25); border: 1px solid rgba(0, 211, 169, 0.45); border-radius: 4px; @@ -165,7 +215,7 @@ gap: 0.35rem; line-height: 1.2; margin: 1rem 0 0; - padding: 0.35rem 0.55rem; + padding: 0.45rem 0.65rem; white-space: nowrap; } @@ -203,9 +253,6 @@ .row.flex-xl-nowrap.resizable-panels-ready > .td-sidebar-toc { flex: 0 0 var(--docs-toc-width); - max-width: var(--docs-toc-width); - min-width: 220px; - order: 3; } .resizable-panel-handle--toc { From 2a31d5873b5f7d10d07339aa65f2878714c40871 Mon Sep 17 00:00:00 2001 From: Rudra2637 Date: Wed, 27 May 2026 00:35:57 +0530 Subject: [PATCH 14/14] Address resizable panels review feedback Signed-off-by: Rudra2637 --- assets/js/resizable-panels.js | 253 +++++++++++++++++----------------- layouts/partials/scripts.html | 27 ++-- 2 files changed, 139 insertions(+), 141 deletions(-) diff --git a/assets/js/resizable-panels.js b/assets/js/resizable-panels.js index c35f71463ba..415294ffb72 100644 --- a/assets/js/resizable-panels.js +++ b/assets/js/resizable-panels.js @@ -19,68 +19,59 @@ toc: { min: 10, max: 28 }, main: { min: 42 }, }; - - class ResizablePanels { - constructor(row) { - this.row = row; - this.sidebar = row.querySelector('.td-sidebar'); - this.main = row.querySelector('main[role="main"]'); - this.toc = row.querySelector('.td-sidebar-toc'); - this.mediaQuery = window.matchMedia(RESIZABLE_QUERY); - this.activeHandle = null; - this.startX = 0; - this.startWidths = null; - this.widths = this.getStoredWidths(); - - if (!this.sidebar || !this.main) { - return; - } - - this.init(); + const LEGACY_GRID_COLUMNS = 12; + + function setupResizablePanels(row) { + const sidebar = row.querySelector('.td-sidebar'); + const main = row.querySelector('main[role="main"]'); + const toc = row.querySelector('.td-sidebar-toc'); + const mediaQuery = window.matchMedia(RESIZABLE_QUERY); + let activeHandle = null; + let sidebarHandle = null; + let tocHandle = null; + let startX = 0; + let startWidths = null; + let widths = getStoredWidths(); + + if (!sidebar || !main) { + return; } - init() { - this.row.classList.add('resizable-panels-ready'); - this.applyWidths(this.widths); - this.createHandles(); - this.createResetButton(); - this.bindEvents(); - } + row.classList.add('resizable-panels-ready'); + applyWidths(widths); + createHandles(); + createResetButton(); + bindEvents(); - bindEvents() { - document.addEventListener('pointermove', (event) => - this.onPointerMove(event), - ); - document.addEventListener('pointerup', () => this.stopResize()); - document.addEventListener('pointercancel', () => this.stopResize()); + function bindEvents() { + document.addEventListener('pointermove', onPointerMove); + document.addEventListener('pointerup', stopResize); + document.addEventListener('pointercancel', stopResize); const onBreakpointChange = () => { - if (this.mediaQuery.matches) { - this.applyWidths(this.widths); + if (mediaQuery.matches) { + applyWidths(widths); } }; - if (this.mediaQuery.addEventListener) { - this.mediaQuery.addEventListener('change', onBreakpointChange); + if (mediaQuery.addEventListener) { + mediaQuery.addEventListener('change', onBreakpointChange); } else { - this.mediaQuery.addListener(onBreakpointChange); + mediaQuery.addListener(onBreakpointChange); } } - createHandles() { - this.sidebarHandle = this.createHandle( - 'sidebar', - 'Resize navigation sidebar', - ); - this.sidebar.appendChild(this.sidebarHandle); + function createHandles() { + sidebarHandle = createHandle('sidebar', 'Resize navigation sidebar'); + sidebar.appendChild(sidebarHandle); - if (this.toc) { - this.tocHandle = this.createHandle('toc', 'Resize table of contents'); - this.toc.appendChild(this.tocHandle); + if (toc) { + tocHandle = createHandle('toc', 'Resize table of contents'); + toc.appendChild(tocHandle); } } - createHandle(target, label) { + function createHandle(target, label) { const handle = document.createElement('div'); handle.className = `resizable-panel-handle resizable-panel-handle--${target}`; handle.dataset.resizeTarget = target; @@ -90,17 +81,17 @@ handle.setAttribute('role', 'separator'); handle.title = label; - handle.addEventListener('pointerdown', (event) => - this.startResize(event, handle), - ); - handle.addEventListener('keydown', (event) => - this.onHandleKeydown(event, target), - ); + handle.addEventListener('pointerdown', (event) => { + startResize(event, handle); + }); + handle.addEventListener('keydown', (event) => { + onHandleKeydown(event, target); + }); return handle; } - createResetButton() { + function createResetButton() { const resetButton = document.createElement('button'); resetButton.type = 'button'; resetButton.id = 'reset-panel-widths'; @@ -108,65 +99,65 @@ resetButton.innerHTML = 'Reset layout'; resetButton.title = 'Reset panel widths to default'; - resetButton.addEventListener('click', () => this.reset()); + resetButton.addEventListener('click', reset); - this.sidebar.appendChild(resetButton); + sidebar.appendChild(resetButton); } - startResize(event, handle) { - if (!this.mediaQuery.matches) { + function startResize(event, handle) { + if (!mediaQuery.matches) { return; } event.preventDefault(); - this.activeHandle = handle; - this.startX = event.clientX; - this.startWidths = { ...this.widths }; + activeHandle = handle; + startX = event.clientX; + startWidths = { ...widths }; handle.classList.add('resizable-panel-handle--active'); handle.setPointerCapture(event.pointerId); document.body.classList.add('resizable-panels-dragging'); } - onPointerMove(event) { - if (!this.activeHandle || !this.startWidths) { + function onPointerMove(event) { + if (!activeHandle || !startWidths) { return; } - const rowWidth = this.row.getBoundingClientRect().width; + const rowWidth = row.getBoundingClientRect().width; if (!rowWidth) { return; } - const target = this.activeHandle.dataset.resizeTarget; - const delta = ((event.clientX - this.startX) / rowWidth) * 100; - const nextWidths = { ...this.startWidths }; + const target = activeHandle.dataset.resizeTarget; + const delta = ((event.clientX - startX) / rowWidth) * 100; + const nextWidths = { ...startWidths }; if (target === 'sidebar') { - nextWidths.sidebar = this.startWidths.sidebar + delta; + nextWidths.sidebar = startWidths.sidebar + delta; } if (target === 'toc') { - nextWidths.toc = this.startWidths.toc - delta; + nextWidths.toc = startWidths.toc - delta; } - this.widths = this.normalizeWidths(nextWidths); - this.applyWidths(this.widths); + widths = normalizeWidths(nextWidths); + applyWidths(widths); } - stopResize() { - if (!this.activeHandle) { + function stopResize() { + if (!activeHandle) { return; } - this.activeHandle.classList.remove('resizable-panel-handle--active'); - this.activeHandle = null; - this.startWidths = null; + activeHandle.classList.remove('resizable-panel-handle--active'); + activeHandle = null; + startWidths = null; document.body.classList.remove('resizable-panels-dragging'); - this.saveWidths(); + saveWidths(); } - onHandleKeydown(event, target) { - if (!this.mediaQuery.matches) { + function onHandleKeydown(event, target) { + if (!mediaQuery.matches) { return; } @@ -176,7 +167,7 @@ } event.preventDefault(); - const nextWidths = { ...this.widths }; + const nextWidths = { ...widths }; const direction = event.key === 'ArrowRight' ? 1 : -1; if (event.key === 'Home') { @@ -189,91 +180,105 @@ nextWidths.sidebar += direction * STEP; } - this.widths = this.normalizeWidths(nextWidths); - this.applyWidths(this.widths); - this.saveWidths(); + widths = normalizeWidths(nextWidths); + applyWidths(widths); + saveWidths(); } - applyWidths(widths) { - const normalized = this.normalizeWidths(widths); + function applyWidths(nextWidths) { + const normalized = normalizeWidths(nextWidths); const mainWidth = 100 - normalized.sidebar - normalized.toc; - this.row.style.setProperty( - '--docs-sidebar-width', - `${normalized.sidebar}%`, - ); - this.row.style.setProperty('--docs-toc-width', `${normalized.toc}%`); - this.row.style.setProperty('--docs-main-width', `${mainWidth}%`); - this.row.style.setProperty( + row.style.setProperty('--docs-sidebar-width', `${normalized.sidebar}%`); + row.style.setProperty('--docs-toc-width', `${normalized.toc}%`); + row.style.setProperty('--docs-main-width', `${mainWidth}%`); + row.style.setProperty( '--docs-main-without-toc-width', `${100 - normalized.sidebar}%`, ); - this.updateHandleValues(normalized); + updateHandleValues(normalized); } - updateHandleValues(widths) { - if (this.sidebarHandle) { - this.sidebarHandle.setAttribute('aria-valuemin', LIMITS.sidebar.min); - this.sidebarHandle.setAttribute('aria-valuemax', LIMITS.sidebar.max); - this.sidebarHandle.setAttribute( + function updateHandleValues(nextWidths) { + if (sidebarHandle) { + sidebarHandle.setAttribute('aria-valuemin', LIMITS.sidebar.min); + sidebarHandle.setAttribute('aria-valuemax', LIMITS.sidebar.max); + sidebarHandle.setAttribute( 'aria-valuenow', - Math.round(widths.sidebar), + Math.round(nextWidths.sidebar), ); } - if (this.tocHandle) { - this.tocHandle.setAttribute('aria-valuemin', LIMITS.toc.min); - this.tocHandle.setAttribute('aria-valuemax', LIMITS.toc.max); - this.tocHandle.setAttribute('aria-valuenow', Math.round(widths.toc)); + if (tocHandle) { + tocHandle.setAttribute('aria-valuemin', LIMITS.toc.min); + tocHandle.setAttribute('aria-valuemax', LIMITS.toc.max); + tocHandle.setAttribute('aria-valuenow', Math.round(nextWidths.toc)); } } - reset() { - this.widths = { ...DEFAULT_WIDTHS }; - this.applyWidths(this.widths); + function reset() { + widths = { ...DEFAULT_WIDTHS }; + applyWidths(widths); localStorage.removeItem(STORAGE_KEY); } - getStoredWidths() { + function getStoredWidths() { try { const saved = JSON.parse(localStorage.getItem(STORAGE_KEY)); - if ( - saved && - (saved.sidebar <= 12 || saved.toc <= 12 || saved.main <= 12) - ) { - return this.normalizeWidths({ + + if (isLegacyColumnWidths(saved)) { + return normalizeWidths({ sidebar: saved.sidebar - ? (saved.sidebar / 12) * 100 + ? (saved.sidebar / LEGACY_GRID_COLUMNS) * 100 : DEFAULT_WIDTHS.sidebar, - toc: saved.toc ? (saved.toc / 12) * 100 : DEFAULT_WIDTHS.toc, + toc: saved.toc + ? (saved.toc / LEGACY_GRID_COLUMNS) * 100 + : DEFAULT_WIDTHS.toc, }); } - return this.normalizeWidths(saved || DEFAULT_WIDTHS); + return normalizeWidths(saved || DEFAULT_WIDTHS); } catch (error) { return { ...DEFAULT_WIDTHS }; } } - saveWidths() { + function isLegacyColumnWidths(saved) { + if (!saved) { + return false; + } + + return ['sidebar', 'toc', 'main'].some((key) => { + const value = Number(saved[key]); + const limit = LIMITS[key]; + + if (!Number.isFinite(value) || !limit) { + return false; + } + + return value < limit.min || (limit.max && value > limit.max); + }); + } + + function saveWidths() { try { - localStorage.setItem(STORAGE_KEY, JSON.stringify(this.widths)); + localStorage.setItem(STORAGE_KEY, JSON.stringify(widths)); } catch (error) { // Ignore storage failures so resizing still works in private modes. } } - normalizeWidths(widths) { + function normalizeWidths(nextWidths) { const next = { - sidebar: this.clamp( - Number(widths && widths.sidebar), + sidebar: clamp( + Number(nextWidths && nextWidths.sidebar), LIMITS.sidebar.min, LIMITS.sidebar.max, DEFAULT_WIDTHS.sidebar, ), - toc: this.toc - ? this.clamp( - Number(widths && widths.toc), + toc: toc + ? clamp( + Number(nextWidths && nextWidths.toc), LIMITS.toc.min, LIMITS.toc.max, DEFAULT_WIDTHS.toc, @@ -300,7 +305,7 @@ }; } - clamp(value, min, max, fallback) { + function clamp(value, min, max, fallback) { if (!Number.isFinite(value)) { return fallback; } @@ -313,7 +318,7 @@ document.querySelectorAll('.row.flex-xl-nowrap').forEach((row) => { if (!row.dataset.resizablePanelsInitialized) { row.dataset.resizablePanelsInitialized = 'true'; - new ResizablePanels(row); + setupResizablePanels(row); } }); } diff --git a/layouts/partials/scripts.html b/layouts/partials/scripts.html index 7d764480d74..09d5183fe7f 100644 --- a/layouts/partials/scripts.html +++ b/layouts/partials/scripts.html @@ -1,20 +1,13 @@ -{{ $resizablePanels := resources.Get "js/resizable-panels.js" -}} -{{ if $resizablePanels }} - -{{ end -}} - -{{ $kanvasTransition := resources.Get "js/kanvas-architectural-transition.js" -}} -{{ if $kanvasTransition }} - -{{ end -}} - -{{ $kanvasPopup := resources.Get "js/kanvas-corner-popup.js" -}} -{{ if $kanvasPopup }} - -{{ end -}} +{{ $scripts := slice + "js/resizable-panels.js" + "js/kanvas-architectural-transition.js" + "js/kanvas-corner-popup.js" + "js/offline-search.js" +-}} -{{ $offlineSearch := resources.Get "js/offline-search.js" -}} -{{ if $offlineSearch }} - +{{ range $scripts -}} + {{ with resources.Get . -}} + + {{ end -}} {{ end -}}