feat: convert sidebar to collapsible menu
Daniel Silva committed
Aug 19, 2026 at 15:25 UTC
f15d0e2f514d59ebfbccaaec8c5275bc55a4ce77
2 files changed
+70
-38
frontend/css/layout.css
+52
-30
@@ -9,8 +9,20 @@
9
width: var(--sidebar-width);
10
background: var(--sidebar-bg);
11
color: var(--sidebar-text-hover);
12
- flex-shrink: 0;
12
+ position: fixed;
13
+ top: 0;
14
+ left: 0;
15
+ height: 100vh;
16
+ z-index: 100;
17
+ display: flex;
18
+ flex-direction: column;
19
+ transform: translateX(-100%);
20
transition: transform 0.3s ease;
21
+ box-shadow: 0 0 1.25rem rgba(0, 0, 0, 0.2);
22
+}
23
+
24
+.sidebar.open {
25
+ transform: translateX(0);
26
}
27
28
.sidebar-header {
@@ -31,6 +43,8 @@
43
.sidebar-nav {
44
list-style: none;
45
padding: 1rem 0;
46
+ overflow-y: auto;
47
+ flex: 1;
48
}
49
50
.sidebar-nav a {
@@ -54,6 +68,15 @@
68
text-decoration: none;
69
}
70
71
+.sidebar-footer {
72
+ padding: 1rem 1.25rem;
73
+ border-top: 1px solid var(--sidebar-border);
74
+}
75
+
76
+.sidebar-footer .btn {
77
+ width: 100%;
78
+}
79
+
80
.main-wrapper {
81
flex: 1;
82
display: flex;
@@ -62,31 +85,43 @@
85
}
86
87
.topbar {
65
- height: var(--header-height);
66
- background: var(--topbar-bg);
67
- color: var(--topbar-text);
68
- border-bottom: 1px solid var(--gray-200);
69
- display: flex;
70
- align-items: center;
71
- justify-content: space-between;
72
- padding: 0 1.5rem;
88
+ height: 0;
89
}
90
75
-.topbar-toggle {
76
- display: none;
77
- background: none;
91
+.menu-toggle {
92
+ position: fixed;
93
+ top: 1rem;
94
+ left: 1rem;
95
+ z-index: 90;
96
+ width: 2.75rem;
97
+ height: 2.75rem;
98
border: none;
79
- font-size: 1.5rem;
99
+ border-radius: 50%;
100
+ background: var(--sidebar-bg);
101
+ color: var(--sidebar-text-hover);
102
+ font-size: 1.25rem;
103
cursor: pointer;
81
- color: var(--topbar-text);
104
+ box-shadow: 0 0.125rem 0.5rem rgba(0, 0, 0, 0.2);
105
}
106
107
.main-content {
108
flex: 1;
86
- padding: 1.5rem;
109
+ padding: 4.75rem 1.5rem 1.5rem;
110
overflow-y: auto;
111
}
112
113
+.visually-hidden {
114
+ position: absolute;
115
+ width: 1px;
116
+ height: 1px;
117
+ padding: 0;
118
+ margin: -1px;
119
+ overflow: hidden;
120
+ clip: rect(0, 0, 0, 0);
121
+ white-space: nowrap;
122
+ border: 0;
123
+}
124
+
125
.auth-layout {
126
min-height: 100vh;
127
display: flex;
@@ -157,20 +192,7 @@
192
}
193
194
@media (max-width: 768px) {
160
- .sidebar {
161
- position: fixed;
162
- top: 0;
163
- left: 0;
164
- height: 100vh;
165
- z-index: 100;
166
- transform: translateX(-100%);
167
- }
168
-
169
- .sidebar.open {
170
- transform: translateX(0);
171
- }
172
-
173
- .topbar-toggle {
174
- display: block;
195
+ .main-content {
196
+ padding: 4.75rem 1rem 1rem;
197
}
198
}
frontend/js/layout.js
+18
-8
@@ -1,5 +1,5 @@
1
/**
2
- * MAPS Connect - Layout compartido (sidebar + topbar)
2
+ * MAPS Connect - Layout compartido (menú lateral plegable)
3
*/
4
5
const NAV_ITEMS = [
@@ -38,7 +38,12 @@ const Layout = {
38
<small>${user?.nombre || 'Usuario'}</small>
39
</div>
40
<ul class="sidebar-nav">${navLinks}</ul>
41
+ <div class="sidebar-footer">
42
+ <button class="btn btn-sm btn-outline" id="btn-logout">Cerrar sesión</button>
43
+ </div>
44
`;
45
+
46
+ document.getElementById('btn-logout')?.addEventListener('click', () => Auth.logout());
47
},
48
49
renderTopbar() {
@@ -46,17 +51,22 @@ const Layout = {
51
if (!topbar) return;
52
53
topbar.innerHTML = `
49
- <button class="topbar-toggle" id="sidebar-toggle" aria-label="Menú">Menú</button>
50
- <span id="topbar-title"></span>
51
- <button class="btn btn-sm btn-outline" id="btn-logout">Cerrar sesión</button>
54
+ <button class="menu-toggle" id="sidebar-toggle" type="button"
55
+ aria-label="Abrir menú" aria-controls="sidebar" aria-expanded="false">
56
+ <span aria-hidden="true">☰</span>
57
+ </button>
58
+ <span id="topbar-title" class="visually-hidden"></span>
59
`;
53
-
54
- document.getElementById('btn-logout')?.addEventListener('click', () => Auth.logout());
60
},
61
62
bindToggle() {
58
- document.getElementById('sidebar-toggle')?.addEventListener('click', () => {
59
- document.getElementById('sidebar')?.classList.toggle('open');
63
+ const toggle = document.getElementById('sidebar-toggle');
64
+ const sidebar = document.getElementById('sidebar');
65
+
66
+ toggle?.addEventListener('click', () => {
67
+ const isOpen = sidebar?.classList.toggle('open') ?? false;
68
+ toggle.setAttribute('aria-expanded', String(isOpen));
69
+ toggle.setAttribute('aria-label', isOpen ? 'Cerrar menú' : 'Abrir menú');
70
});
71
},
72