@@ -13,7 +13,51 @@ type MenuItem = {
1313 onclick : ( ) => void | Promise < void >
1414}
1515
16+ const MENU_COLLAPSED_KEY = 'solid-panes-menu-collapsed'
17+ let menuCollapsed = false
18+
19+ const loadMenuCollapsedState = ( ) : boolean => {
20+ try {
21+ return localStorage . getItem ( MENU_COLLAPSED_KEY ) === 'true'
22+ } catch ( error ) {
23+ return false
24+ }
25+ }
26+
27+ const saveMenuCollapsedState = ( collapsed : boolean ) : void => {
28+ try {
29+ localStorage . setItem ( MENU_COLLAPSED_KEY , String ( collapsed ) )
30+ } catch ( error ) {
31+ // ignore storage errors
32+ }
33+ }
34+
35+ const updateMenuCollapseButton = ( ) : void => {
36+ const collapseBtn = document . getElementById ( 'MenuCollapseBtn' ) as HTMLButtonElement | null
37+ if ( ! collapseBtn ) return
38+ collapseBtn . textContent = menuCollapsed ? '\u203A' : '\u2039'
39+ collapseBtn . setAttribute ( 'aria-label' , menuCollapsed ? 'Expand navigation menu' : 'Collapse navigation menu' )
40+ }
41+
42+ const updateCollapseButtonPosition = ( navMenu : HTMLElement | null , collapseBtn : HTMLButtonElement | null ) : void => {
43+ if ( ! navMenu || ! collapseBtn ) return
44+ const navRect = navMenu . getBoundingClientRect ( )
45+ const rootFontSize = parseFloat ( getComputedStyle ( document . documentElement ) . fontSize ) || 16
46+ const buttonWidth = parseFloat ( getComputedStyle ( collapseBtn ) . width ) || 1.25 * rootFontSize
47+ const offsetRem = ( navRect . width - buttonWidth / 2 ) / rootFontSize
48+ collapseBtn . style . setProperty ( 'left' , `${ offsetRem . toFixed ( 3 ) } rem` , 'important' )
49+ }
50+
51+ const applyMenuCollapsedState = ( navMenu : HTMLElement | null ) : void => {
52+ if ( ! navMenu ) return
53+ navMenu . classList . toggle ( 'collapsed' , menuCollapsed )
54+ updateMenuCollapseButton ( )
55+ const collapseBtn = document . getElementById ( 'MenuCollapseBtn' ) as HTMLButtonElement | null
56+ updateCollapseButtonPosition ( navMenu , collapseBtn )
57+ }
58+
1659const ensureMenuSkeleton = ( ) => {
60+ menuCollapsed = loadMenuCollapsedState ( )
1761 const root = document . querySelector ( '[role="main"]' ) || document . body
1862
1963 let navMenu = document . getElementById ( 'NavMenu' ) as HTMLElement | null
@@ -55,6 +99,18 @@ const ensureMenuSkeleton = () => {
5599 overlay . hidden = true
56100 document . body . appendChild ( overlay )
57101 }
102+
103+ let collapseBtn = document . getElementById ( 'MenuCollapseBtn' ) as HTMLButtonElement | null
104+ if ( ! collapseBtn ) {
105+ collapseBtn = document . createElement ( 'button' )
106+ collapseBtn . id = 'MenuCollapseBtn'
107+ collapseBtn . className = 'menu-collapse'
108+ collapseBtn . type = 'button'
109+ collapseBtn . setAttribute ( 'aria-label' , 'Collapse navigation menu' )
110+ collapseBtn . textContent = '\u2039'
111+ collapseBtn . hidden = true
112+ document . body . appendChild ( collapseBtn )
113+ }
58114}
59115
60116const getMenuItems = async ( subject : NamedNode , outliner : any ) : Promise < MenuItem [ ] > => {
@@ -146,22 +202,28 @@ const renderMenuItems = async (subject: NamedNode, outliner: OutlineManager, con
146202export const refreshMenu = ( layout : 'mobile' | 'desktop' ) => {
147203 const navMenu = document . getElementById ( 'NavMenu' ) as HTMLElement | null
148204 const toggle = document . getElementById ( 'MenuToggleBtn' ) as HTMLButtonElement | null
205+ const collapseBtn = document . getElementById ( 'MenuCollapseBtn' ) as HTMLButtonElement | null
149206 const overlay = document . getElementById ( 'MenuOverlay' ) as HTMLElement | null
150207
151- if ( ! navMenu || ! toggle || ! overlay ) return
208+ if ( ! navMenu || ! toggle || ! overlay || ! collapseBtn ) return
152209
153210 if ( layout === 'mobile' ) {
154211 navMenu . classList . add ( 'mobile-hidden' )
155212 navMenu . classList . remove ( 'mobile-visible' )
156213 toggle . hidden = false
214+ collapseBtn . hidden = true
157215 overlay . hidden = true
158216 navMenu . hidden = false
217+ navMenu . classList . remove ( 'collapsed' )
159218 toggle . setAttribute ( 'aria-expanded' , 'false' )
160219 } else {
161220 navMenu . classList . remove ( 'mobile-hidden' , 'mobile-visible' )
162221 toggle . hidden = true
222+ collapseBtn . hidden = false
163223 overlay . hidden = true
164224 navMenu . hidden = false
225+ applyMenuCollapsedState ( navMenu )
226+ updateCollapseButtonPosition ( navMenu , collapseBtn )
165227 toggle . setAttribute ( 'aria-expanded' , 'false' )
166228 }
167229}
@@ -200,6 +262,15 @@ export const createLeftSideMenu = async (subject: NamedNode, outliner: OutlineMa
200262 } )
201263 }
202264
265+ const collapseBtn = document . getElementById ( 'MenuCollapseBtn' ) as HTMLButtonElement | null
266+ if ( collapseBtn ) {
267+ collapseBtn . addEventListener ( 'click' , ( ) => {
268+ menuCollapsed = ! menuCollapsed
269+ saveMenuCollapsedState ( menuCollapsed )
270+ applyMenuCollapsedState ( navMenu )
271+ } )
272+ }
273+
203274 if ( menuOverlay ) {
204275 menuOverlay . addEventListener ( 'click' , closeMobileMenu )
205276 }
0 commit comments