Timeline 4
cardText Align Left2 ColumnsButtonsDateMultiple ImagesCards

Code
relume-timeline4.tsx
"use client";
import { useRef } from "react";
import { useScroll, useTransform, motion } from "motion/react";
import { Button, type ButtonProps } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { ChevronRight } from "relume-icons";
type ImageProps = {
src: string;
alt?: string;
};
type Timeline = {
date: string;
heading: string;
description: string;
buttons: ButtonProps[];
image: ImageProps;
};
type Props = {
tagline: string;
heading: string;
description: string;
buttons: ButtonProps[];
timelines: Timeline[];
};
export type Timeline4Props = React.ComponentPropsWithoutRef<"section"> & Partial<Props>;
export const Timeline4 = (props: Timeline4Props) => {
const { tagline, heading, description, buttons, timelines } = {
...props,
...Timeline4Defaults,
};
return (
<section className="px-[5%] py-16 md:py-24 lg:py-28">
<div className="container">
<div className="relative grid auto-cols-fr grid-cols-1 items-start justify-center gap-6 sm:gap-12 md:grid-cols-2 md:gap-24 lg:gap-32">
<div className="relative top-0 z-10 md:sticky md:top-20 md:z-auto md:pr-4">
<p className="mb-3 font-semibold md:mb-4">{tagline}</p>
<h1 className="mb-5 text-h2 font-bold md:mb-6">{heading}</h1>
<p className="text-medium">{description}</p>
<div className="mt-6 flex flex-wrap items-center gap-4 md:mt-8">
{buttons.map((button, index) => (
<Button key={index} {...button}>
{button.title}
</Button>
))}
</div>
</div>
<div className="absolute z-0 flex h-full w-8 flex-col items-center justify-self-start [grid-area:2/1/3/2] md:z-auto md:justify-self-center md:[grid-area:auto]">
<div className="absolute z-10 h-16 w-1 bg-gradient-to-b from-scheme-background to-transparent" />
<div className="sticky top-0 mt-[-50vh] h-[50vh] w-[3px] bg-scheme-text" />
<div className="h-full w-[3px] bg-scheme-text/15" />
<div className="absolute bottom-0 z-0 h-16 w-1 bg-gradient-to-b from-transparent to-scheme-background" />
<div className="absolute top-[-50vh] h-[50vh] w-full bg-scheme-background" />
</div>
<div className="grid auto-cols-fr gap-x-12 gap-y-8 sm:gap-y-12 md:gap-x-20 md:gap-y-20">
{timelines.map((timeline, index) => (
<Timeline key={index} timeline={timeline} />
))}
</div>
</div>
</div>
</section>
);
};
const Timeline = ({ timeline }: { timeline: Timeline }) => {
const circleRef = useRef<HTMLDivElement>(null);
const { scrollYProgress } = useScroll({
target: circleRef,
offset: ["start center", "start start"],
});
const backgroundColor = {
backgroundColor: useTransform(
scrollYProgress,
[0, 0.5],
["var(--color-neutral)", "var(--color-scheme-text)"],
),
};
return (
<div className="relative">
<div className="absolute flex h-full w-8 items-start justify-center md:-ml-24 md:w-24 lg:-ml-32 lg:w-32">
<motion.div
ref={circleRef}
className="z-20 mt-9 size-4 rounded-full shadow-[0_0_0_8px_#fff] md:mt-12"
style={backgroundColor}
/>
</div>
<Card className="ml-12 md:ml-0">
<div className="p-6 md:p-8">
<h2 className="mb-3 text-h3 font-bold md:mb-4">{timeline.date}</h2>
<h3 className="mb-3 text-h5 font-bold md:mb-4">{timeline.heading}</h3>
<p>{timeline.description}</p>
<div className="mt-6 flex flex-wrap items-center gap-4 md:mt-8">
{timeline.buttons.map((button, index) => (
<Button key={index} {...button}>
{button.title}
</Button>
))}
</div>
</div>
<div className="overflow-hidden">
<img className="w-full" src={timeline.image.src} alt={timeline.image.alt} />
</div>
</Card>
</div>
);
};
export const Timeline4Defaults: Props = {
tagline: "Tagline",
heading: "Medium length section heading goes here",
description:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat.",
buttons: [
{ title: "Button", variant: "secondary" },
{
title: "Button",
variant: "link",
size: "link",
iconRight: <ChevronRight className="text-scheme-text" />,
},
],
timelines: [
{
date: "Date",
heading: "Short heading here",
description:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat. Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
buttons: [
{ title: "Button", variant: "secondary" },
{
title: "Button",
variant: "link",
size: "link",
iconRight: <ChevronRight className="text-scheme-text" />,
},
],
image: {
src: "https://d22po4pjz3o32e.cloudfront.net/placeholder-image.svg",
alt: "Relume placeholder image 1",
},
},
{
date: "Date",
heading: "Short heading here",
description:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat. Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
buttons: [
{ title: "Button", variant: "secondary" },
{
title: "Button",
variant: "link",
size: "link",
iconRight: <ChevronRight className="text-scheme-text" />,
},
],
image: {
src: "https://d22po4pjz3o32e.cloudfront.net/placeholder-image.svg",
alt: "Relume placeholder image 2",
},
},
{
date: "Date",
heading: "Short heading here",
description:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat. Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
buttons: [
{ title: "Button", variant: "secondary" },
{
title: "Button",
variant: "link",
size: "link",
iconRight: <ChevronRight className="text-scheme-text" />,
},
],
image: {
src: "https://d22po4pjz3o32e.cloudfront.net/placeholder-image.svg",
alt: "Relume placeholder image 3",
},
},
{
date: "Date",
heading: "Short heading here",
description:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat. Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
buttons: [
{ title: "Button", variant: "secondary" },
{
title: "Button",
variant: "link",
size: "link",
iconRight: <ChevronRight className="text-scheme-text" />,
},
],
image: {
src: "https://d22po4pjz3o32e.cloudfront.net/placeholder-image.svg",
alt: "Relume placeholder image 4",
},
},
],
};npm i @radix-ui/react-accordion @radix-ui/react-checkbox @radix-ui/react-label @radix-ui/react-radio-group @radix-ui/react-select @radix-ui/react-slot class-variance-authority@0.7.1 clsx@^2.1.1 motion@^12.15.0 relume-icons@1.3.0 tailwind-merge@^2.2.2


