Skip to content

Commit d71b202

Browse files
committed
Move sidebar toggle into the pane
1 parent 9591e00 commit d71b202

3 files changed

Lines changed: 138 additions & 76 deletions

File tree

astro.config.mjs

Lines changed: 3 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -19,62 +19,13 @@ export default defineConfig({
1919
favicon: '/favicon.svg',
2020
lastUpdated: true,
2121
pagefind: true,
22-
head: [
23-
{
24-
tag: 'script',
25-
attrs: { type: 'module' },
26-
content: `(() => {
27-
const STORAGE_KEY = 'devops-sidebar-collapsed';
28-
const root = document.documentElement;
29-
30-
const readStoredState = () => {
31-
try {
32-
return localStorage.getItem(STORAGE_KEY) === '1';
33-
} catch {
34-
return false;
35-
}
36-
};
37-
38-
const writeState = (collapsed) => {
39-
root.dataset.sidebarCollapsed = collapsed ? 'true' : 'false';
40-
try {
41-
localStorage.setItem(STORAGE_KEY, collapsed ? '1' : '0');
42-
} catch {}
43-
44-
const button = document.querySelector('[data-sidebar-toggle]');
45-
if (button) {
46-
button.setAttribute('aria-pressed', String(collapsed));
47-
button.setAttribute('aria-label', collapsed ? 'Expand sidebar' : 'Collapse sidebar');
48-
button.title = collapsed ? 'Expand sidebar' : 'Collapse sidebar';
49-
}
50-
};
51-
52-
const mount = () => {
53-
const titleWrapper = document.querySelector('.title-wrapper');
54-
if (!titleWrapper || titleWrapper.querySelector('[data-sidebar-toggle]')) return;
55-
56-
const button = document.createElement('button');
57-
button.type = 'button';
58-
button.className = 'sidebar-toggle print:hidden';
59-
button.dataset.sidebarToggle = 'true';
60-
button.innerHTML =
61-
'<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="m15 18-6-6 6-6"></path></svg>';
62-
titleWrapper.prepend(button);
63-
button.addEventListener('click', () => writeState(root.dataset.sidebarCollapsed !== 'true'));
64-
writeState(root.dataset.sidebarCollapsed === 'true');
65-
};
66-
67-
root.dataset.sidebarCollapsed = readStoredState() ? 'true' : 'false';
68-
document.addEventListener('DOMContentLoaded', mount, { once: true });
69-
if (document.readyState !== 'loading') mount();
70-
window.addEventListener('pageshow', mount);
71-
})();`,
72-
},
73-
],
7422
tableOfContents: {
7523
minHeadingLevel: 2,
7624
maxHeadingLevel: 3,
7725
},
26+
components: {
27+
Sidebar: './src/components/Sidebar.astro',
28+
},
7829
sidebar: [
7930
{ label: 'Home', link: '/' },
8031
{

src/components/Sidebar.astro

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
---
2+
import MobileMenuFooter from '@astrojs/starlight/components/MobileMenuFooter.astro';
3+
import SidebarPersister from '@astrojs/starlight/components/SidebarPersister.astro';
4+
import SidebarSublist from '@astrojs/starlight/components/SidebarSublist.astro';
5+
6+
const { sidebar } = Astro.locals.starlightRoute;
7+
---
8+
9+
<SidebarPersister>
10+
<div class="sidebar-shell">
11+
<button
12+
type="button"
13+
class="sidebar-collapse-toggle print:hidden"
14+
data-sidebar-toggle
15+
aria-pressed="false"
16+
aria-label="Collapse sidebar"
17+
title="Collapse sidebar"
18+
>
19+
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
20+
<path d="m15 18-6-6 6-6"></path>
21+
</svg>
22+
</button>
23+
24+
<div class="sidebar-scroll">
25+
<SidebarSublist sublist={sidebar} />
26+
</div>
27+
</div>
28+
</SidebarPersister>
29+
30+
<div class="md:sl-hidden">
31+
<MobileMenuFooter />
32+
</div>
33+
34+
<script is:inline>
35+
(() => {
36+
const STORAGE_KEY = 'devops-sidebar-collapsed';
37+
const COLLAPSED_WIDTH = '3.25rem';
38+
const root = document.documentElement;
39+
const getPane = () => document.querySelector('.sidebar-pane');
40+
const getShell = () => document.querySelector('.sidebar-shell');
41+
42+
const readStoredState = () => {
43+
try {
44+
return localStorage.getItem(STORAGE_KEY) === '1';
45+
} catch {
46+
return false;
47+
}
48+
};
49+
50+
const syncLayout = (collapsed) => {
51+
root.style.setProperty('--sl-sidebar-width', collapsed ? COLLAPSED_WIDTH : '19rem');
52+
const pane = getPane();
53+
const shell = getShell();
54+
if (pane) {
55+
pane.style.width = collapsed ? COLLAPSED_WIDTH : '';
56+
pane.style.minWidth = collapsed ? COLLAPSED_WIDTH : '';
57+
pane.style.maxWidth = collapsed ? COLLAPSED_WIDTH : '';
58+
pane.style.overflow = collapsed ? 'hidden' : '';
59+
}
60+
if (shell) {
61+
shell.style.width = collapsed ? COLLAPSED_WIDTH : '';
62+
}
63+
};
64+
65+
const syncButton = () => {
66+
const button = document.querySelector('[data-sidebar-toggle]');
67+
if (!button) return;
68+
const collapsed = root.dataset.sidebarCollapsed === 'true';
69+
button.setAttribute('aria-pressed', String(collapsed));
70+
button.setAttribute('aria-label', collapsed ? 'Expand sidebar' : 'Collapse sidebar');
71+
button.title = collapsed ? 'Expand sidebar' : 'Collapse sidebar';
72+
};
73+
74+
const setState = (collapsed, persist = true) => {
75+
root.dataset.sidebarCollapsed = collapsed ? 'true' : 'false';
76+
syncLayout(collapsed);
77+
if (persist) {
78+
try {
79+
localStorage.setItem(STORAGE_KEY, collapsed ? '1' : '0');
80+
} catch {}
81+
}
82+
syncButton();
83+
};
84+
85+
const mount = () => {
86+
const button = document.querySelector('[data-sidebar-toggle]');
87+
if (!button || button.dataset.bound === 'true') return;
88+
button.dataset.bound = 'true';
89+
button.addEventListener('click', () => setState(root.dataset.sidebarCollapsed !== 'true'));
90+
setState(readStoredState(), false);
91+
};
92+
93+
setState(readStoredState(), false);
94+
if (document.readyState === 'loading') {
95+
document.addEventListener('DOMContentLoaded', mount, { once: true });
96+
} else {
97+
mount();
98+
}
99+
window.addEventListener('pageshow', mount);
100+
})();
101+
</script>

src/styles/portal.css

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,9 @@ body,
9090
.header,
9191
.sidebar-pane,
9292
.sidebar-content,
93-
.sidebar-toggle,
93+
.sidebar-shell,
94+
.sidebar-scroll,
95+
.sidebar-collapse-toggle,
9496
.top-level,
9597
.top-level :is(summary, li, span),
9698
.top-level a,
@@ -118,39 +120,52 @@ starlight-theme-select {
118120
border-inline-end-color: var(--portal-line-soft) !important;
119121
}
120122

121-
.sidebar-toggle {
123+
.sidebar-shell {
124+
position: relative;
125+
}
126+
127+
.sidebar-scroll {
128+
min-height: 100%;
129+
padding-top: 3rem;
130+
}
131+
132+
.sidebar-collapse-toggle {
122133
align-items: center;
123134
background: var(--portal-panel);
124135
border: 1px solid var(--portal-line-soft);
125136
border-radius: 0.45rem;
126137
color: var(--portal-ink);
138+
cursor: pointer;
127139
display: inline-flex;
128140
height: 2.35rem;
129141
justify-content: center;
130-
margin-inline-end: 0.75rem;
142+
position: absolute;
143+
inset-block-start: 0.75rem;
144+
inset-inline-end: 0.5rem;
131145
min-width: 2.35rem;
132146
padding: 0;
147+
z-index: 2;
133148
transition:
134149
background-color 0.16s ease,
135150
border-color 0.16s ease,
136151
color 0.16s ease,
137152
transform 0.16s ease;
138153
}
139154

140-
.sidebar-toggle:hover,
141-
.sidebar-toggle:focus-visible {
155+
.sidebar-collapse-toggle:hover,
156+
.sidebar-collapse-toggle:focus-visible {
142157
background: var(--portal-accent-soft);
143158
border-color: #7bcf72;
144159
color: var(--portal-ink);
145160
}
146161

147-
.sidebar-toggle svg {
162+
.sidebar-collapse-toggle svg {
148163
height: 1rem;
149164
width: 1rem;
150165
transition: transform 0.16s ease;
151166
}
152167

153-
html[data-sidebar-collapsed="true"] .sidebar-toggle svg {
168+
html[data-sidebar-collapsed="true"] .sidebar-collapse-toggle svg {
154169
transform: rotate(180deg);
155170
}
156171

@@ -882,32 +897,23 @@ a[href="#_top"],
882897
border-color: var(--portal-line-soft);
883898
}
884899

885-
@media (width >= 50em) {
900+
@media (min-width: 50rem) {
886901
html[data-sidebar-collapsed="true"] {
887-
--sl-content-inline-start: 0rem;
888-
--sl-sidebar-width: 0rem;
902+
--sl-sidebar-width: 3.25rem;
889903
}
890904

891-
html[data-sidebar-collapsed="true"] .sidebar {
892-
display: none !important;
893-
}
894-
895-
html[data-sidebar-collapsed="true"] .sidebar-pane {
896-
max-width: 0 !important;
897-
min-width: 0 !important;
905+
html[data-sidebar-collapsed="true"] .sidebar-scroll {
898906
opacity: 0;
899-
overflow: hidden;
900-
padding: 0 !important;
901907
pointer-events: none;
902-
width: 0 !important;
903908
}
904909

905-
html[data-sidebar-collapsed="true"] .sidebar-content {
906-
overflow: hidden;
910+
html[data-sidebar-collapsed="true"] .sidebar-collapse-toggle {
911+
inset-inline-end: 0.4rem;
907912
}
908913

909-
html[data-sidebar-collapsed="true"] .sidebar {
910-
border-inline-end-color: transparent !important;
914+
html[data-sidebar-collapsed="true"] .sidebar-pane {
915+
overflow: hidden;
916+
width: var(--sl-sidebar-width) !important;
911917
}
912918
}
913919

@@ -965,6 +971,10 @@ a[href="#_top"],
965971
width: 100%;
966972
}
967973

974+
.sidebar-collapse-toggle {
975+
display: none;
976+
}
977+
968978
.metric-grid {
969979
grid-template-columns: repeat(2, minmax(0, 1fr));
970980
}

0 commit comments

Comments
 (0)