From d052b3cfe9a7136533e660571c131831e60ed759 Mon Sep 17 00:00:00 2001
From: Brandon Saldan <26472557+brandonsaldan@users.noreply.github.com>
Date: Tue, 31 Dec 2024 19:34:56 -0500
Subject: [PATCH] feat(about): add mobile animations
---
src/components/about-page.tsx | 128 ++----------------
src/components/{ => about}/about-navbar.tsx | 4 +-
.../about/animated-image-gallery.tsx | 114 ++++++++++++++++
.../about/animated-team-members.tsx | 75 ++++++++++
.../about/animated-testimonials.tsx | 87 ++++++++++++
5 files changed, 290 insertions(+), 118 deletions(-)
rename src/components/{ => about}/about-navbar.tsx (97%)
create mode 100644 src/components/about/animated-image-gallery.tsx
create mode 100644 src/components/about/animated-team-members.tsx
create mode 100644 src/components/about/animated-testimonials.tsx
diff --git a/src/components/about-page.tsx b/src/components/about-page.tsx
index dc73d4b..6cc2224 100644
--- a/src/components/about-page.tsx
+++ b/src/components/about-page.tsx
@@ -1,5 +1,8 @@
'use client'
-import { Navbar } from '@/components/about-navbar'
+import { Navbar } from '@/components/about/about-navbar'
+import AnimatedImageGallery from '@/components/about/animated-image-gallery'
+import AnimatedTeamMember from '@/components/about/animated-team-members'
+import AnimatedTestimonial from '@/components/about/animated-testimonials'
import { Container } from '@/components/container'
import { Footer } from '@/components/footer'
import { Link } from '@/components/link'
@@ -198,52 +201,7 @@ function About() {
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -303,27 +261,7 @@ function About() {
className="grid gap-x-8 gap-y-12 sm:grid-cols-2 sm:gap-y-16 xl:col-span-2"
>
{people.map((person) => (
-
-
-
-
-
-
-
-
- {person.name}
-
-
- {person.role}
-
-
-
-
-
+
))}
@@ -333,31 +271,10 @@ function About() {
{testimonials.map((testimonial) => (
-
+ testimonial={testimonial}
+ />
))}
@@ -395,31 +312,10 @@ function About() {
{testimonials.map((testimonial) => (
-
+ testimonial={testimonial}
+ />
))}
diff --git a/src/components/about-navbar.tsx b/src/components/about/about-navbar.tsx
similarity index 97%
rename from src/components/about-navbar.tsx
rename to src/components/about/about-navbar.tsx
index 04da660..60c7e8d 100644
--- a/src/components/about-navbar.tsx
+++ b/src/components/about/about-navbar.tsx
@@ -3,8 +3,8 @@
import { Disclosure, DisclosureButton } from '@headlessui/react'
import { Bars2Icon } from '@heroicons/react/24/solid'
import { AnimatePresence, motion } from 'framer-motion'
-import { Link } from './link'
-import { LogoDark } from './logo'
+import { Link } from '../link'
+import { LogoDark } from '../logo'
const links = [
{ href: '/installation', label: 'Installation' },
diff --git a/src/components/about/animated-image-gallery.tsx b/src/components/about/animated-image-gallery.tsx
new file mode 100644
index 0000000..e604aa0
--- /dev/null
+++ b/src/components/about/animated-image-gallery.tsx
@@ -0,0 +1,114 @@
+import { useEffect, useRef } from 'react'
+
+const AnimatedImageGallery = () => {
+ const imageRefs = [useRef(null), useRef(null)]
+
+ useEffect(() => {
+ if (window.innerWidth >= 640) return
+
+ const observers = imageRefs.map((ref, index) => {
+ const threshold = 0.5 - index * 0.1
+
+ const observer = new IntersectionObserver(
+ (entries) => {
+ entries.forEach((entry) => {
+ if (entry.isIntersecting) {
+ setTimeout(() => {
+ if (ref.current) {
+ ref.current.classList.remove(
+ 'opacity-0',
+ 'translate-x-8',
+ '-translate-x-8',
+ )
+ ref.current.classList.add('opacity-100', 'translate-x-0')
+ }
+ }, index * 100)
+ observer.unobserve(entry.target)
+ }
+ })
+ },
+ {
+ threshold,
+ rootMargin: '0px 0px -10% 0px',
+ },
+ )
+
+ if (ref.current) {
+ observer.observe(ref.current)
+ }
+
+ return observer
+ })
+
+ return () => {
+ observers.forEach((observer) => observer.disconnect())
+ }
+ }, [])
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )
+}
+
+export default AnimatedImageGallery
diff --git a/src/components/about/animated-team-members.tsx b/src/components/about/animated-team-members.tsx
new file mode 100644
index 0000000..810cd13
--- /dev/null
+++ b/src/components/about/animated-team-members.tsx
@@ -0,0 +1,75 @@
+import { useEffect, useRef } from 'react'
+
+interface TeamMember {
+ name: string
+ role: string
+ imageUrl: string
+ href: string
+}
+
+interface AnimatedTeamMemberProps {
+ person: TeamMember
+}
+
+const AnimatedTeamMembers = ({ person }: AnimatedTeamMemberProps) => {
+ const memberRef = useRef(null)
+
+ useEffect(() => {
+ if (window.innerWidth >= 640) return
+
+ const observer = new IntersectionObserver(
+ (entries) => {
+ entries.forEach((entry) => {
+ if (entry.isIntersecting) {
+ if (memberRef.current) {
+ memberRef.current.classList.remove('opacity-0', 'translate-y-8')
+ memberRef.current.classList.add('opacity-100', 'translate-y-0')
+ }
+ observer.unobserve(entry.target)
+ }
+ })
+ },
+ {
+ threshold: 0.2,
+ rootMargin: '0px 0px -10% 0px',
+ },
+ )
+
+ if (memberRef.current) {
+ observer.observe(memberRef.current)
+ }
+
+ return () => {
+ observer.disconnect()
+ }
+ }, [])
+
+ return (
+
+
+
+
+
+
+
+
+ {person.name}
+
+
+ {person.role}
+
+
+
+
+
+ )
+}
+
+export default AnimatedTeamMembers
diff --git a/src/components/about/animated-testimonials.tsx b/src/components/about/animated-testimonials.tsx
new file mode 100644
index 0000000..0c96d34
--- /dev/null
+++ b/src/components/about/animated-testimonials.tsx
@@ -0,0 +1,87 @@
+import { useEffect, useRef } from 'react'
+
+interface TestimonialAuthor {
+ name: string
+ handle: string
+ imageUrl: string
+}
+
+interface Testimonial {
+ body: string
+ author: TestimonialAuthor
+ href: string
+}
+
+interface AnimatedTestimonialProps {
+ testimonial: Testimonial
+}
+
+const AnimatedTestimonials = ({ testimonial }: AnimatedTestimonialProps) => {
+ const testimonialRef = useRef(null)
+
+ useEffect(() => {
+ if (window.innerWidth >= 640) return
+
+ const observer = new IntersectionObserver(
+ (entries) => {
+ entries.forEach((entry) => {
+ if (entry.isIntersecting) {
+ if (testimonialRef.current) {
+ testimonialRef.current.classList.remove(
+ 'opacity-0',
+ 'translate-y-8',
+ )
+ testimonialRef.current.classList.add(
+ 'opacity-100',
+ 'translate-y-0',
+ )
+ }
+ observer.unobserve(entry.target)
+ }
+ })
+ },
+ {
+ threshold: 0.2,
+ rootMargin: '0px 0px -10% 0px',
+ },
+ )
+
+ if (testimonialRef.current) {
+ observer.observe(testimonialRef.current)
+ }
+
+ return () => {
+ observer.disconnect()
+ }
+ }, [])
+
+ return (
+
+ )
+}
+
+export default AnimatedTestimonials