Skip to content

Commit 54c5c9b

Browse files
Preconnect for bootstrap and datatables. Mobile offcanvas toggle added next to brand. Never collapse logout implemented. Sidebar removed from small screens. Mobile menu puplutes the same sidebar category tree or list.
1 parent 1e3eafc commit 54c5c9b

File tree

3 files changed

+230
-35
lines changed

3 files changed

+230
-35
lines changed

ApiIntegrationMvc/Views/Shared/_Layout.cshtml

Lines changed: 71 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
<title>@ViewData["Title"] - ApiIntegrationMvc</title>
77
<script type="importmap"></script>
88

9+
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>
10+
<link rel="preconnect" href="https://cdn.datatables.net" crossorigin>
11+
12+
913
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.css" rel="stylesheet" />
1014
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" />
1115
<link rel="stylesheet" href="https://cdn.datatables.net/2.1.8/css/dataTables.bootstrap5.css" />
@@ -19,13 +23,43 @@
1923
<body>
2024
<div class="app-grid">
2125
<header class="app-header">
22-
<nav class="navbar navbar-expand-sm navbar-light bg-white border-bottom box-shadow mb-3">
26+
<nav class="navbar navbar-expand-sm navbar-light bg-white border-bottom box-shadow-sm mb-3">
2327
<div class="container-fluid">
24-
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">Integration Portal</a>
25-
26-
<!-- Keep only one collapse container -->
28+
<!-- LEFT: brand + mobile offcanvas toggle (always visible on < md) -->
29+
<div class="d-flex align-items-center gap-2">
30+
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">Integration Portal</a>
31+
32+
<button class="nav-link d-md-none"
33+
type="button"
34+
data-bs-toggle="offcanvas"
35+
data-bs-target="#offcanvasSidebar"
36+
aria-controls="offcanvasSidebar"
37+
aria-label="Open menu"
38+
data-bs-placement="bottom"
39+
title="Menu">
40+
<i class="bi bi-list me-1"></i>
41+
</button>
42+
</div>
43+
<!-- CENTER: collapsing nav links -->
2744
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
2845
<ul class="navbar-nav flex-grow-1">
46+
<li>
47+
<!-- Desktop toggle (md+ only) -->
48+
<button id="sidebarToggle"
49+
class="nav-link d-none d-md-inline-flex"
50+
type="button"
51+
aria-label="Toggle sidebar"
52+
data-bs-toggle="tooltip"
53+
data-bs-placement="bottom"
54+
title="Toggle sidebar">
55+
<i class="bi bi-layout-sidebar me-1"></i>
56+
</button>
57+
58+
</li>
59+
<li>
60+
61+
</li>
62+
2963
<li class="nav-item">
3064
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">
3165
<i class="bi bi-house-door me-1"></i> Home
@@ -37,31 +71,31 @@
3771
</a>
3872
</li>
3973
</ul>
74+
</div>
4075

41-
<ul class="navbar-nav ms-auto">
42-
@if (User?.Identity?.IsAuthenticated == true)
43-
{
44-
<li class="nav-item">
45-
<form asp-area="Account" asp-controller="Login" asp-action="Logout" method="post" class="d-inline">
46-
@Html.AntiForgeryToken()
47-
<button type="submit" class="btn btn-link nav-link">
48-
<i class="bi bi-box-arrow-right me-1"></i> Logout
49-
</button>
50-
</form>
51-
</li>
52-
}
53-
else
54-
{
55-
<li class="nav-item">
56-
<a class="nav-link" asp-area="Account" asp-controller="Login" asp-action="Index">Login</a>
57-
</li>
58-
}
59-
</ul>
76+
<!-- RIGHT: Logout (ALWAYS visible, never collapses) -->
77+
<div class="d-flex ms-auto align-items-center">
78+
@if (User?.Identity?.IsAuthenticated == true)
79+
{
80+
<form asp-area="Account" asp-controller="Login" asp-action="Logout" method="post" class="d-inline">
81+
@Html.AntiForgeryToken()
82+
<button type="submit" class="btn btn-link nav-link">
83+
<i class="bi bi-box-arrow-right me-1"></i> Logout
84+
</button>
85+
</form>
86+
}
87+
else
88+
{
89+
<a class="nav-link" asp-area="Account" asp-controller="Login" asp-action="Index">Login</a>
90+
}
6091
</div>
92+
6193
</div>
6294
</nav>
95+
96+
6397
</header>
64-
<aside class="app-sidebar" id="appSidebar">
98+
<aside class="app-sidebar d-none d-md-block" id="appSidebar">
6599
@await Component.InvokeAsync("CategoryTree")
66100
</aside>
67101

@@ -72,13 +106,23 @@
72106
</div>
73107
</main>
74108

75-
<footer class="border-top footer text-muted app-footer">
109+
<footer class="border-top text-muted app-footer">
76110
<div class="container">
77111
&copy; 2025 - Integration Portal -
78112
<a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
79113
</div>
80114
</footer>
81115
</div>
116+
<!-- Offcanvas for mobile (place OUTSIDE .app-grid) -->
117+
<div class="offcanvas offcanvas-start" id="offcanvasSidebar" tabindex="-1">
118+
<div class="offcanvas-header">
119+
<h5 class="mb-0">Integration Portal</h5>
120+
<button class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
121+
</div>
122+
<div class="offcanvas-body p-0">
123+
@await Component.InvokeAsync("CategoryTree", new { model = Model })
124+
</div>
125+
</div>
82126
<!-- Scripts unchanged -->
83127
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
84128
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation/1.19.5/jquery.validate.min.js"></script>
@@ -104,6 +148,8 @@
104148
});
105149
}
106150
});
151+
107152
</script>
153+
108154
</body>
109155
</html>

ApiIntegrationMvc/wwwroot/css/site.css

Lines changed: 138 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
/* Single baseline only (avoid duplicates) */
2020
html {
2121
font-size: var(--app-base-size);
22+
height: 100%;
23+
margin: 0;
2224
}
2325

2426
@media (min-width:768px) {
@@ -32,7 +34,7 @@ body {
3234
font-size: 1rem; /* everything scales from html */
3335
line-height: 1.5;
3436
color: var(--app-text);
35-
min-height: 100%;
37+
height: 100%;
3638
margin: 0; /* remove page jump; footer spacing handled in layout areas */
3739
}
3840

@@ -62,14 +64,14 @@ body {
6264
App Layout Grid
6365
========================= */
6466
.app-grid {
65-
display: grid;
66-
grid-template-columns: 280px 1fr;
67+
display: grid;
6768
grid-template-rows: auto 1fr auto; /* header | content | footer */
6869
grid-template-areas:
6970
"header header"
7071
"sidebar main"
7172
"footer footer";
72-
min-height: 100dvh;
73+
height: 100svh; /* or 100vh; ensures fixed viewport height */
74+
overflow: hidden; /* KEY: stop page scroll; inner cells will scroll */
7375
}
7476

7577
.app-header {
@@ -80,7 +82,9 @@ body {
8082
grid-area: sidebar;
8183
padding: 1rem .75rem;
8284
background: linear-gradient(135deg,#f8f9fa,#f1f3f4);
83-
overflow-y: auto;
85+
min-height: 0; /* allow them to shrink inside the 1fr row */
86+
min-width: 0; /* avoid horizontal overflow pushing layout */
87+
overflow: auto; /* scroll here, not the page */
8488
scrollbar-width: none; /* Firefox */
8589
border-right: none;
8690
/* Typography: sidebar slightly compact but consistent */
@@ -89,14 +93,43 @@ body {
8993
line-height: 1.45;
9094
}
9195

92-
.app-sidebar::-webkit-scrollbar {
93-
display: none;
96+
/* Collapse behavior on desktop */
97+
@media (min-width: 768px) {
98+
99+
.app-grid {
100+
display: grid;
101+
grid-template-columns: 280px 1fr;
102+
grid-template-areas:
103+
"header header"
104+
"sidebar main"
105+
"footer footer";
106+
}
107+
108+
109+
.app-grid.sidebar-collapsed {
110+
grid-template-columns: 0 1fr; /* hide the first column */
111+
}
112+
113+
.app-grid.sidebar-collapsed .app-sidebar {
114+
display: none; /* make sure it's gone */
94115
}
116+
}
117+
/* (Optional) smoother width change for main area */
118+
.app-grid {
119+
transition: grid-template-columns .15s ease;
120+
}
121+
122+
.app-sidebar::-webkit-scrollbar {
123+
display: none;
124+
}
95125

96126
.app-main {
97127
grid-area: main;
98-
padding: 1rem;
99-
border-top: 1px solid #dee2e6; /* prevents content running under sidebar on some layouts */
128+
min-height: 0;
129+
min-width: 0;
130+
overflow: auto;
131+
grid-area: main;
132+
padding: 1rem;
100133
padding-top: 1rem;
101134
font: inherit; /* inherit same font as body */
102135
}
@@ -116,7 +149,8 @@ body {
116149
.app-sidebar {
117150
position: sticky;
118151
top: 0;
119-
max-height: 100dvh;
152+
max-height: 100%;
153+
overflow-y: auto; /* sidebar may scroll independently */
120154
}
121155
}
122156

@@ -284,3 +318,97 @@ h3 {
284318
.table-dark {
285319
background-color: var(--brand-dark) !important;
286320
}
321+
322+
/* Hide table until DataTables finishes */
323+
table.dataTable-pending {
324+
visibility: hidden;
325+
}
326+
327+
/* Smooth change when collapsing */
328+
.app-grid {
329+
transition: grid-template-columns .2s ease;
330+
}
331+
332+
/* Default desktop: sidebar + main */
333+
@media (min-width:768px) {
334+
.app-grid {
335+
grid-template-columns: 280px 1fr;
336+
}
337+
}
338+
339+
/* Collapsed desktop: hide sidebar, let main take full width */
340+
@media (min-width:768px) {
341+
.app-grid.sidebar-collapsed {
342+
grid-template-columns: 0 1fr;
343+
}
344+
345+
.app-grid.sidebar-collapsed .app-sidebar {
346+
display: none !important;
347+
}
348+
/* beats d-md-block!important */
349+
}
350+
351+
/* Keep header above content if needed */
352+
.app-header {
353+
z-index: 1030;
354+
}
355+
356+
/* Mobile: collapse grid to a single column */
357+
@media (max-width: 767.98px) {
358+
.app-grid {
359+
grid-template-columns: 1fr !important;
360+
grid-template-areas:
361+
"header"
362+
"main"
363+
"footer"; /* no sidebar row on mobile */
364+
}
365+
366+
/* Ensure main spans the only column (defensive) */
367+
.app-main {
368+
grid-column: 1 / -1;
369+
}
370+
371+
/* Ensure the offcanvas copy of the menu stays visible */
372+
.offcanvas .app-sidebar {
373+
display: block !important;
374+
overflow: auto;
375+
max-height: calc(100vh - 56px); /* tweak if your header height differs */
376+
}
377+
378+
/* Hide the static sidebar track completely on mobile */
379+
#appSidebar,
380+
.app-sidebar {
381+
display: none !important; /* d-none d-md-block does this too, this is just belt & braces */
382+
}
383+
384+
}
385+
386+
/* Compact, icon-only button */
387+
.btn-icon {
388+
--icon-size: 2.25rem; /* tweak: 2.0–2.5rem */
389+
width: var(--icon-size);
390+
height: var(--icon-size);
391+
padding: 0;
392+
display: inline-flex;
393+
align-items: center;
394+
justify-content: center;
395+
border: 0;
396+
border-radius: .5rem;
397+
background: transparent;
398+
}
399+
400+
.btn-icon i {
401+
font-size: 1.2rem;
402+
line-height: 1;
403+
}
404+
/* icon size */
405+
406+
/* Subtle hover/focus */
407+
.btn-icon:hover {
408+
background: rgba(0,0,0,.06);
409+
}
410+
411+
.btn-icon:focus-visible {
412+
outline: 2px solid rgba(13,110,253,.6);
413+
outline-offset: 2px;
414+
}

ApiIntegrationMvc/wwwroot/js/site.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,24 @@
22
// for details on configuring this project to bundle and minify static web assets.
33

44
// Write your JavaScript code.
5+
6+
7+
document.addEventListener('DOMContentLoaded', function () {
8+
const grid = document.querySelector('.app-grid');
9+
const btn = document.getElementById('sidebarToggle');
10+
11+
if (grid && btn) {
12+
btn.addEventListener('click', () => {
13+
const collapsed = grid.classList.toggle('sidebar-collapsed');
14+
btn.setAttribute('aria-pressed', collapsed ? 'true' : 'false');
15+
16+
// If you use DataTables in .app-main, nudge it after layout change
17+
if (window.jQuery && jQuery.fn.dataTable) {
18+
jQuery('.dataTable').each(function () {
19+
const api = jQuery(this).DataTable();
20+
api.columns.adjust().responsive.recalc();
21+
});
22+
}
23+
});
24+
}
25+
});

0 commit comments

Comments
 (0)