Blocks

Layout 416

standard
Text Align Left2 ColumnsIconsButtons
Layout 416

Code

relume-layout416.tsx
"use client";

import { useEffect, useMemo, useRef } from "react";
import { MotionStyle, motion, useScroll, useTransform } from "motion/react";
import { useMediaQuery } from "@/hooks/use-media-query";
import { Button, type ButtonProps } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { ChevronRight, RelumeIcon } from "relume-icons";

type FeatureSectionProps = {
  title: string;
  description: string;
};

type Props = {
  tagline: string;
  heading: string;
  description: string;
  buttons: ButtonProps[];
  featureSections: FeatureSectionProps[];
};

export type Layout416Props = React.ComponentPropsWithoutRef<"section"> & Partial<Props>;

const MotionCard = motion.create(Card) as any;

export const Layout416 = (props: Layout416Props) => {
  const { tagline, heading, description, buttons, featureSections } = {
    ...Layout416Defaults,
    ...props,
  };

  const isMobile = useMediaQuery("(max-width: 767px)");
  const containerRef = useRef<HTMLDivElement>(null);

  const { scrollYProgress } = useScroll({
    target: containerRef,
    offset: isMobile ? ["20% start", "end end"] : ["start start", "end end"],
  });

  const transforms = useMemo(
    () =>
      featureSections.map((_, index) => {
        const total = featureSections.length;
        const start = index / total;
        const end = (index + 1) / total;

        return {
          rotate: useTransform(scrollYProgress, [start, end], [0 + index * 3, -30]),
          translateY: useTransform(scrollYProgress, [start, end], ["0vh", "-100vh"]),
          translateX: useTransform(scrollYProgress, [start, end], ["0vw", "-10vw"]),
        };
      }),
    [scrollYProgress, featureSections.length],
  );

  return (
    <section ref={containerRef} className="px-[5%]">
      <div className="container">
        <div className="relative h-[300svh] lg:h-[300vh]">
          <div className="static grid h-full grid-cols-1 content-start items-center gap-x-20 gap-y-16 py-16 md:sticky md:top-0 md:h-[100svh] md:grid-cols-2 md:content-normal md:py-0 lg:h-screen">
            <div>
              <p className="mb-3 font-semibold md:mb-4">{tagline}</p>
              <h2 className="mb-5 text-h2 font-bold md:mb-6">{heading}</h2>
              <p className="text-medium">{description}</p>
              <div className="mt-6 flex items-center gap-x-4 md:mt-8">
                {buttons.map((button, i) => (
                  <Button key={i} {...button}>
                    {button.title}
                  </Button>
                ))}
              </div>
            </div>
            <div className="sticky top-[25%] flex min-h-[24.5rem] flex-col items-center justify-center md:relative md:top-0 md:min-h-[auto]">
              {featureSections.map((section, index) => (
                <MotionCard
                  key={index}
                  className="absolute mx-6 flex flex-col justify-between p-8 md:ml-0"
                  style={{
                    rotate:
                      index === featureSections.length - 1 ? "6deg" : transforms[index].rotate,
                    translateY:
                      index === featureSections.length - 1 ? "0vh" : transforms[index].translateY,
                    translateX:
                      index === featureSections.length - 1 ? "0vw" : transforms[index].translateX,
                    zIndex: featureSections.length - index,
                  }}
                >
                  <RelumeIcon className="mb-6 size-12 text-scheme-text md:mb-8" />
                  <h3 className="mb-3 text-h5 font-bold md:mb-4">{section.title}</h3>
                  <p>{section.description}</p>
                </MotionCard>
              ))}
            </div>
          </div>
        </div>
      </div>
      <div className="absolute inset-0 -z-10 mt-[100vh]" />
    </section>
  );
};

export const Layout416Defaults: 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" />,
    },
  ],
  featureSections: [
    {
      title: "Subheading one",
      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.",
    },
    {
      title: "Subheading two",
      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.",
    },
    {
      title: "Subheading three",
      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.",
    },
  ],
};

npm i @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