feat(images): add lazy loading for images

This commit is contained in:
2025-02-04 19:18:32 +01:00
parent e1a670cf6d
commit 8e7e3457b0
10 changed files with 295 additions and 151 deletions

38
package-lock.json generated
View File

@@ -17,6 +17,7 @@
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-icons": "^5.4.0", "react-icons": "^5.4.0",
"react-lazy-load-image-component": "^1.6.3",
"react-router-dom": "^7.0.2" "react-router-dom": "^7.0.2"
}, },
"devDependencies": { "devDependencies": {
@@ -24,6 +25,7 @@
"@simonsmith/cypress-image-snapshot": "^9.1.0", "@simonsmith/cypress-image-snapshot": "^9.1.0",
"@types/react": "^18.3.12", "@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1", "@types/react-dom": "^18.3.1",
"@types/react-lazy-load-image-component": "^1.6.4",
"@vitejs/plugin-react": "^4.3.4", "@vitejs/plugin-react": "^4.3.4",
"eslint": "^9.18.0", "eslint": "^9.18.0",
"eslint-plugin-react-hooks": "^5.1.0", "eslint-plugin-react-hooks": "^5.1.0",
@@ -1864,6 +1866,17 @@
"@types/react": "*" "@types/react": "*"
} }
}, },
"node_modules/@types/react-lazy-load-image-component": {
"version": "1.6.4",
"resolved": "https://registry.npmjs.org/@types/react-lazy-load-image-component/-/react-lazy-load-image-component-1.6.4.tgz",
"integrity": "sha512-8pFPeDPF4yVG4lU1/ixZidJEEDZmEOYOTYDvmIu2IAabyuv97Q7n/93nMCocHvQ7vD1czKGiW+op55D9m3MkdA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/react": "*",
"csstype": "^3.0.2"
}
},
"node_modules/@types/sinonjs__fake-timers": { "node_modules/@types/sinonjs__fake-timers": {
"version": "8.1.1", "version": "8.1.1",
"resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz", "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz",
@@ -5603,6 +5616,12 @@
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
"license": "MIT"
},
"node_modules/lodash.merge": { "node_modules/lodash.merge": {
"version": "4.6.2", "version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -5615,6 +5634,12 @@
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/lodash.throttle": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
"integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==",
"license": "MIT"
},
"node_modules/log-symbols": { "node_modules/log-symbols": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
@@ -6495,6 +6520,19 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/react-lazy-load-image-component": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/react-lazy-load-image-component/-/react-lazy-load-image-component-1.6.3.tgz",
"integrity": "sha512-kdQYUDbuISF3T9El0sBLNoWrmPohqlytcG4ognLtHYjY8bZAsJ0/Ez+VaV+0QlVyUY3K6dDXkuQAz3GpvdjBkw==",
"license": "MIT",
"dependencies": {
"lodash.debounce": "^4.0.8",
"lodash.throttle": "^4.1.1"
},
"peerDependencies": {
"react": "^15.x.x || ^16.x.x || ^17.x.x || ^18.x.x || ^19.x.x"
}
},
"node_modules/react-refresh": { "node_modules/react-refresh": {
"version": "0.14.2", "version": "0.14.2",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",

View File

@@ -34,6 +34,7 @@
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-icons": "^5.4.0", "react-icons": "^5.4.0",
"react-lazy-load-image-component": "^1.6.3",
"react-router-dom": "^7.0.2" "react-router-dom": "^7.0.2"
}, },
"devDependencies": { "devDependencies": {
@@ -41,6 +42,7 @@
"@simonsmith/cypress-image-snapshot": "^9.1.0", "@simonsmith/cypress-image-snapshot": "^9.1.0",
"@types/react": "^18.3.12", "@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1", "@types/react-dom": "^18.3.1",
"@types/react-lazy-load-image-component": "^1.6.4",
"@vitejs/plugin-react": "^4.3.4", "@vitejs/plugin-react": "^4.3.4",
"eslint": "^9.18.0", "eslint": "^9.18.0",
"eslint-plugin-react-hooks": "^5.1.0", "eslint-plugin-react-hooks": "^5.1.0",

View File

@@ -1,21 +1,31 @@
import './banner.css'; import './banner.css';
import { LazyLoadImage } from 'react-lazy-load-image-component';
interface BannerProps { interface BannerProps {
image: { src: string, alt?: string }; image: { src: string; alt?: string };
header: string; header: string;
children?: React.ReactNode; children?: React.ReactNode;
label?: string; label?: string;
} }
export const Banner: React.FC<BannerProps> = ({ image, header, children, label }) => { export const Banner: React.FC<BannerProps> = ({
return ( image,
<div className={'banner'}> header,
<img className={'banner-image'} src={image.src} alt={image.alt} /> children,
<div className={'banner-content'}> label,
<h3 className={'banner-header'}>{header}</h3> }) => {
<p className={'banner-text'}>{children}</p> return (
{ label && <span className={'banner-label'}>{label}</span> } <div className={'banner'}>
</div> <LazyLoadImage
</div> className={'banner-image'}
); src={image.src}
} alt={image.alt}
/>
<div className={'banner-content'}>
<h3 className={'banner-header'}>{header}</h3>
<p className={'banner-text'}>{children}</p>
{label && <span className={'banner-label'}>{label}</span>}
</div>
</div>
);
};

View File

@@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import { NavArrowLeft, NavArrowRight } from 'iconoir-react'; import { NavArrowLeft, NavArrowRight } from 'iconoir-react';
import './carousel.css'; import './carousel.css';
import { LazyLoadImage } from 'react-lazy-load-image-component';
interface CarouselProps { interface CarouselProps {
carousel: CarouselImageProps[]; carousel: CarouselImageProps[];
@@ -78,7 +79,7 @@ export const Carousel: React.FC<CarouselProps> = ({ carousel }) => {
<div className={'carousel-slider'} ref={carouselSlider}> <div className={'carousel-slider'} ref={carouselSlider}>
{[...carousel, ...carousel].map((image, index) => ( {[...carousel, ...carousel].map((image, index) => (
<div key={index} className={'slide'}> <div key={index} className={'slide'}>
<img <LazyLoadImage
key={index} key={index}
className={`slide-image ${index === currentIndex ? 'active' : ''}`} className={`slide-image ${index === currentIndex ? 'active' : ''}`}
src={image.image} src={image.image}

View File

@@ -1,33 +1,47 @@
interface PartnerProps { interface PartnerProps {
name: string; name: string;
logo: string; logo: string;
texts: React.ReactNode[]; texts: React.ReactNode[];
photos: string[]; photos: string[];
link: string; link: string;
} }
import { ArrowUpRight } from 'iconoir-react'; import { ArrowUpRight } from 'iconoir-react';
import { LazyLoadImage } from 'react-lazy-load-image-component';
import './partner.css'; import './partner.css';
export const Partner: React.FC<PartnerProps> = ({ name, logo, texts, photos, link }) => ( export const Partner: React.FC<PartnerProps> = ({
<div className={'partner'}> name,
<div className={'partner-infos'}> logo,
<img className={'partner-logo'} src={logo} alt={name} /> texts,
<div className={'partner-content'}> photos,
<h2 className={'partner-name'}>{name}</h2> link,
{texts.map((text, index) => ( }) => (
<p key={index} className={'partner-text'}>{text}</p> <div className={'partner'}>
))} <div className={'partner-infos'}>
</div> <LazyLoadImage className={'partner-logo'} src={logo} alt={name} />
<a className={'partner-link'} target={'_blank'} href={link}> <div className={'partner-content'}>
<ArrowUpRight /> <h2 className={'partner-name'}>{name}</h2>
Accéder au site {texts.map((text, index) => (
</a> <p key={index} className={'partner-text'}>
</div> {text}
<div className={'partner-photos'}> </p>
{photos.map((photo, index) => ( ))}
<img key={index} className={'partner-preview'} src={photo} alt={`Photo ${index + 1} - ${name}`} /> </div>
))} <a className={'partner-link'} target={'_blank'} href={link}>
</div> <ArrowUpRight />
Accéder au site
</a>
</div> </div>
); <div className={'partner-photos'}>
{photos.map((photo, index) => (
<img
key={index}
className={'partner-preview'}
src={photo}
alt={`Photo ${index + 1} - ${name}`}
/>
))}
</div>
</div>
);

View File

@@ -1,51 +1,56 @@
import React from "react"; import React from 'react';
import "./team.css"; import './team.css';
import { LazyLoadImage } from 'react-lazy-load-image-component';
interface TeamMemberProps { interface TeamMemberProps {
name: string; name: string;
role: string; role: string;
image: string; image: string;
} }
interface TeamGroupProps { interface TeamGroupProps {
title: string; title: string;
members: TeamMemberProps[]; members: TeamMemberProps[];
} }
interface TeamProps { interface TeamProps {
groups: TeamGroupProps[]; groups: TeamGroupProps[];
} }
const TeamMember: React.FC<TeamMemberProps> = ({ name, role, image }) => { const TeamMember: React.FC<TeamMemberProps> = ({ name, role, image }) => {
return ( return (
<div className='team-member'> <div className="team-member">
<div className='team-member-image-container'> <div className="team-member-image-container">
<img className='team-member-image' src={image} alt={name} /> <LazyLoadImage className="team-member-image" src={image} alt={name} />
</div> </div>
<div className='team-member-content'> <div className="team-member-content">
<h5 className='team-member-name'>{name}</h5> <h5 className="team-member-name">{name}</h5>
<p className='team-member-role'>{role}</p> <p className="team-member-role">{role}</p>
</div> </div>
</div> </div>
); );
} };
const TeamGroup: React.FC<TeamGroupProps> = ({ title, members }) => { const TeamGroup: React.FC<TeamGroupProps> = ({ title, members }) => {
return ( return (
<div className='team-group'> <div className="team-group">
<h3 className='team-group-title'>{title}</h3> <h3 className="team-group-title">{title}</h3>
<div className='team-group-list'> <div className="team-group-list">
{members.map((member, index) => <TeamMember key={index} {...member} />)} {members.map((member, index) => (
</div> <TeamMember key={index} {...member} />
</div> ))}
); </div>
} </div>
);
};
export const Team: React.FC<TeamProps> = ({ groups }) => { export const Team: React.FC<TeamProps> = ({ groups }) => {
return ( return (
<div className={"team"}> <div className={'team'}>
{groups.map((group, index) => <TeamGroup key={index} {...group} />)} {groups.map((group, index) => (
</div> <TeamGroup key={index} {...group} />
); ))}
} </div>
);
};

View File

@@ -1,34 +1,37 @@
import React from "react"; import React from 'react';
import './timeline.css'; import './timeline.css';
import { LazyLoadImage } from 'react-lazy-load-image-component';
interface StatCardProps { interface StatCardProps {
type: "stat"; type: 'stat';
data: string; data: string;
label: string; label: string;
}; }
interface ImageCardProps { interface ImageCardProps {
type: "image"; type: 'image';
src: string; src: string;
alt: string; alt: string;
fit: "contain" | "cover"; fit: 'contain' | 'cover';
}; }
interface TimelineProjectProps { interface TimelineProjectProps {
title: string; title: string;
date: string; date: string;
banner?: string; banner?: string;
paragraphs: React.ReactNode[]; paragraphs: React.ReactNode[];
cards?: (StatCardProps|ImageCardProps)[]; cards?: (StatCardProps | ImageCardProps)[];
}; }
interface TimelineProps { interface TimelineProps {
projects: TimelineProjectProps[]; projects: TimelineProjectProps[];
}; }
const TimelineProjectCard: React.FC<StatCardProps | ImageCardProps> = (props) => { const TimelineProjectCard: React.FC<StatCardProps | ImageCardProps> = (
if (props.type === "stat") { props
) => {
if (props.type === 'stat') {
const { data, label } = props as StatCardProps; const { data, label } = props as StatCardProps;
return ( return (
<div className={'timeline-project-card card_stat'}> <div className={'timeline-project-card card_stat'}>
@@ -36,46 +39,63 @@ const TimelineProjectCard: React.FC<StatCardProps | ImageCardProps> = (props) =>
<p className={'timeline-project-card-subtext'}>{label}</p> <p className={'timeline-project-card-subtext'}>{label}</p>
</div> </div>
); );
} else if (props.type === "image") { } else if (props.type === 'image') {
const { src, alt, fit } = props as ImageCardProps; const { src, alt, fit } = props as ImageCardProps;
return ( return (
<div className={`timeline-project-card card_image_${fit}`}> <div className={`timeline-project-card card_image_${fit}`}>
<img className={"timeline-project-card-image"} src={src} alt={alt} /> <LazyLoadImage
className={'timeline-project-card-image'}
src={src}
alt={alt}
/>
</div> </div>
); );
} }
}; };
export const TimelineProject: React.FC<TimelineProjectProps> = ({ title, banner, date, paragraphs, cards }) => { export const TimelineProject: React.FC<TimelineProjectProps> = ({
title,
banner,
date,
paragraphs,
cards,
}) => {
return ( return (
<div className={'timeline-project'}> <div className={'timeline-project'}>
<div className={'timeline-project-content'}> <div className={'timeline-project-content'}>
<h3 className={'timeline-project-title'}>{title}</h3> <h3 className={'timeline-project-title'}>{title}</h3>
<p className={'timeline-project-date'}>{date}</p> <p className={'timeline-project-date'}>{date}</p>
</div> </div>
{ banner && <img className={'timeline-project-banner'} src={banner} alt={title} /> } {banner && (
<LazyLoadImage
className={'timeline-project-banner'}
src={banner}
alt={title}
/>
)}
<div className={'timeline-project-description'}> <div className={'timeline-project-description'}>
{ paragraphs.map((paragraph, i) => <p className={'timeline-project-paragraph'} key={i}>{paragraph}</p>) } {paragraphs.map((paragraph, i) => (
<p className={'timeline-project-paragraph'} key={i}>
{paragraph}
</p>
))}
</div> </div>
<div className={'timeline-project-cards'}> <div className={'timeline-project-cards'}>
{ cards && cards.map((card, i) => <TimelineProjectCard key={i} {...card} />) } {cards &&
cards.map((card, i) => <TimelineProjectCard key={i} {...card} />)}
</div> </div>
</div> </div>
); );
} };
export const Timeline: React.FC<TimelineProps> = ({ projects }) => { export const Timeline: React.FC<TimelineProps> = ({ projects }) => {
return ( return (
<div className={'timeline'}> <div className={'timeline'}>
<h4 className={'timeline-tag'}>Aujourd'hui</h4> <h4 className={'timeline-tag'}>Aujourd'hui</h4>
{ {projects.map((project, i) => {
projects.map((project, i) => { return <TimelineProject key={i} {...project} />;
return ( })}
<TimelineProject key={i} {...project} />
);
})
}
<h4 className={'timeline-tag'}>Il y a quelques temps</h4> <h4 className={'timeline-tag'}>Il y a quelques temps</h4>
</div> </div>
); );
} };

View File

@@ -2,6 +2,7 @@ import React from 'react';
import './footer.css'; import './footer.css';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { LazyLoadImage } from 'react-lazy-load-image-component';
interface FooterLinkProps { interface FooterLinkProps {
text: string; text: string;
@@ -9,11 +10,15 @@ interface FooterLinkProps {
target?: string; target?: string;
} }
const FooterLink: React.FC<FooterLinkProps> = ({ text, link, target="_self" }) => { const FooterLink: React.FC<FooterLinkProps> = ({
text,
link,
target = '_self',
}) => {
return ( return (
<Link to={link} className={`footer-link`} target={target}> <Link to={link} className={`footer-link`} target={target}>
{text} {text}
</Link> </Link>
); );
}; };
@@ -21,35 +26,61 @@ export const Footer: React.FC = () => {
return ( return (
<footer className={'footer'}> <footer className={'footer'}>
<div className={'footer-content'}> <div className={'footer-content'}>
<img className={'footer-logo'} src={'https://r2.modelec.club/logo.png'} alt={'Modelec'} /> <LazyLoadImage
className={'footer-logo'}
src={'https://r2.modelec.club/logo.png'}
alt={'Modelec'}
/>
<div className={'footer-part'}> <div className={'footer-part'}>
<h3 className={'footer-title'}>Plan du site</h3> <h3 className={'footer-title'}>Plan du site</h3>
<div className={'footer-links'}> <div className={'footer-links'}>
{[ {[
{ text: 'Accueil', link: '/' }, { text: 'Accueil', link: '/' },
{ text: 'Projets', link: '/projets/' }, { text: 'Projets', link: '/projets/' },
/* /*
{ text: 'Matériels', link: '/materiels/' }, { text: 'Matériels', link: '/materiels/' },
{ text: 'Photos', link: '/photos/' }, { text: 'Photos', link: '/photos/' },
*/ */
{ text: 'Partenaires', link: '/partenaires/' }, { text: 'Partenaires', link: '/partenaires/' },
{ text: 'Nous contacter', link: '/contact/' }, { text: 'Nous contacter', link: '/contact/' },
].map((link) => <FooterLink key={link.text} {...link} />)} ].map((link) => (
<FooterLink key={link.text} {...link} />
))}
</div> </div>
</div> </div>
<div className={'footer-part'}> <div className={'footer-part'}>
<h3 className={'footer-title'}>Nos réseaux</h3> <h3 className={'footer-title'}>Nos réseaux</h3>
<div className={'footer-links'}> <div className={'footer-links'}>
{[ {[
{ text: 'Instagram', link: 'https://www.instagram.com/modelec_isen', target: '_blank' }, {
{ text: 'Youtube', link: 'https://www.youtube.com/@Modelec-ISEN', target: '_blank' }, text: 'Instagram',
{ text: 'Github', link: 'https://www.github.com/modelec', target: '_blank' }, link: 'https://www.instagram.com/modelec_isen',
{ text: 'Mail', link: 'mailto:contact@modelec.club', target: '_blank' }, target: '_blank',
].map((link) => <FooterLink key={link.text} {...link} />)} },
{
text: 'Youtube',
link: 'https://www.youtube.com/@Modelec-ISEN',
target: '_blank',
},
{
text: 'Github',
link: 'https://www.github.com/modelec',
target: '_blank',
},
{
text: 'Mail',
link: 'mailto:contact@modelec.club',
target: '_blank',
},
].map((link) => (
<FooterLink key={link.text} {...link} />
))}
</div> </div>
</div> </div>
</div> </div>
<span className={'footer-credits'}>© 2024 Modelec ISEN Nantes. Site réalisé par AppenISEN.</span> <span className={'footer-credits'}>
© 2024 Modelec ISEN Nantes. Site réalisé par AppenISEN.
</span>
</footer> </footer>
); );
}; };

View File

@@ -5,6 +5,7 @@ import { Menu } from 'iconoir-react';
import './navbar.css'; import './navbar.css';
import { useWindowsSize } from '../../hooks/useWindowsSize'; import { useWindowsSize } from '../../hooks/useWindowsSize';
import { Link, useLocation } from 'react-router-dom'; import { Link, useLocation } from 'react-router-dom';
import { LazyLoadImage } from 'react-lazy-load-image-component';
interface NavbarLinkProps { interface NavbarLinkProps {
text: string; text: string;
@@ -14,37 +15,46 @@ interface NavbarLinkProps {
const NavbarLink: React.FC<NavbarLinkProps> = ({ text, link, isActive }) => { const NavbarLink: React.FC<NavbarLinkProps> = ({ text, link, isActive }) => {
return ( return (
<Link to={link} className={`navbar-link ${isActive ? "link_active" : ""}`}> <Link to={link} className={`navbar-link ${isActive ? 'link_active' : ''}`}>
{text} {text}
</Link> </Link>
); );
}; };
const MobileNavbarLink: React.FC<NavbarLinkProps> = ({ text, link, isActive }) => { const MobileNavbarLink: React.FC<NavbarLinkProps> = ({
text,
link,
isActive,
}) => {
return ( return (
<Link to={link} className={`mobileNavbar-link ${isActive ? "link_active" : ""}`}> <Link
{text} to={link}
</Link> className={`mobileNavbar-link ${isActive ? 'link_active' : ''}`}
>
{text}
</Link>
); );
}; };
export const Navbar = () => { export const Navbar = () => {
const location = useLocation(); const location = useLocation();
const pathname = location.pathname.endsWith('/') ? location.pathname : location.pathname + '/'; const pathname = location.pathname.endsWith('/')
? location.pathname
: location.pathname + '/';
const links = [ const links = [
{ text: 'Accueil', link: '/' }, { text: 'Accueil', link: '/' },
{ text: 'Projets', link: '/projets/' }, { text: 'Projets', link: '/projets/' },
/* /*
{ text: 'Matériels', link: '/materiels/' }, { text: 'Matériels', link: '/materiels/' },
{ text: 'Photos', link: '/photos/' }, { text: 'Photos', link: '/photos/' },
*/ */
{ text: 'Partenaires', link: '/partenaires/' }, { text: 'Partenaires', link: '/partenaires/' },
{ text: 'Nous contacter', link: '/contact/' }, { text: 'Nous contacter', link: '/contact/' },
] ];
const activeLink = links.findIndex((link) => link.link === pathname);
const activeLink = links.findIndex(link => link.link === pathname);
const [isMobileMenuOpen, setIsMobileMenuOpen] = React.useState(false); const [isMobileMenuOpen, setIsMobileMenuOpen] = React.useState(false);
const { width } = useWindowsSize(); const { width } = useWindowsSize();
@@ -58,43 +68,55 @@ export const Navbar = () => {
<> <>
<nav className={'navbar'}> <nav className={'navbar'}>
<Link to={'/'} className={'navbar-logo'}> <Link to={'/'} className={'navbar-logo'}>
<img <LazyLoadImage
className={'navbar-logo-img img_large'} className={'navbar-logo-img img_large'}
src={'https://r2.modelec.club/logo-full.png'} // TODO: Change to SVG logo / import it from assets src={'https://r2.modelec.club/logo-full.png'} // TODO: Change to SVG logo / import it from assets
alt={'Modelec Logo'} alt={'Modelec Logo'}
/> />
<img <LazyLoadImage
className={'navbar-logo-img img_small'} className={'navbar-logo-img img_small'}
src={'https://r2.modelec.club/logo.png'} // TODO: Change to SVG logo / import it from assets src={'https://r2.modelec.club/logo.png'} // TODO: Change to SVG logo / import it from assets
alt={'Modelec Logo'} alt={'Modelec Logo'}
/> />
</Link> </Link>
<div className={'navbar-links'}> <div className={'navbar-links'}>
{ {links.map((link, index) => (
links.map((link, index) => ( <NavbarLink
<NavbarLink key={index} text={link.text} link={link.link} isActive={activeLink == index} /> key={index}
)) text={link.text}
} link={link.link}
isActive={activeLink == index}
/>
))}
</div> </div>
<button className={'navbar-open'} onClick={() => setIsMobileMenuOpen(old => !old)}> <button
className={'navbar-open'}
onClick={() => setIsMobileMenuOpen((old) => !old)}
>
<Menu /> <Menu />
</button> </button>
{ isMobileMenuOpen && {isMobileMenuOpen && (
<nav className={'mobileNavbar'}> <nav className={'mobileNavbar'}>
<h1 className={'mobileNavbar-title'}>Se déplacer sur le site</h1> <h1 className={'mobileNavbar-title'}>Se déplacer sur le site</h1>
<div className={'mobileNavbar-links'}> <div className={'mobileNavbar-links'}>
{ {links.map((link, index) => (
links.map((link, index) => ( <MobileNavbarLink
<MobileNavbarLink key={index} text={link.text} link={link.link} isActive={activeLink == index} /> key={index}
)) text={link.text}
} link={link.link}
isActive={activeLink == index}
/>
))}
</div> </div>
</nav> </nav>
} )}
</nav> </nav>
{ isMobileMenuOpen && {isMobileMenuOpen && (
<div className={'mobileNavbar-overlay'} onClick={() => setIsMobileMenuOpen(false)}></div> <div
} className={'mobileNavbar-overlay'}
onClick={() => setIsMobileMenuOpen(false)}
></div>
)}
</> </>
); );
}; };

View File

@@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { LazyLoadImage } from 'react-lazy-load-image-component';
import { Github, Youtube, Instagram, BookmarkBook } from 'iconoir-react'; import { Github, Youtube, Instagram, BookmarkBook } from 'iconoir-react';
@@ -84,7 +85,7 @@ const Home: React.FC = () => {
target={'_blank'} target={'_blank'}
href={'https://isen-nantes.fr'} href={'https://isen-nantes.fr'}
> >
<img <LazyLoadImage
className={'partners-partner-logo'} className={'partners-partner-logo'}
src={'https://r2.modelec.club/isen.png'} src={'https://r2.modelec.club/isen.png'}
alt={'ISEN Nantes'} alt={'ISEN Nantes'}
@@ -96,7 +97,7 @@ const Home: React.FC = () => {
target={'_blank'} target={'_blank'}
href={'https://www.tracopower.com/fr/fra'} href={'https://www.tracopower.com/fr/fra'}
> >
<img <LazyLoadImage
className={'partners-partner-logo'} className={'partners-partner-logo'}
src={'https://r2.modelec.club/tracopower.jpeg'} src={'https://r2.modelec.club/tracopower.jpeg'}
alt={'Traco Power'} alt={'Traco Power'}
@@ -108,7 +109,7 @@ const Home: React.FC = () => {
target={'_blank'} target={'_blank'}
href={'https://mercurycloud.fr'} href={'https://mercurycloud.fr'}
> >
<img <LazyLoadImage
className={'partners-partner-logo'} className={'partners-partner-logo'}
src={'https://r2.modelec.club/mercurycloud.png'} src={'https://r2.modelec.club/mercurycloud.png'}
alt={'Mercury Cloud'} alt={'Mercury Cloud'}
@@ -120,7 +121,7 @@ const Home: React.FC = () => {
target={'_blank'} target={'_blank'}
href={'https://instagram.com/odyssey_bde'} href={'https://instagram.com/odyssey_bde'}
> >
<img <LazyLoadImage
className={'partners-partner-logo'} className={'partners-partner-logo'}
src={'https://r2.modelec.club/bde.png'} src={'https://r2.modelec.club/bde.png'}
alt={'BDE Odyssey'} alt={'BDE Odyssey'}