diff --git a/src/components/Container.tsx b/src/components/Container.tsx new file mode 100644 index 0000000..4ba9005 --- /dev/null +++ b/src/components/Container.tsx @@ -0,0 +1,5 @@ +import type { PropsWithChildren } from "react"; + +export const Container: React.FC = ({ children }) => { + return
{children}
; +}; diff --git a/src/components/Polaroid.tsx b/src/components/Polaroid.tsx index 6b2aa07..68c83a0 100644 --- a/src/components/Polaroid.tsx +++ b/src/components/Polaroid.tsx @@ -1,4 +1,6 @@ import Image from "next/image"; +import { useEffect, useRef, useState } from "react"; +import useIntersectionObserver from "~/hooks/useIntersectionObserver"; export interface Props { title: string; @@ -7,6 +9,18 @@ export interface Props { } export const Polaroid: React.FC = ({ title, imageSrc, rotation }) => { + const [showImage, setShowImage] = useState(false); + const ref = useRef(null); + const entry = useIntersectionObserver(ref, {}); + + const isOnScreen = !!entry?.isIntersecting; + + useEffect(() => { + if (isOnScreen) { + setShowImage(true); + } + }, [isOnScreen]); + return (
= ({ title, imageSrc, rotation }) => {
{`Showcase +
- {title} + + {title} +
); diff --git a/src/components/ProjectDescription.tsx b/src/components/ProjectDescription.tsx index c04dda8..353c7e3 100644 --- a/src/components/ProjectDescription.tsx +++ b/src/components/ProjectDescription.tsx @@ -12,8 +12,8 @@ export const ProjectDescription: React.FC = ({ children, }) => { return ( -
-

{title}

+
+

{title}

= [ { title: "Parallel", color: "bg-orange", - imageSrc: "https://unsplash.com/photos/OqtafYT5kTw", + imageSrc: "https://picsum.photos/seed/Parallel/200", rotation: "-rotate-12", children: "We started Parallel to help connect educators with content creators to make it easier to create high-quality educational content for platforms like YouTube. We built the platform with high performance, scalability, and accessibility in mind. That's why we chose powerful tools like Next.js, TailwindCSS, RadixUI, Prisma, PlanetScale, and Vercel's hosting platform. ", @@ -13,15 +13,15 @@ export const projects: Array = [ { title: "DropNote", color: "bg-yellow", - imageSrc: "", + imageSrc: "https://picsum.photos/seed/DropNote/200", rotation: "rotate-6", children: "We started Parallel to help connect educators with content creators to make it easier to create high-quality educational content for platforms like YouTube. We built the platform with high performance, scalability, and accessibility in mind. That's why we chose powerful tools like Next.js, TailwindCSS, RadixUI, Prisma, PlanetScale, and Vercel's hosting platform. ", }, { - title: "Flurry", + title: "Flurry Waitlist", color: "bg-green", - imageSrc: "", + imageSrc: "https://picsum.photos/seed/Flurry/200", rotation: "-rotate-3", children: "We started Parallel to help connect educators with content creators to make it easier to create high-quality educational content for platforms like YouTube. We built the platform with high performance, scalability, and accessibility in mind. That's why we chose powerful tools like Next.js, TailwindCSS, RadixUI, Prisma, PlanetScale, and Vercel's hosting platform. ", diff --git a/src/hooks/useIntersectionObserver.ts b/src/hooks/useIntersectionObserver.ts new file mode 100644 index 0000000..581ef15 --- /dev/null +++ b/src/hooks/useIntersectionObserver.ts @@ -0,0 +1,45 @@ +import { type RefObject, useEffect, useState } from "react"; + +interface Args extends IntersectionObserverInit { + freezeOnceVisible?: boolean; +} + +function useIntersectionObserver( + elementRef: RefObject, + { + threshold = 0, + root = null, + rootMargin = "0%", + freezeOnceVisible = false, + }: Args +): IntersectionObserverEntry | undefined { + const [entry, setEntry] = useState(); + + const frozen = entry?.isIntersecting && freezeOnceVisible; + + const updateEntry = ([entry]: IntersectionObserverEntry[]): void => { + setEntry(entry); + }; + + const stringifiedThreshold = JSON.stringify(threshold); + + useEffect(() => { + const node = elementRef?.current; // DOM Ref + const hasIOSupport = !!window.IntersectionObserver; + + if (!hasIOSupport || frozen || !node) return; + + const observerParams = { threshold, root, rootMargin }; + const observer = new IntersectionObserver(updateEntry, observerParams); + + observer.observe(node); + + return () => observer.disconnect(); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [elementRef?.current, stringifiedThreshold, root, rootMargin, frozen]); + + return entry; +} + +export default useIntersectionObserver; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index f3731fe..5273109 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,5 +1,6 @@ import { type NextPage } from "next"; import Head from "next/head"; +import { Container } from "~/components/Container"; import { Polaroid } from "~/components/Polaroid"; import { ProjectDescription } from "~/components/ProjectDescription"; import { projects } from "~/constants/projects"; @@ -43,27 +44,32 @@ const Hero = () => { const Projects = () => { return ( -
- {projects.map((p) => ( -
-
- + +

+ Here are some of our past projects +

+
+ {projects.map((p) => ( +
+
+ +
+
+ + {p.children} + +
-
- - {p.children} - -
-
- ))} -
+ ))} +
+ ); };