feat: add page sections navigation in sidebar with smooth scroll
This commit is contained in:
@@ -100,9 +100,74 @@
|
||||
}
|
||||
}
|
||||
|
||||
.page-sections {
|
||||
padding: 0.5rem 0 0.5rem 1rem;
|
||||
background: rgba(45, 106, 79, 0.05);
|
||||
border-left: 2px solid var(--primary);
|
||||
margin-left: 1.5rem;
|
||||
margin-top: 0.5rem;
|
||||
animation: slideDown 0.3s ease-out;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
max-height: 0;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
max-height: 1000px;
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.section-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
width: 100%;
|
||||
padding: 0.625rem 1rem;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text);
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
font-size: 0.85rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.section-link:hover {
|
||||
background: rgba(45, 106, 79, 0.1);
|
||||
color: var(--primary-dark);
|
||||
padding-left: 1.25rem;
|
||||
}
|
||||
|
||||
.section-link i {
|
||||
width: 16px;
|
||||
text-align: center;
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.section-link span {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.sidebar {
|
||||
top: 60px;
|
||||
width: 260px;
|
||||
}
|
||||
|
||||
.page-sections {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import React from 'react'
|
||||
import { NavLink } from 'react-router-dom'
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { NavLink, useLocation } from 'react-router-dom'
|
||||
import './Sidebar.css'
|
||||
|
||||
function Sidebar({ isOpen, closeSidebar }) {
|
||||
const location = useLocation()
|
||||
const [pageSections, setPageSections] = useState([])
|
||||
const [expandedPage, setExpandedPage] = useState(null)
|
||||
|
||||
const menuItems = [
|
||||
{ path: '/', icon: 'fa-home', label: 'Accueil' },
|
||||
{
|
||||
@@ -27,6 +31,66 @@ function Sidebar({ isOpen, closeSidebar }) {
|
||||
{ path: '/conclusion', icon: 'fa-flag-checkered', label: 'Conclusion' },
|
||||
]
|
||||
|
||||
useEffect(() => {
|
||||
setPageSections([])
|
||||
setExpandedPage(location.pathname)
|
||||
|
||||
const extractSections = () => {
|
||||
const sections = []
|
||||
const headers = document.querySelectorAll('.content-section h2, .intro h2, .radar-section h2, .comparison-table h2, .synthesis h2')
|
||||
|
||||
headers.forEach((header, index) => {
|
||||
const text = header.textContent
|
||||
const icon = header.querySelector('i')
|
||||
const iconClass = icon ? icon.className.split(' ').find(c => c.startsWith('fa-')) : 'fa-circle'
|
||||
|
||||
if (!header.id) {
|
||||
header.id = `section-${index}`
|
||||
}
|
||||
|
||||
sections.push({
|
||||
id: header.id,
|
||||
text: text.replace(/[^\w\s\u00C0-\u017F]/g, '').trim(),
|
||||
icon: iconClass
|
||||
})
|
||||
})
|
||||
|
||||
if (sections.length > 0) {
|
||||
setPageSections(sections)
|
||||
}
|
||||
}
|
||||
|
||||
const timer = setTimeout(extractSections, 150)
|
||||
return () => clearTimeout(timer)
|
||||
}, [location.pathname])
|
||||
|
||||
const scrollToSection = (sectionId) => {
|
||||
const element = document.getElementById(sectionId)
|
||||
if (element) {
|
||||
const headerHeight = 70
|
||||
const elementPosition = element.getBoundingClientRect().top + window.pageYOffset
|
||||
const offsetPosition = elementPosition - headerHeight - 20
|
||||
|
||||
window.scrollTo({
|
||||
top: offsetPosition,
|
||||
behavior: 'smooth'
|
||||
})
|
||||
|
||||
if (window.innerWidth < 1024) {
|
||||
closeSidebar()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const toggleSections = (path) => {
|
||||
if (expandedPage === path) {
|
||||
setExpandedPage(null)
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' })
|
||||
} else {
|
||||
setExpandedPage(path)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={`sidebar-overlay ${isOpen ? 'active' : ''}`} onClick={closeSidebar}></div>
|
||||
@@ -35,14 +99,40 @@ function Sidebar({ isOpen, closeSidebar }) {
|
||||
{menuItems.map((item, index) => (
|
||||
<div key={index} className="menu-item">
|
||||
{item.path ? (
|
||||
<>
|
||||
<NavLink
|
||||
to={item.path}
|
||||
className={({ isActive }) => `nav-link ${isActive ? 'active' : ''}`}
|
||||
onClick={closeSidebar}
|
||||
onClick={(e) => {
|
||||
if (location.pathname === item.path) {
|
||||
e.preventDefault()
|
||||
toggleSections(item.path)
|
||||
} else {
|
||||
closeSidebar()
|
||||
}
|
||||
}}
|
||||
>
|
||||
<i className={`fas ${item.icon}`}></i>
|
||||
<span>{item.label}</span>
|
||||
{location.pathname === item.path && pageSections.length > 0 && (
|
||||
<i className={`fas fa-chevron-${expandedPage === item.path ? 'up' : 'down'}`} style={{marginLeft: 'auto', fontSize: '0.8rem'}}></i>
|
||||
)}
|
||||
</NavLink>
|
||||
{location.pathname === item.path && expandedPage === item.path && pageSections.length > 0 && (
|
||||
<div className="page-sections">
|
||||
{pageSections.map((section, sIdx) => (
|
||||
<button
|
||||
key={sIdx}
|
||||
className="section-link"
|
||||
onClick={() => scrollToSection(section.id)}
|
||||
>
|
||||
<i className={`fas ${section.icon}`}></i>
|
||||
<span>{section.text}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="nav-section">
|
||||
@@ -51,15 +141,40 @@ function Sidebar({ isOpen, closeSidebar }) {
|
||||
</div>
|
||||
<div className="sub-menu">
|
||||
{item.children.map((child, childIndex) => (
|
||||
<React.Fragment key={childIndex}>
|
||||
<NavLink
|
||||
key={childIndex}
|
||||
to={child.path}
|
||||
className={({ isActive }) => `nav-link sub-link ${isActive ? 'active' : ''}`}
|
||||
onClick={closeSidebar}
|
||||
onClick={(e) => {
|
||||
if (location.pathname === child.path) {
|
||||
e.preventDefault()
|
||||
toggleSections(child.path)
|
||||
} else {
|
||||
closeSidebar()
|
||||
}
|
||||
}}
|
||||
>
|
||||
<i className={`fas ${child.icon}`}></i>
|
||||
<span>{child.label}</span>
|
||||
{location.pathname === child.path && pageSections.length > 0 && (
|
||||
<i className={`fas fa-chevron-${expandedPage === child.path ? 'up' : 'down'}`} style={{marginLeft: 'auto', fontSize: '0.8rem'}}></i>
|
||||
)}
|
||||
</NavLink>
|
||||
{location.pathname === child.path && expandedPage === child.path && pageSections.length > 0 && (
|
||||
<div className="page-sections">
|
||||
{pageSections.map((section, sIdx) => (
|
||||
<button
|
||||
key={sIdx}
|
||||
className="section-link"
|
||||
onClick={() => scrollToSection(section.id)}
|
||||
>
|
||||
<i className={`fas ${section.icon}`}></i>
|
||||
<span>{section.text}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -314,13 +314,88 @@ function WAVE() {
|
||||
</section>
|
||||
|
||||
<section className="content-section">
|
||||
<h2><i className="fas fa-clipboard-list"></i> Synthèse de l'audit</h2>
|
||||
<h2><i className="fas fa-file-alt"></i> Rapport de synthèse</h2>
|
||||
<div style={{padding: '2rem', background: 'var(--bg)', borderRadius: '8px', lineHeight: '1.8'}}>
|
||||
<p>
|
||||
L'audit d'accessibilité du site ville-de-chauray.fr réalisé avec l'outil WAVE révèle des problématiques
|
||||
importantes qui impactent significativement l'expérience des utilisateurs en situation de handicap.
|
||||
Sur les 29 erreurs critiques détectées, trois catégories majeures se dégagent et nécessitent une
|
||||
attention immédiate.
|
||||
</p>
|
||||
|
||||
<h3 style={{marginTop: '1.5rem'}}>Constat principal</h3>
|
||||
<p>
|
||||
Le site présente 29 erreurs d'accessibilité, 54 alertes nécessitant une vérification humaine,
|
||||
et surtout 326 erreurs de contraste. Ces chiffres indiquent que le site n'est actuellement pas
|
||||
conforme aux standards d'accessibilité WCAG 2.1 niveau AA, ce qui exclut de facto une partie
|
||||
significative des utilisateurs potentiels.
|
||||
</p>
|
||||
|
||||
<h3 style={{marginTop: '1.5rem'}}>Problèmes majeurs identifiés</h3>
|
||||
<p>
|
||||
<strong>Premièrement</strong>, 23 images sur le site ne possèdent pas d'attribut alt, rendant leur
|
||||
contenu totalement inaccessible aux personnes utilisant des lecteurs d'écran. Cette lacune concerne
|
||||
principalement les logos de partenaires et certaines images illustratives, privant ainsi les utilisateurs
|
||||
malvoyants d'informations potentiellement importantes.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Deuxièmement</strong>, les 326 erreurs de contraste constituent un obstacle majeur pour les
|
||||
personnes malvoyantes, daltoniennes ou consultant le site dans des conditions de luminosité difficiles.
|
||||
Le footer notamment présente un ratio de contraste de seulement 2.3:1 (liens gris #999 sur fond #f5f5f5),
|
||||
bien en deçà du minimum requis de 4.5:1 pour le texte normal. Cela concerne environ 8% de la population
|
||||
masculine atteinte de daltonisme, sans compter les seniors et les personnes malvoyantes.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Troisièmement</strong>, trois formulaires présentent des champs sans label correctement associé,
|
||||
ce qui rend leur utilisation problématique pour les lecteurs d'écran et complique la navigation au clavier.
|
||||
Cette erreur impacte directement la capacité des utilisateurs à contacter la mairie ou à utiliser les
|
||||
services en ligne proposés.
|
||||
</p>
|
||||
|
||||
<h3 style={{marginTop: '1.5rem'}}>Plan de correction</h3>
|
||||
<p>
|
||||
Les corrections nécessaires sont heureusement relativement simples à mettre en œuvre. L'ajout des
|
||||
attributs alt aux images représente environ 2 heures de travail. La correction des contrastes,
|
||||
principalement via l'ajustement des couleurs CSS, nécessite 1 heure. L'association des labels aux
|
||||
formulaires demande également 1 heure. Au total, les corrections critiques peuvent être réalisées
|
||||
en 5h30, un investissement modeste au regard de l'amélioration significative de l'accessibilité.
|
||||
</p>
|
||||
|
||||
<h3 style={{marginTop: '1.5rem'}}>Impact attendu</h3>
|
||||
<p>
|
||||
La mise en œuvre de ces corrections permettra au site de devenir accessible à environ 15-20%
|
||||
d'utilisateurs supplémentaires, incluant les personnes en situation de handicap visuel, moteur
|
||||
ou cognitif. Au-delà de l'aspect légal (conformité à la loi du 11 février 2005), ces améliorations
|
||||
bénéficieront à l'ensemble des utilisateurs : meilleure lisibilité pour tous, navigation plus intuitive,
|
||||
et amélioration du référencement naturel (SEO).
|
||||
</p>
|
||||
|
||||
<h3 style={{marginTop: '1.5rem'}}>Limites de l'audit WAVE</h3>
|
||||
<p>
|
||||
Il convient de noter que WAVE, bien qu'excellent pour identifier les erreurs techniques, ne peut
|
||||
détecter tous les problèmes d'accessibilité. La pertinence des textes alternatifs, la cohérence
|
||||
globale du parcours utilisateur, et l'expérience réelle avec les technologies d'assistance nécessitent
|
||||
une validation humaine complémentaire. Un test avec un lecteur d'écran (NVDA, JAWS ou VoiceOver)
|
||||
et une navigation complète au clavier restent indispensables pour garantir une accessibilité optimale.
|
||||
</p>
|
||||
|
||||
<p style={{marginTop: '1.5rem', fontStyle: 'italic'}}>
|
||||
En conclusion, cet audit WAVE constitue une première étape essentielle dans la mise en conformité
|
||||
du site ville-de-chauray.fr. Les corrections identifiées sont prioritaires et réalisables rapidement,
|
||||
ouvrant la voie à une démarche d'accessibilité plus complète incluant un audit RGAA complet et des
|
||||
tests utilisateurs avec des personnes en situation de handicap.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="content-section">
|
||||
<h2><i className="fas fa-clipboard-list"></i> Synthèse technique</h2>
|
||||
|
||||
<div style={{marginBottom: '2rem'}}>
|
||||
<h3>Problèmes majeurs identifiés</h3>
|
||||
<ul>
|
||||
<li><strong>8 images</strong> sans alternative textuelle (bloquant pour lecteurs d'écran)</li>
|
||||
<li><strong>5 contrastes insuffisants</strong> (illisible pour malvoyants)</li>
|
||||
<li><strong>23 images</strong> sans alternative textuelle (bloquant pour lecteurs d'écran)</li>
|
||||
<li><strong>326 contrastes insuffisants</strong> (illisible pour malvoyants)</li>
|
||||
<li><strong>3 formulaires</strong> mal structurés (labels manquants ou non associés)</li>
|
||||
<li><strong>Structure de titres incohérente</strong> (saut de h2 à h4)</li>
|
||||
<li><strong>Liens ambigus</strong> ("Cliquez ici" sans contexte)</li>
|
||||
|
||||
@@ -10,6 +10,29 @@ function WebsiteCarbon() {
|
||||
<p className="subtitle">Décryptage technique et pédagogique</p>
|
||||
</header>
|
||||
|
||||
<section className="content-section">
|
||||
<h2><i className="fas fa-book-open"></i> Introduction</h2>
|
||||
<div style={{padding: '1.5rem', background: 'var(--bg)', borderRadius: '8px', lineHeight: '1.8'}}>
|
||||
<p>
|
||||
Dans un contexte où le numérique représente 4% des émissions mondiales de gaz à effet de serre,
|
||||
il devient crucial de mesurer et comprendre l'impact environnemental de nos sites web.
|
||||
Website Carbon Calculator s'inscrit dans cette démarche de sensibilisation et d'évaluation.
|
||||
</p>
|
||||
<p>
|
||||
Cet outil en ligne, créé par l'agence britannique Wholegrain Digital, permet d'estimer rapidement
|
||||
l'empreinte carbone d'une page web en analysant son poids et en le convertissant en émissions de CO₂.
|
||||
Simple d'utilisation et pédagogique, il constitue une porte d'entrée idéale pour sensibiliser
|
||||
développeurs, designers et décideurs aux enjeux de l'écoconception web.
|
||||
</p>
|
||||
<p>
|
||||
Ce document vise à décrypter le fonctionnement technique de Website Carbon Calculator,
|
||||
à en comprendre les forces et les limites, et à proposer une approche pédagogique pour
|
||||
l'utiliser efficacement dans une démarche d'écoconception. Nous analyserons également
|
||||
un cas pratique sur le site ville-de-chauray.fr pour illustrer concrètement l'interprétation des résultats.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="content-section">
|
||||
<h2><i className="fas fa-info-circle"></i> Présentation générale</h2>
|
||||
<div className="info-card">
|
||||
@@ -352,6 +375,54 @@ function WebsiteCarbon() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="content-section">
|
||||
<h2><i className="fas fa-file-alt"></i> Conclusion</h2>
|
||||
<div style={{padding: '2rem', background: 'var(--bg)', borderRadius: '8px', lineHeight: '1.8'}}>
|
||||
<p>
|
||||
Website Carbon Calculator se révèle être un outil précieux dans l'arsenal de l'écoconception web,
|
||||
particulièrement adapté à une première approche de sensibilisation. Sa force réside dans sa simplicité
|
||||
d'utilisation et sa capacité à traduire des données techniques (poids de page) en indicateurs
|
||||
compréhensibles par tous (émissions de CO₂, équivalences concrètes).
|
||||
</p>
|
||||
|
||||
<h3 style={{marginTop: '1.5rem'}}>Forces principales</h3>
|
||||
<p>
|
||||
L'outil excelle dans sa dimension pédagogique : en quelques secondes, il permet de visualiser
|
||||
l'impact environnemental d'un site et de le comparer à la moyenne du web. Les équivalences proposées
|
||||
(kilomètres en voiture, tasses de thé, arbres nécessaires) rendent l'information tangible et facilitent
|
||||
la prise de conscience. L'intégration de la vérification de l'hébergement vert via la Green Web Foundation
|
||||
ajoute une dimension actionnable immédiate.
|
||||
</p>
|
||||
|
||||
<h3 style={{marginTop: '1.5rem'}}>Limites à considérer</h3>
|
||||
<p>
|
||||
Cependant, il convient de rester conscient des limites méthodologiques de l'outil. Le coefficient
|
||||
de 0.81 kWh/Go est une moyenne qui ne reflète pas la diversité des contextes d'utilisation
|
||||
(type de réseau, distance géographique, appareil utilisé). L'analyse se limite à une seule page
|
||||
et ne prend pas en compte le parcours utilisateur complet, ni la consommation énergétique réelle
|
||||
du serveur. Les valeurs obtenues sont donc des estimations approximatives, utiles pour la comparaison
|
||||
mais non scientifiquement précises.
|
||||
</p>
|
||||
|
||||
<h3 style={{marginTop: '1.5rem'}}>Recommandations d'usage</h3>
|
||||
<p>
|
||||
Pour une démarche d'écoconception complète, Website Carbon Calculator doit être utilisé en
|
||||
complémentarité avec d'autres outils. EcoIndex apportera une méthodologie plus scientifique
|
||||
en intégrant le nombre de requêtes et la complexité du DOM. Lighthouse permettra d'obtenir
|
||||
des recommandations techniques précises pour optimiser les performances. GreenIT-Analysis
|
||||
offrira un référentiel de 115 bonnes pratiques à appliquer.
|
||||
</p>
|
||||
|
||||
<p style={{marginTop: '1.5rem'}}>
|
||||
En conclusion, Website Carbon Calculator remplit parfaitement son rôle d'outil de sensibilisation
|
||||
et de première évaluation. Il constitue un excellent point de départ pour engager une démarche
|
||||
d'écoconception, à condition de l'intégrer dans une approche plus globale combinant plusieurs
|
||||
outils et méthodologies. Son utilisation régulière permet de suivre l'évolution de l'empreinte
|
||||
carbone d'un site et de mesurer l'impact des optimisations mises en place.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="conclusion-banner">
|
||||
<i className="fas fa-check-circle"></i>
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user