Skip to content

Commit c13369e

Browse files
committed
basic implementation of a collapsable sidebar with button and auto half-screen detection
1 parent 740ff1f commit c13369e

File tree

3 files changed

+290
-0
lines changed

3 files changed

+290
-0
lines changed

source/astatic/cyclus.css_t

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,82 @@ div.sphinxsidebar ul li a:hover {
128128
background: none !important;
129129
border-color: transparent !important;
130130
}
131+
132+
/* Sidebar toggle button */
133+
.sidebar-toggle-button {
134+
position: absolute;
135+
top: 10px;
136+
right: 10px;
137+
z-index: 1000;
138+
width: 36px;
139+
height: 36px;
140+
padding: 0;
141+
margin: 0;
142+
border: 2px solid #4b1a07;
143+
border-radius: 4px;
144+
background-color: #fcf1df;
145+
color: #4b1a07;
146+
font-size: 18px;
147+
font-weight: bold;
148+
cursor: pointer;
149+
display: flex;
150+
align-items: center;
151+
justify-content: center;
152+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
153+
transition: all 0.2s ease;
154+
}
155+
156+
.sidebar-toggle-button:hover {
157+
background-color: #4b1a07;
158+
color: #fcf1df;
159+
box-shadow: 0 2px 6px rgba(75, 26, 7, 0.4);
160+
}
161+
162+
.sidebar-toggle-button:active {
163+
transform: scale(0.95);
164+
}
165+
166+
.sidebar-toggle-button:focus {
167+
outline: 2px solid #bb3f3f;
168+
outline-offset: 2px;
169+
}
170+
171+
/* Button when collapsed */
172+
.sidebar-toggle-button.sidebar-toggle-collapsed {
173+
position: fixed !important;
174+
display: flex !important;
175+
visibility: visible !important;
176+
opacity: 1 !important;
177+
z-index: 1000 !important;
178+
transition: none !important; /* No animation when moving */
179+
}
180+
181+
/* Custom tooltip */
182+
.sidebar-toggle-button::after {
183+
content: attr(title);
184+
position: absolute;
185+
bottom: 100%;
186+
left: 50%;
187+
transform: translateX(-50%);
188+
margin-bottom: 5px;
189+
padding: 4px 8px;
190+
background-color: #34312e;
191+
color: #fcf1df;
192+
font-size: 12px;
193+
white-space: nowrap;
194+
border-radius: 3px;
195+
opacity: 0;
196+
pointer-events: none;
197+
transition: opacity 0.1s ease;
198+
z-index: 1001;
199+
}
200+
201+
.sidebar-toggle-button:hover::after {
202+
opacity: 1;
203+
}
204+
205+
/* Bodywrapper when sidebar is collapsed */
206+
body.sidebar-collapsed-body .bodywrapper {
207+
background-color: white !important;
208+
position: relative;
209+
}

source/astatic/sidebar-toggle.js

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
// Sidebar collapse functionality for Cyclus documentation
2+
(function() {
3+
'use strict';
4+
5+
const COLLAPSE_THRESHOLD = 0.55; // Auto-collapse when window width <= 55% of screen width
6+
const STORAGE_KEY = 'cyclus-sidebar-collapsed';
7+
8+
let sidebar = null;
9+
let toggleButton = null;
10+
11+
function init() {
12+
if (document.readyState === 'loading') {
13+
document.addEventListener('DOMContentLoaded', setupSidebarToggle);
14+
} else {
15+
setupSidebarToggle();
16+
}
17+
}
18+
19+
function setupSidebarToggle() {
20+
// Find the sidebar element
21+
sidebar = document.querySelector('.sphinxsidebar');
22+
if (!sidebar) {
23+
console.warn('Sidebar element not found');
24+
return;
25+
}
26+
27+
// Create toggle button
28+
toggleButton = document.createElement('button');
29+
toggleButton.id = 'sidebar-toggle-btn';
30+
toggleButton.className = 'sidebar-toggle-button';
31+
toggleButton.setAttribute('aria-label', 'Toggle sidebar');
32+
toggleButton.setAttribute('title', 'Collapse sidebar');
33+
toggleButton.innerHTML = '◀';
34+
35+
// Initially place button in sidebar (top-right)
36+
sidebar.style.position = 'relative';
37+
sidebar.appendChild(toggleButton);
38+
39+
// Check initial state
40+
const wasCollapsed = localStorage.getItem(STORAGE_KEY) === 'true';
41+
const shouldAutoCollapse = checkAutoCollapse();
42+
43+
if (wasCollapsed || shouldAutoCollapse) {
44+
collapseSidebar(true);
45+
}
46+
47+
// Button click handler
48+
toggleButton.addEventListener('click', function() {
49+
const isCollapsed = sidebar.style.display === 'none';
50+
if (isCollapsed) {
51+
expandSidebar();
52+
} else {
53+
collapseSidebar(false);
54+
}
55+
});
56+
57+
// Window resize handler for auto-collapse
58+
let resizeTimeout;
59+
window.addEventListener('resize', function() {
60+
clearTimeout(resizeTimeout);
61+
resizeTimeout = setTimeout(function() {
62+
const shouldCollapse = checkAutoCollapse();
63+
const isCollapsed = sidebar.style.display === 'none';
64+
65+
if (shouldCollapse && !isCollapsed) {
66+
collapseSidebar(true);
67+
} else if (!shouldCollapse && isCollapsed && localStorage.getItem(STORAGE_KEY) !== 'true') {
68+
expandSidebar();
69+
}
70+
}, 150);
71+
});
72+
}
73+
74+
function checkAutoCollapse() {
75+
const windowWidth = window.innerWidth || document.documentElement.clientWidth;
76+
const screenWidth = screen.width;
77+
return windowWidth <= (screenWidth * COLLAPSE_THRESHOLD);
78+
}
79+
80+
function collapseSidebar(isAutoCollapse) {
81+
if (!sidebar || !toggleButton) return;
82+
83+
// Hide sidebar
84+
sidebar.style.display = 'none';
85+
86+
// Move button to body first
87+
if (sidebar.contains(toggleButton)) {
88+
document.body.appendChild(toggleButton);
89+
}
90+
91+
toggleButton.classList.add('sidebar-toggle-collapsed');
92+
toggleButton.style.position = 'fixed';
93+
94+
// Adjust bodywrapper to match navbar width FIRST
95+
const bodyWrapper = document.querySelector('.bodywrapper');
96+
if (bodyWrapper) {
97+
const relatedNav = document.querySelector('.related');
98+
if (relatedNav) {
99+
const navRect = relatedNav.getBoundingClientRect();
100+
const navStyles = window.getComputedStyle(relatedNav);
101+
const docWrapper = document.querySelector('.documentwrapper');
102+
const docLeft = docWrapper ? docWrapper.getBoundingClientRect().left : 0;
103+
104+
// Calculate relative position
105+
const relativeNavLeft = navRect.left - docLeft;
106+
const navWidth = navRect.right - navRect.left;
107+
108+
bodyWrapper.style.marginLeft = relativeNavLeft + 'px';
109+
bodyWrapper.style.width = navWidth + 'px';
110+
bodyWrapper.style.backgroundColor = 'white';
111+
bodyWrapper.classList.add('sidebar-collapsed-body');
112+
113+
// Force layout recalculation
114+
bodyWrapper.offsetHeight;
115+
}
116+
}
117+
118+
// NOW position button AFTER bodywrapper has been repositioned
119+
// Get button's current vertical position
120+
const rect = toggleButton.getBoundingClientRect();
121+
let targetTop = rect.top;
122+
123+
// Ensure button is below navbar
124+
const relatedNav = document.querySelector('.related');
125+
if (relatedNav) {
126+
const navBottom = relatedNav.getBoundingClientRect().bottom;
127+
if (targetTop < navBottom + 10) {
128+
targetTop = navBottom + 10;
129+
}
130+
}
131+
132+
// Position button at bodywrapper's left edge (all the way to the left)
133+
let targetLeft = '0px';
134+
if (bodyWrapper) {
135+
// Get bodywrapper's NEW position after it's been repositioned
136+
const bodyRect = bodyWrapper.getBoundingClientRect();
137+
targetLeft = bodyRect.left + 'px';
138+
}
139+
140+
// Disable transition for instant positioning
141+
toggleButton.style.transition = 'none';
142+
toggleButton.style.top = targetTop + 'px';
143+
toggleButton.style.left = targetLeft;
144+
toggleButton.innerHTML = '▶';
145+
toggleButton.setAttribute('title', 'Expand sidebar');
146+
147+
// Re-enable transition after a brief moment (for hover effects)
148+
setTimeout(function() {
149+
toggleButton.style.transition = '';
150+
}, 10);
151+
152+
// Store state
153+
if (!isAutoCollapse) {
154+
localStorage.setItem(STORAGE_KEY, 'true');
155+
}
156+
157+
document.body.classList.add('sidebar-collapsed-body');
158+
}
159+
160+
function expandSidebar() {
161+
if (!sidebar || !toggleButton) return;
162+
163+
// Show sidebar
164+
sidebar.style.display = '';
165+
166+
// Move button back to sidebar
167+
if (document.body.contains(toggleButton)) {
168+
sidebar.appendChild(toggleButton);
169+
}
170+
171+
toggleButton.classList.remove('sidebar-toggle-collapsed');
172+
toggleButton.style.position = 'absolute';
173+
174+
// Disable transition for instant positioning
175+
toggleButton.style.transition = 'none';
176+
toggleButton.style.top = '10px';
177+
toggleButton.style.right = '10px';
178+
toggleButton.style.left = 'auto';
179+
toggleButton.innerHTML = '◀';
180+
toggleButton.setAttribute('title', 'Collapse sidebar');
181+
182+
// Re-enable transition after a brief moment (for hover effects)
183+
setTimeout(function() {
184+
toggleButton.style.transition = '';
185+
}, 10);
186+
187+
// Restore bodywrapper
188+
const bodyWrapper = document.querySelector('.bodywrapper');
189+
if (bodyWrapper) {
190+
bodyWrapper.style.marginLeft = '';
191+
bodyWrapper.style.width = '';
192+
bodyWrapper.style.backgroundColor = '';
193+
bodyWrapper.classList.remove('sidebar-collapsed-body');
194+
}
195+
196+
// Clear state
197+
localStorage.removeItem(STORAGE_KEY);
198+
document.body.classList.remove('sidebar-collapsed-body');
199+
}
200+
201+
init();
202+
})();
203+

source/atemplates/layout.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{# Extend the cloud theme's layout and add sidebar toggle script #}
2+
{% extends "!layout.html" %}
3+
4+
{% block extrahead %}
5+
{{ super() }}
6+
<script type="text/javascript" src="{{ pathto('_static/sidebar-toggle.js', 1) }}"></script>
7+
{% endblock %}
8+

0 commit comments

Comments
 (0)