Skip to content

Commit 2f0b8e0

Browse files
chore: refactor website nav menu
1 parent 299839b commit 2f0b8e0

3 files changed

Lines changed: 170 additions & 51 deletions

File tree

Lines changed: 34 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
@inject ApplicationSettings ApplicationSettings
2+
@inject IJSRuntime JSRuntime
23

34
<nav class="navbar navbar-expand-lg">
45
<div class="container">
@@ -7,19 +8,32 @@
78
@ApplicationSettings.Localized.Name
89
</a>
910

10-
<!-- Desktop menu (hidden on mobile) -->
11-
<div class="d-none d-lg-block">
12-
<ul class="navbar-nav ms-auto">
11+
<!-- Mobile menu button and language selector -->
12+
<div class="d-lg-none d-flex align-items-center">
13+
<SelectLanguage BtnCssClass="btn btn-outline-light border-0 me-2"
14+
ShowIcon="true"
15+
ShowShortName="true"
16+
ShowSelectedItem="true"
17+
IconCssClass="text-secondary fs-5"
18+
WrapperCssClass="language-selector-mobile" />
19+
<button class="navbar-toggler" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasNavbar" aria-controls="offcanvasNavbar" aria-label="Toggle navigation">
20+
<span class="navbar-toggler-icon"></span>
21+
</button>
22+
</div>
23+
24+
<!-- Single Navigation Menu - Works for both desktop and mobile -->
25+
<div class="navbar-nav-container">
26+
<ul class="navbar-nav responsive-nav" id="singleNavMenu">
1327
<li class="nav-item">
14-
<a class="nav-link" href="#home">@WebAppLocalizer[WebAppResource.Nav_Home]</a>
28+
<a class="nav-link" href="#home" data-bs-dismiss="offcanvas" data-scroll-to-section="true">@WebAppLocalizer[WebAppResource.Nav_Home]</a>
1529
</li>
1630
<li class="nav-item">
17-
<a class="nav-link" href="#pricing">@WebAppLocalizer[WebAppResource.Nav_Pricing]</a>
31+
<a class="nav-link" href="#pricing" data-bs-dismiss="offcanvas" data-scroll-to-section="true">@WebAppLocalizer[WebAppResource.Nav_Pricing]</a>
1832
</li>
1933
<li class="nav-item">
20-
<a class="nav-link" href="#">@WebAppLocalizer[WebAppResource.Nav_Contact]</a>
34+
<a class="nav-link" href="#" data-bs-dismiss="offcanvas">@WebAppLocalizer[WebAppResource.Nav_Contact]</a>
2135
</li>
22-
<li class="nav-item me-2">
36+
<li class="nav-item language-selector-desktop">
2337
<SelectLanguage BtnCssClass="btn btn-outline-light border-0"
2438
ShowIcon="true"
2539
ShowShortName="true"
@@ -28,7 +42,7 @@
2842
SelectedItemCssClass="text-secondary ms-1"
2943
WrapperCssClass="language-selector" />
3044
</li>
31-
<li class="nav-item ms-2">
45+
<li class="nav-item dashboard-btn">
3246
@if (CurrentUser.IsAuthenticated())
3347
{
3448
<a class="btn btn-warning-gradient" href="/dashboard">
@@ -46,19 +60,6 @@
4660
</li>
4761
</ul>
4862
</div>
49-
50-
<!-- Mobile menu button and language selector -->
51-
<div class="d-lg-none d-flex align-items-center">
52-
<SelectLanguage BtnCssClass="btn btn-outline-light border-0 me-2"
53-
ShowIcon="true"
54-
ShowShortName="true"
55-
ShowSelectedItem="true"
56-
IconCssClass="text-secondary fs-5"
57-
WrapperCssClass="language-selector-mobile" />
58-
<button class="navbar-toggler" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasNavbar" aria-controls="offcanvasNavbar" aria-label="Toggle navigation">
59-
<span class="navbar-toggler-icon"></span>
60-
</button>
61-
</div>
6263
</div>
6364
</nav>
6465

@@ -72,21 +73,18 @@
7273
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
7374
</div>
7475
<div class="offcanvas-body">
75-
<ul class="navbar-nav justify-content-end flex-grow-1 pe-3">
76-
<li class="nav-item">
77-
<a class="nav-link" href="#home" data-bs-dismiss="offcanvas">@WebAppLocalizer[WebAppResource.Nav_Home]</a>
78-
</li>
79-
<li class="nav-item">
80-
<a class="nav-link" href="#pricing" data-bs-dismiss="offcanvas">@WebAppLocalizer[WebAppResource.Nav_Pricing]</a>
81-
</li>
82-
<li class="nav-item">
83-
<a class="nav-link" href="#" data-bs-dismiss="offcanvas">@WebAppLocalizer[WebAppResource.Nav_Contact]</a>
84-
</li>
85-
<li class="nav-item mt-3">
86-
<a class="btn btn-outline-primary-gradient w-100 mb-2" href="/dashboard" data-bs-dismiss="offcanvas">
87-
<i class="bi bi-box-arrow-in-right"></i> @WebAppLocalizer[WebAppResource.Nav_Dashboard]
88-
</a>
89-
</li>
76+
<ul class="navbar-nav justify-content-end flex-grow-1 pe-3" id="offcanvasNavMenu">
77+
<!-- Navigation items will be cloned here by JavaScript -->
9078
</ul>
9179
</div>
9280
</div>
81+
82+
@code {
83+
protected override async Task OnAfterRenderAsync(bool firstRender)
84+
{
85+
if (firstRender)
86+
{
87+
await JSRuntime.InvokeVoidAsync("setupSingleNavMenu");
88+
}
89+
}
90+
}

src/2-Clients/WebApp/wwwroot/website/css/app.css

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ html {
2828
overflow-x: hidden;
2929
width: 100%;
3030
max-width: 100%;
31+
scroll-padding-top: 100px; /* Account for fixed navbar */
3132
}
3233

3334
body {
@@ -121,6 +122,79 @@ html {
121122
width: 100%;
122123
}
123124

125+
/* Responsive Navigation Styles */
126+
.navbar-nav-container {
127+
display: flex;
128+
align-items: center;
129+
}
130+
131+
.responsive-nav {
132+
display: flex;
133+
align-items: center;
134+
margin: 0;
135+
padding: 0;
136+
list-style: none;
137+
}
138+
139+
/* Desktop Styles (lg and up) */
140+
@media (min-width: 992px) {
141+
.navbar-nav-container {
142+
margin-left: auto;
143+
}
144+
145+
.responsive-nav {
146+
flex-direction: row;
147+
gap: 0.5rem;
148+
}
149+
150+
.responsive-nav .nav-item {
151+
margin: 0 0.25rem;
152+
}
153+
154+
.language-selector-desktop {
155+
margin-right: 0.5rem;
156+
}
157+
158+
.dashboard-btn {
159+
margin-left: 0.5rem;
160+
}
161+
162+
.dashboard-btn .btn {
163+
padding: 0.75rem 2rem;
164+
border-radius: 0.75rem;
165+
}
166+
}
167+
168+
/* Mobile Styles (below lg) */
169+
@media (max-width: 991.98px) {
170+
.navbar-nav-container {
171+
display: none;
172+
}
173+
174+
.responsive-nav {
175+
flex-direction: column;
176+
width: 100%;
177+
}
178+
179+
.responsive-nav .nav-item {
180+
width: 100%;
181+
margin: 0.5rem 0;
182+
}
183+
184+
.language-selector-desktop {
185+
display: none;
186+
}
187+
188+
.dashboard-btn {
189+
margin-top: 1rem;
190+
}
191+
192+
.dashboard-btn .btn {
193+
width: 100%;
194+
margin-bottom: 0.5rem;
195+
}
196+
}
197+
124198
.btn-primary-gradient {
125199
background: var(--gradient-primary);
126200
border: none;

src/2-Clients/WebApp/wwwroot/website/js/app.js

Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,68 @@
11
// Modern SaaS Website JavaScript - Super Simple Animations
2+
23
document.addEventListener('DOMContentLoaded', function() {
34

5+
// Handle navigation menu anchor link clicks using data attribute
6+
function setupScrollToSectionLinks() {
7+
const scrollLinks = document.querySelectorAll('.nav-link[data-scroll-to-section="true"]');
8+
9+
scrollLinks.forEach(link => {
10+
link.addEventListener('click', function(e) {
11+
e.preventDefault();
12+
const href = this.getAttribute('href');
13+
14+
// Skip empty hash
15+
if (href === '#') return;
16+
17+
// Update URL
18+
window.location.hash = href;
19+
20+
// Scroll to element
21+
const target = document.querySelector(href);
22+
if (target) {
23+
target.scrollIntoView({ behavior: 'smooth' });
24+
}
25+
});
26+
});
27+
}
28+
29+
// Setup scroll links when DOM is ready
30+
setupScrollToSectionLinks();
31+
32+
// Single Navigation Menu Setup - Clones nav items to offcanvas
33+
window.setupSingleNavMenu = function() {
34+
const singleNavMenu = document.getElementById('singleNavMenu');
35+
const offcanvasNavMenu = document.getElementById('offcanvasNavMenu');
36+
37+
if (singleNavMenu && offcanvasNavMenu) {
38+
// Clone the navigation items
39+
const navItems = singleNavMenu.querySelectorAll('.nav-item');
40+
41+
navItems.forEach(item => {
42+
// Skip language selector for mobile (it's already in the header)
43+
if (item.classList.contains('language-selector-desktop')) {
44+
return;
45+
}
46+
47+
const clonedItem = item.cloneNode(true);
48+
49+
// Add mobile-specific classes
50+
if (clonedItem.classList.contains('dashboard-btn')) {
51+
clonedItem.classList.add('mt-3');
52+
const btn = clonedItem.querySelector('.btn');
53+
if (btn) {
54+
btn.classList.add('w-100', 'mb-2');
55+
}
56+
}
57+
58+
offcanvasNavMenu.appendChild(clonedItem);
59+
});
60+
61+
// Setup scroll links for cloned items
62+
setupScrollToSectionLinks();
63+
}
64+
};
65+
466
// Simple animation checker - runs on every scroll
567
function checkAnimations() {
668
const elements = document.querySelectorAll('.fade-in, .slide-in-left, .slide-in-right, .scale-in');
@@ -53,21 +115,6 @@ document.addEventListener('DOMContentLoaded', function() {
53115
updateParallax();
54116
}
55117

56-
// Smooth scrolling for anchor links (exclude dropdown toggles)
57-
document.querySelectorAll('a[href^="#"]:not(.dropdown-toggle):not([data-bs-toggle])').forEach(anchor => {
58-
anchor.addEventListener('click', function (e) {
59-
e.preventDefault();
60-
const target = document.querySelector(this.getAttribute('href'));
61-
if (target) {
62-
const offsetTop = target.offsetTop - 80;
63-
window.scrollTo({
64-
top: offsetTop,
65-
behavior: 'smooth'
66-
});
67-
}
68-
});
69-
});
70-
71118
// Button hover effects
72119
function addHoverEffects() {
73120
document.querySelectorAll('.btn-primary-gradient, .btn-outline-primary-gradient').forEach(button => {

0 commit comments

Comments
 (0)