|
| 1 | +import { useScroll, useTransform, motion } from 'framer-motion'; |
| 2 | +import { useEffect, useRef, useState } from 'react'; |
| 3 | +import { TimelineEntry } from './types'; |
| 4 | + |
| 5 | +const Timeline = ({ data }: { data: TimelineEntry[] }) => { |
| 6 | + const ref = useRef<HTMLDivElement>(null); |
| 7 | + const containerRef = useRef<HTMLDivElement>(null); |
| 8 | + const [height, setHeight] = useState(0); |
| 9 | + |
| 10 | + useEffect(() => { |
| 11 | + if (ref.current) { |
| 12 | + const rect = ref.current.getBoundingClientRect(); |
| 13 | + setHeight(rect.height); |
| 14 | + } |
| 15 | + }, [ref]); |
| 16 | + |
| 17 | + const { scrollYProgress } = useScroll({ |
| 18 | + target: containerRef, |
| 19 | + offset: ['start 10%', 'end 50%'], |
| 20 | + }); |
| 21 | + |
| 22 | + const heightTransform = useTransform(scrollYProgress, [0, 1], [0, height]); |
| 23 | + const opacityTransform = useTransform(scrollYProgress, [0, 0.1], [0, 1]); |
| 24 | + |
| 25 | + return ( |
| 26 | + <div |
| 27 | + className="w-full bg-white dark:bg-neutral-950 font-sans md:px-10" |
| 28 | + ref={containerRef} |
| 29 | + > |
| 30 | + <div className="max-w-7xl mx-auto py-20 px-4 md:px-8 lg:px-10"> |
| 31 | + <h2 className="text-lg md:text-4xl mb-4 text-black dark:text-white max-w-4xl"> |
| 32 | + Changelog from my journey |
| 33 | + </h2> |
| 34 | + <p className="text-neutral-700 dark:text-neutral-300 text-sm md:text-base max-w-sm"> |
| 35 | + I've been working on Aceternity for the past 2 years. Here's |
| 36 | + a timeline of my journey. |
| 37 | + </p> |
| 38 | + </div> |
| 39 | + |
| 40 | + <div ref={ref} className="relative max-w-7xl mx-auto pb-20"> |
| 41 | + {data.map((item, index) => ( |
| 42 | + <div |
| 43 | + key={index} |
| 44 | + className="flex justify-start pt-10 md:pt-40 md:gap-10" |
| 45 | + > |
| 46 | + <div className="sticky flex flex-col md:flex-row z-40 items-center top-40 self-start max-w-xs lg:max-w-sm md:w-full"> |
| 47 | + <div className="h-10 absolute left-3 md:left-3 w-10 rounded-full bg-white dark:bg-black flex items-center justify-center"> |
| 48 | + <div className="h-4 w-4 rounded-full bg-neutral-200 dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-700 p-2" /> |
| 49 | + </div> |
| 50 | + <h3 className="hidden md:block text-xl md:pl-20 md:text-5xl font-bold text-neutral-500 dark:text-neutral-500 "> |
| 51 | + {item.title} |
| 52 | + </h3> |
| 53 | + </div> |
| 54 | + |
| 55 | + <div className="relative pl-20 pr-4 md:pl-4 w-full"> |
| 56 | + <h3 className="md:hidden block text-2xl mb-4 text-left font-bold text-neutral-500 dark:text-neutral-500"> |
| 57 | + {item.title} |
| 58 | + </h3> |
| 59 | + {item.content}{' '} |
| 60 | + </div> |
| 61 | + </div> |
| 62 | + ))} |
| 63 | + <div |
| 64 | + style={{ |
| 65 | + height: height + 'px', |
| 66 | + }} |
| 67 | + className="absolute md:left-8 left-8 top-0 overflow-hidden w-[2px] bg-[linear-gradient(to_bottom,var(--tw-gradient-stops))] from-transparent from-[0%] via-neutral-200 dark:via-neutral-700 to-transparent to-[99%] [mask-image:linear-gradient(to_bottom,transparent_0%,black_10%,black_90%,transparent_100%)] " |
| 68 | + > |
| 69 | + <motion.div |
| 70 | + style={{ |
| 71 | + height: heightTransform, |
| 72 | + opacity: opacityTransform, |
| 73 | + }} |
| 74 | + className="absolute inset-x-0 top-0 w-[2px] bg-gradient-to-t from-purple-500 via-blue-500 to-transparent from-[0%] via-[10%] rounded-full" |
| 75 | + /> |
| 76 | + </div> |
| 77 | + </div> |
| 78 | + </div> |
| 79 | + ); |
| 80 | +}; |
| 81 | + |
| 82 | +export default Timeline; |
0 commit comments