mirror of
https://github.com/modelec/modelec.club.git
synced 2026-01-18 16:37:30 +01:00
feat(images): add lazy loading for images
This commit is contained in:
38
package-lock.json
generated
38
package-lock.json
generated
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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'}
|
||||||
|
|||||||
Reference in New Issue
Block a user