diff --git a/package-lock.json b/package-lock.json
index 48a0ef92..de56ff35 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -34,6 +34,8 @@
"react-datepicker": "^6.1.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.48.2",
+ "react-loading-skeleton": "^3.4.0",
+ "react-multi-carousel": "^2.8.5",
"uuidv4": "^6.2.13",
"yarn": "^1.22.19",
"zod": "^3.22.4"
@@ -44,6 +46,7 @@
"@types/react": "18.0.14",
"@types/react-datepicker": "^6.0.1",
"@types/react-dom": "18.0.5",
+ "@types/react-image-gallery": "^1.2.4",
"@typescript-eslint/eslint-plugin": "^5.33.0",
"@typescript-eslint/parser": "^5.33.0",
"autoprefixer": "^10.4.7",
@@ -1456,6 +1459,15 @@
"@types/react": "*"
}
},
+ "node_modules/@types/react-image-gallery": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@types/react-image-gallery/-/react-image-gallery-1.2.4.tgz",
+ "integrity": "sha512-H0xpmT5rlSH0qiTvcUDCPDLRBi3J3Xa4COqaDqGb7ffLFpQoPAxpZdNuKCuThhFI0xJmNnMubZiD6B3kCBHtcw==",
+ "dev": true,
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
"node_modules/@types/scheduler": {
"version": "0.16.3",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
@@ -6065,6 +6077,22 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
+ "node_modules/react-loading-skeleton": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/react-loading-skeleton/-/react-loading-skeleton-3.4.0.tgz",
+ "integrity": "sha512-1oJEBc9+wn7BbkQQk7YodlYEIjgeR+GrRjD+QXkVjwZN7LGIcAFHrx4NhT7UHGBxNY1+zax3c+Fo6XQM4R7CgA==",
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/react-multi-carousel": {
+ "version": "2.8.5",
+ "resolved": "https://registry.npmjs.org/react-multi-carousel/-/react-multi-carousel-2.8.5.tgz",
+ "integrity": "sha512-C5DAvJkfzR2JK9YixZ3oyF9x6R4LW6nzTpIXrl9Oujxi4uqP9SzVVCjl+JLM3tSdqdjAx/oWZK3dTVBSR73Q+w==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/react-onclickoutside": {
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.13.0.tgz",
diff --git a/package.json b/package.json
index 86bf8a9b..b5649fbe 100644
--- a/package.json
+++ b/package.json
@@ -36,6 +36,8 @@
"react-datepicker": "^6.1.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.48.2",
+ "react-loading-skeleton": "^3.4.0",
+ "react-multi-carousel": "^2.8.5",
"uuidv4": "^6.2.13",
"yarn": "^1.22.19",
"zod": "^3.22.4"
@@ -46,6 +48,7 @@
"@types/react": "18.0.14",
"@types/react-datepicker": "^6.0.1",
"@types/react-dom": "18.0.5",
+ "@types/react-image-gallery": "^1.2.4",
"@typescript-eslint/eslint-plugin": "^5.33.0",
"@typescript-eslint/parser": "^5.33.0",
"autoprefixer": "^10.4.7",
diff --git a/postcss.config.cjs b/postcss.config.cjs
index 12a703d9..6887c826 100644
--- a/postcss.config.cjs
+++ b/postcss.config.cjs
@@ -1,5 +1,7 @@
module.exports = {
plugins: {
+ "postcss-import": {},
+ "tailwindcss/nesting": {},
tailwindcss: {},
autoprefixer: {},
},
diff --git a/src/app/public/about/page.tsx b/src/app/public/about/page.tsx
index a9fd485c..fded5386 100644
--- a/src/app/public/about/page.tsx
+++ b/src/app/public/about/page.tsx
@@ -23,7 +23,7 @@ const AboutLayout = () => {
};
}, []);
return (
-
+
Our Story
diff --git a/src/app/public/team/page.tsx b/src/app/public/team/page.tsx
index 7ad1ab25..dab40c7c 100644
--- a/src/app/public/team/page.tsx
+++ b/src/app/public/team/page.tsx
@@ -32,7 +32,7 @@ const PublicLayout = () => {
Meet TLP
-
+
The Legacy Project, Inc. (TLP) connects college students with local
elders in their community with the purpose of building strong
intergenerational relationships and documenting the life histories of
diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx
index 5e13cb53..59b6f150 100644
--- a/src/components/Navbar.tsx
+++ b/src/components/Navbar.tsx
@@ -48,7 +48,12 @@ const Navbar = () => {
return (
setDropdownVisible(false)}>
-
+
diff --git a/src/components/PhotoCarousel.tsx b/src/components/PhotoCarousel.tsx
index f25b5289..07d58706 100644
--- a/src/components/PhotoCarousel.tsx
+++ b/src/components/PhotoCarousel.tsx
@@ -1,118 +1,82 @@
-import React, { useState, useEffect, useRef } from "react";
-import Image from "next/legacy/image";
+"use client";
-type PhotoProps = {
- filePath: string;
-};
+import "src/styles/animation.css";
+import React from "react";
+import NextImage from "next/image";
+import "react-loading-skeleton/dist/skeleton.css";
+import Skeleton from "react-loading-skeleton";
+import "react-multi-carousel/lib/styles.css";
+import Carousel from "react-multi-carousel";
interface PhotoCarouselParams {
imagePaths: string[];
}
-const PhotoCarousel = ({ imagePaths }: PhotoCarouselParams) => {
- const [photos, setPhotos] = useState
([]);
- const [activeIndex, setActiveIndex] = useState(0);
- const [show, setShow] = useState(1);
- const widthRef = useRef(null);
-
- const [elemWidth, setElemWidth] = useState(0);
+const Image = ({ path }: { path: string }) => {
+ const [loaded, setLoaded] = React.useState(false);
+ const [preloaded, setPreloaded] = React.useState(false);
- useEffect(() => {
- function handleResize() {
- setElemWidth(widthRef.current ? widthRef.current.offsetWidth : 0);
- const showVal = widthRef.current
- ? Math.floor(widthRef.current.offsetWidth / 250)
- : 1;
- setShow(showVal || 1);
- }
- window.addEventListener("resize", handleResize);
- handleResize();
- return () => {
- window.removeEventListener("resize", handleResize);
- };
+ React.useEffect(() => {
+ setTimeout(() => {
+ setPreloaded(true);
+ }, 2000);
}, []);
- const carouselHeight = elemWidth / show;
- const carouselPad = 20;
+ return (
+
+ {(!preloaded || !loaded) && }
+ {
+ setLoaded(true);
+ }}
+ />
+
+ );
+};
- const nextIndex = () => {
- setActiveIndex((prevIndex) => (prevIndex + 1) % photos.length);
- };
+const PhotoCarousel = ({ imagePaths }: PhotoCarouselParams) => {
+ const photos = React.useMemo(
+ () => imagePaths.map((path) => ),
+ [imagePaths]
+ );
- const prevIndex = () => {
- setActiveIndex(
- (prevIndex) => (prevIndex - 1 + photos.length) % photos.length
- );
+ const responsive = {
+ superLargeDesktop: {
+ // the naming can be any, depends on you.
+ breakpoint: { max: 4000, min: 3000 },
+ items: 5,
+ },
+ desktop: {
+ breakpoint: { max: 3000, min: 1024 },
+ items: 4,
+ },
+ tablet: {
+ breakpoint: { max: 1024, min: 464 },
+ items: 2,
+ },
+ mobile: {
+ breakpoint: { max: 464, min: 0 },
+ items: 1,
+ },
};
- useEffect(() => {
- setPhotos(
- imagePaths.map((path) => ({
- filePath: path,
- }))
- );
- }, [imagePaths]);
-
return (
-
-
-
prevIndex()}
- xmlns="http://www.w3.org/2000/svg"
- >
-
-
-
-
- {photos.map((photo, index) => (
-
-
-
- ))}
-
-
nextIndex()}
- className="hover:cursor-pointer"
- xmlns="http://www.w3.org/2000/svg"
- >
-
-
-
-
+
+ {photos}
+ {/* @note - We add an empty div because the last image is cut */}
+
+
);
};
diff --git a/src/styles/animation.css b/src/styles/animation.css
new file mode 100644
index 00000000..ca504ab0
--- /dev/null
+++ b/src/styles/animation.css
@@ -0,0 +1,32 @@
+.skeleton {
+ position: relative;
+ inline-size: 100%;
+ overflow: hidden;
+}
+
+@keyframes shimmer {
+ from {
+ transform: "translateX(-100%)";
+ }
+ to {
+ transform: "translateX(100%)";
+ }
+}
+
+&.skeleton {
+ background: "var(--gray-4)";
+
+ &::before {
+ content: "";
+ position: absolute;
+ inset: 0;
+ background-image: linear-gradient(
+ 90deg,
+ rgba(255, 255, 255, 0) 0,
+ rgba(255, 255, 255, 0.2) 20%,
+ rgba(255, 255, 255, 0.5) 60%,
+ rgba(255, 255, 255, 0)
+ );
+ animation: shimmer 2s infinite;
+ }
+}