[feat]: Integration of layout-agnostic resizable partials#1128
[feat]: Integration of layout-agnostic resizable partials#1128banana-three-join wants to merge 1 commit into
Conversation
…of previous resizable and fix of minor bugs Signed-off-by: Lenox Wiltshire <lenoxwiltshire@gmail.com>
There was a problem hiding this comment.
Code Review
This pull request refactors the sidebar and table of contents (TOC) resizing functionality into a reusable, layout-agnostic resizable component, replacing the old grid-based resizer logic across various layouts and introducing a new ecosystem-box shortcode to clean up inline styles in content files. The review feedback suggests improving the robustness of the resizable JavaScript initialization by avoiding hardcoded fallback values, wrapping localStorage access in try-catch blocks, optimizing early returns, and handling pointercancel events. Additionally, it is recommended to consolidate CSS by removing inline styles from the ecosystem-box shortcode and defining proper classes in the SCSS files.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| function init(el) { | ||
| if (el.__resizableReady) return; | ||
| el.__resizableReady = true; | ||
|
|
||
| var side = el.dataset.resizableSide || "right"; | ||
| var key = "resizable:" + el.dataset.resizableKey; | ||
| var min = parseFloat(el.dataset.resizableMin) || 0; | ||
| var max = parseFloat(el.dataset.resizableMax) || Infinity; | ||
| var def = parseFloat(el.dataset.resizableDefault) || min || 280; | ||
| var handle = el.querySelector(":scope > .resizable__handle"); | ||
| if (!handle || !el.dataset.resizableKey) return; | ||
|
|
||
| function setWidth(px) { | ||
| el.style.setProperty("--resizable-w", clamp(px, min, max) + "px"); | ||
| } | ||
| function save() { | ||
| localStorage.setItem(key, parseFloat(getComputedStyle(el).width)); | ||
| } | ||
|
|
||
| var saved = parseFloat(localStorage.getItem(key)); | ||
| setWidth(isNaN(saved) ? def : saved); | ||
|
|
||
| var startX = 0, startW = 0; | ||
|
|
||
| function onMove(e) { | ||
| var delta = side === "right" ? e.clientX - startX : startX - e.clientX; | ||
| setWidth(startW + delta); | ||
| } | ||
| function onUp(e) { | ||
| handle.releasePointerCapture(e.pointerId); | ||
| window.removeEventListener("pointermove", onMove); | ||
| window.removeEventListener("pointerup", onUp); | ||
| el.classList.remove("is-resizing"); | ||
| document.body.classList.remove("is-resizing"); | ||
| save(); | ||
| } | ||
|
|
||
| handle.addEventListener("pointerdown", function (e) { | ||
| e.preventDefault(); | ||
| startX = e.clientX; | ||
| startW = el.getBoundingClientRect().width; | ||
| handle.setPointerCapture(e.pointerId); | ||
| el.classList.add("is-resizing"); | ||
| document.body.classList.add("is-resizing"); | ||
| window.addEventListener("pointermove", onMove); | ||
| window.addEventListener("pointerup", onUp); | ||
| }); |
There was a problem hiding this comment.
Refactor Resizable Initialization for Robustness and Adherence to Style Rules
This refactoring addresses several critical areas of improvement:
- Avoid Hardcoded Fallbacks: Resolves CSS custom properties (
--resizable-min,--resizable-max,--resizable-default) directly fromgetComputedStyle(el)instead of hardcoding fallback values in JavaScript, adhering to the general rules. - Safe
localStorageAccess: Wraps alllocalStoragereads and writes intry-catchblocks to prevent script crashes in environments where local storage is blocked or unavailable (e.g., private browsing, iframe previews). - Early Return Optimization: Validates
el.dataset.resizableKeyandhandlebefore settingel.__resizableReady = trueto avoid marking invalid elements as ready. - Robust Gesture Handling: Adds a listener for
pointercancelto ensure event listeners are cleaned up and resizing state is reset if the gesture is interrupted.
function init(el) {
var key = el.dataset.resizableKey;
var handle = el.querySelector(":scope > .resizable__handle");
if (!handle || !key) return;
if (el.__resizableReady) return;
el.__resizableReady = true;
var side = el.dataset.resizableSide || "right";
var storageKey = "resizable:" + key;
var style = getComputedStyle(el);
var min = parseFloat(style.getPropertyValue("--resizable-min"));
var max = parseFloat(style.getPropertyValue("--resizable-max"));
var def = parseFloat(style.getPropertyValue("--resizable-default"));
if (isNaN(min) || isNaN(max) || isNaN(def)) return;
function setWidth(px) {
el.style.setProperty("--resizable-w", clamp(px, min, max) + "px");
}
function save() {
try {
localStorage.setItem(storageKey, parseFloat(getComputedStyle(el).width));
} catch (e) {
console.warn("localStorage is not available:", e);
}
}
var saved;
try {
saved = parseFloat(localStorage.getItem(storageKey));
} catch (e) {
saved = NaN;
}
setWidth(isNaN(saved) ? def : saved);
var startX = 0, startW = 0;
function onMove(e) {
var delta = side === "right" ? e.clientX - startX : startX - e.clientX;
setWidth(startW + delta);
}
function onUp(e) {
handle.releasePointerCapture(e.pointerId);
window.removeEventListener("pointermove", onMove);
window.removeEventListener("pointerup", onUp);
window.removeEventListener("pointercancel", onUp);
el.classList.remove("is-resizing");
document.body.classList.remove("is-resizing");
save();
}
handle.addEventListener("pointerdown", function (e) {
e.preventDefault();
startX = e.clientX;
startW = el.getBoundingClientRect().width;
handle.setPointerCapture(e.pointerId);
el.classList.add("is-resizing");
document.body.classList.add("is-resizing");
window.addEventListener("pointermove", onMove);
window.addEventListener("pointerup", onUp);
window.addEventListener("pointercancel", onUp);
});References
- Avoid hardcoding fallback values in JavaScript for CSS custom properties (variables), as this creates multiple sources of truth for UI styling. If the script cannot resolve the CSS variables, it is better to halt execution or handle the error rather than falling back to hardcoded defaults.
| <a href="{{ ref . $link }}" class="{{ $class }}" style="display:flex; gap:1.5rem; text-decoration:none; color:inherit;"> | ||
| <span style="min-width:50px; align-self:center; line-height:1.85rem;"> | ||
| {{- if $isImage -}} | ||
| <img src="{{ $icon | relURL }}" alt="{{ $title }} logo" style="height:65px; width:65px; border:0; background:transparent;"> | ||
| {{- else -}} | ||
| {{- with resources.Get (printf "icons/%s.svg" $icon) -}} | ||
| {{- .Content | safeHTML -}} | ||
| {{- else -}} | ||
| <!-- SVG not found: {{ $icon }} --> | ||
| {{- end -}} | ||
| {{- end -}} | ||
| </span> | ||
| <span style="margin:auto; color:#ccc; line-height:1.85rem;"> | ||
| <strong>{{ $title }}</strong> {{ .Inner | .Page.RenderString }} | ||
| </span> | ||
| </a> No newline at end of file |
There was a problem hiding this comment.
Consolidate CSS and Remove Inline Styles
To complete the CSS consolidation and separate structure from styling, remove the inline styles from the shortcode template and use the .ecosystem-box, .ecosystem-box__icon, and .ecosystem-box__text classes. This utilizes the classes already partially defined in the SCSS and keeps the HTML clean and maintainable.
<a href="{{ ref . $link }}" class="{{ $class }} ecosystem-box">
<span class="ecosystem-box__icon">
{{- if $isImage -}}
<img src="{{ $icon | relURL }}" alt="{{ $title }} logo">
{{- else -}}
{{- with resources.Get (printf "icons/%s.svg" $icon) -}}
{{- .Content | safeHTML -}}
{{- else -}}
<!-- SVG not found: {{ $icon }} -->
{{- end -}}
{{- end -}}
</span>
<span class="ecosystem-box__text">
<strong>{{ $title }}</strong> {{ .Inner | .Page.RenderString }}
</span>
</a>| border: 0; | ||
| background: transparent; | ||
| } | ||
| .hidden-highlight-box .ecosystem-box__text { padding-left: 1rem; } No newline at end of file |
There was a problem hiding this comment.
Add Ecosystem Box Layout and Typography Styles
Consolidate the styles removed from the shortcode by adding the .ecosystem-box classes to the SCSS file. This keeps the styling defined once and applied everywhere the shortcode renders.
.hidden-highlight-box .ecosystem-box__text { padding-left: 1rem; }
.ecosystem-box {
display: flex;
gap: 1.5rem;
text-decoration: none;
color: inherit;
&__icon {
min-width: 50px;
align-self: center;
line-height: 1.85rem;
}
&__text {
margin: auto;
color: #ccc;
line-height: 1.85rem;
}
}|
🚀 Preview deployment: https://docs.layer5.io/pr-preview/pr-1128/
|
Showcase
https://www.loom.com/share/43e69c7fc24945e5bb353d4650dd28ee
Changes
Resizable component (new, layout-agnostic — distributed via this parent module)
Removed the previous resizable sidebar implementation and replaced it with a layout-agnostic component distributed through this module. In Hugo, a theme can be consumed in different contexts, and the same resizable markup needs to behave correctly whether the host arranges its layout with Bootstrap's grid, CSS grid, or plain flex. To support that, this module serves as the parent module that downstream themes import the resizable items from: the component governs its own width through a CSS custom property (
--resizable-w) and makes no assumptions about the host's layout system, so importing themes get resize behavior without inheriting any layout coupling. Host-specific concerns (e.g. Bootstrap's.rowinteractions) live in a separate override file that stays with the consuming theme, keeping the distributed component portable. The component also offers local persistence — widths are stored per-origin inlocalStorageand restored before paint — so a reader's chosen sizing carries across pages and sessions in whichever theme imports it.Responsive behavior across widths
Verified and corrected the layout across its full range of viewport widths: at desktop the sidebar and TOC are resizable, sticky, and in-row; below the mobile breakpoint the row stacks (sidebar full-width on top, main below), resize is disabled, and the handle is hidden. The desktop width is preserved through
localStorageand restored when returning from a mobile width — the stored value is never overwritten by the mobile layout. Also removed a redundant sticky offset that was creating a gap between the header and the content.Shortcode for structured content
Integrating the resizable partials into
baseof.htmlsurfaced a latent content problem: a page's_index.mdcontained hand-written raw HTML with an unclosed tag, which caused the parser to swallow surrounding closing tags and reparent elements — breaking the surrounding layout. The restructure itself was minor; its value here is that it exposed why certain content needs to be handled differently from ordinary Markdown. Rather than patch the single page, this PR introduces a shortcode that emits balanced HTML by construction. Content authors pass parameters and text instead of hand-writing<div>structures, so a missing closing tag can no longer cascade into a layout break. This addresses the entire class of issue and is the durable fix. Converting the affected pages to the shortcode also makes clear which content blocks are structural (and therefore belong in a template that guarantees balance) versus ordinary prose — which leads directly to the styling cleanup below.CSS consolidation
With those structural blocks now owned by the shortcode, their styling no longer belongs in the content either. The affected pages had previously inlined identical callout-box styles as
<style>blocks inside two separate_index.mdfiles. As part of moving the structure into the shortcode, those styles are consolidated into shared SCSS — defined once, applied everywhere the shortcode renders. This removes the duplication, eliminates a per-page<style>block that was itself a parse-risk point, and completes the separation of concerns: structure lives in the shortcode template, styling lives in shared SCSS, and the Markdown content goes back to being just content.Notes for Reviewers
Signed commits