🧭 Feature: Navbar responsive avec menu mobile
Story 2.1: - Navbar sticky avec logo et liens de navigation - Menu hamburger pour mobile (< 1024px) - JavaScript vanilla pour toggle menu et effet scroll - Accessibilité complète (aria-expanded, Escape, focus) - Classes CSS nav-link et nav-link-active - Intégration dans index.php avec $currentPage Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -160,6 +160,17 @@
|
|||||||
.breadcrumb-current {
|
.breadcrumb-current {
|
||||||
@apply text-text-primary;
|
@apply text-text-primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Navigation links */
|
||||||
|
.nav-link {
|
||||||
|
@apply px-4 py-2 text-sm font-medium text-text-secondary
|
||||||
|
hover:text-text-primary transition-colors rounded-lg
|
||||||
|
hover:bg-surface-light;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link-active {
|
||||||
|
@apply text-primary bg-primary/10;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer utilities {
|
@layer utilities {
|
||||||
|
|||||||
@@ -3,4 +3,85 @@
|
|||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
console.log('Portfolio chargé');
|
console.log('Portfolio chargé');
|
||||||
|
initMobileMenu();
|
||||||
|
initNavbarScroll();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gestion du menu mobile
|
||||||
|
*/
|
||||||
|
function initMobileMenu() {
|
||||||
|
const toggle = document.getElementById('mobile-menu-toggle');
|
||||||
|
const menu = document.getElementById('mobile-menu');
|
||||||
|
|
||||||
|
if (!toggle || !menu) return;
|
||||||
|
|
||||||
|
const hamburgerIcon = toggle.querySelector('.hamburger-icon');
|
||||||
|
const closeIcon = toggle.querySelector('.close-icon');
|
||||||
|
|
||||||
|
function openMenu() {
|
||||||
|
menu.classList.remove('hidden');
|
||||||
|
menu.setAttribute('aria-hidden', 'false');
|
||||||
|
toggle.setAttribute('aria-expanded', 'true');
|
||||||
|
toggle.setAttribute('aria-label', 'Fermer le menu');
|
||||||
|
hamburgerIcon?.classList.add('hidden');
|
||||||
|
closeIcon?.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeMenu() {
|
||||||
|
menu.classList.add('hidden');
|
||||||
|
menu.setAttribute('aria-hidden', 'true');
|
||||||
|
toggle.setAttribute('aria-expanded', 'false');
|
||||||
|
toggle.setAttribute('aria-label', 'Ouvrir le menu');
|
||||||
|
hamburgerIcon?.classList.remove('hidden');
|
||||||
|
closeIcon?.classList.add('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleMenu() {
|
||||||
|
const isOpen = toggle.getAttribute('aria-expanded') === 'true';
|
||||||
|
if (isOpen) {
|
||||||
|
closeMenu();
|
||||||
|
} else {
|
||||||
|
openMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle au clic
|
||||||
|
toggle.addEventListener('click', toggleMenu);
|
||||||
|
|
||||||
|
// Fermer au clic sur un lien
|
||||||
|
menu.querySelectorAll('a').forEach(link => {
|
||||||
|
link.addEventListener('click', closeMenu);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fermer avec Escape
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Escape' && toggle.getAttribute('aria-expanded') === 'true') {
|
||||||
|
closeMenu();
|
||||||
|
toggle.focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fermer si on redimensionne vers desktop
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
if (window.innerWidth >= 1024) {
|
||||||
|
closeMenu();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Effet d'ombre au scroll
|
||||||
|
*/
|
||||||
|
function initNavbarScroll() {
|
||||||
|
const navbar = document.getElementById('navbar');
|
||||||
|
if (!navbar) return;
|
||||||
|
|
||||||
|
window.addEventListener('scroll', () => {
|
||||||
|
if (window.scrollY > 10) {
|
||||||
|
navbar.classList.add('shadow-lg');
|
||||||
|
} else {
|
||||||
|
navbar.classList.remove('shadow-lg');
|
||||||
|
}
|
||||||
|
}, { passive: true });
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ include_template('header', [
|
|||||||
'pageTitle' => 'Portfolio en construction',
|
'pageTitle' => 'Portfolio en construction',
|
||||||
'pageDescription' => 'Mon portfolio de développeur web arrive bientôt. Restez connectés !'
|
'pageDescription' => 'Mon portfolio de développeur web arrive bientôt. Restez connectés !'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
include_template('navbar', [
|
||||||
|
'currentPage' => 'home'
|
||||||
|
]);
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<main class="min-h-screen flex items-center justify-center">
|
<main class="min-h-screen flex items-center justify-center">
|
||||||
|
|||||||
84
templates/navbar.php
Normal file
84
templates/navbar.php
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Template Navbar
|
||||||
|
* Variables disponibles :
|
||||||
|
* - $currentPage (string) : identifiant de la page active ('home', 'projects', 'skills', 'about', 'contact')
|
||||||
|
*/
|
||||||
|
|
||||||
|
$currentPage = $currentPage ?? 'home';
|
||||||
|
|
||||||
|
$navLinks = [
|
||||||
|
['id' => 'home', 'label' => 'Accueil', 'url' => '/'],
|
||||||
|
['id' => 'projects', 'label' => 'Projets', 'url' => '/projets'],
|
||||||
|
['id' => 'skills', 'label' => 'Compétences', 'url' => '/competences'],
|
||||||
|
['id' => 'about', 'label' => 'Me Découvrir', 'url' => '/a-propos'],
|
||||||
|
['id' => 'contact', 'label' => 'Contact', 'url' => '/contact', 'isCta' => true],
|
||||||
|
];
|
||||||
|
?>
|
||||||
|
|
||||||
|
<header id="navbar" class="fixed top-0 left-0 right-0 z-50 bg-background/95 backdrop-blur-sm border-b border-border/50 transition-shadow duration-300">
|
||||||
|
<nav class="container-content" aria-label="Navigation principale">
|
||||||
|
<div class="flex items-center justify-between h-16 lg:h-20">
|
||||||
|
|
||||||
|
<!-- Logo -->
|
||||||
|
<a href="/" class="text-xl font-bold text-text-primary hover:text-primary transition-colors">
|
||||||
|
Portfolio
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<!-- Navigation Desktop -->
|
||||||
|
<ul class="hidden lg:flex items-center gap-1">
|
||||||
|
<?php foreach ($navLinks as $link): ?>
|
||||||
|
<?php if (!empty($link['isCta'])): ?>
|
||||||
|
<li class="ml-4">
|
||||||
|
<a href="<?= $link['url'] ?>" class="btn-primary text-sm">
|
||||||
|
<?= $link['label'] ?>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php else: ?>
|
||||||
|
<li>
|
||||||
|
<a href="<?= $link['url'] ?>"
|
||||||
|
class="nav-link <?= $currentPage === $link['id'] ? 'nav-link-active' : '' ?>">
|
||||||
|
<?= $link['label'] ?>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<!-- Bouton Hamburger Mobile -->
|
||||||
|
<button
|
||||||
|
id="mobile-menu-toggle"
|
||||||
|
class="lg:hidden p-2 text-text-primary hover:text-primary transition-colors"
|
||||||
|
aria-label="Ouvrir le menu"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-controls="mobile-menu"
|
||||||
|
>
|
||||||
|
<!-- Icône Hamburger -->
|
||||||
|
<svg class="w-6 h-6 hamburger-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
|
||||||
|
</svg>
|
||||||
|
<!-- Icône Close (hidden par défaut) -->
|
||||||
|
<svg class="w-6 h-6 close-icon hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Menu Mobile -->
|
||||||
|
<div id="mobile-menu" class="lg:hidden hidden" aria-hidden="true">
|
||||||
|
<ul class="py-4 space-y-2 border-t border-border">
|
||||||
|
<?php foreach ($navLinks as $link): ?>
|
||||||
|
<li>
|
||||||
|
<a href="<?= $link['url'] ?>"
|
||||||
|
class="block py-3 px-4 rounded-lg <?= $currentPage === $link['id'] ? 'bg-surface text-primary' : 'text-text-secondary hover:bg-surface hover:text-text-primary' ?> transition-colors">
|
||||||
|
<?= $link['label'] ?>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- Spacer pour compenser la navbar fixed -->
|
||||||
|
<div class="h-16 lg:h-20"></div>
|
||||||
Reference in New Issue
Block a user