Blocks

Layout 411

standard
2 ColumnsText Align LeftImage/Video RightImageMultiple ImagesAbout Section
Layout 411

Code

relume-layout411.tsx
import { useRef } from "react";
import { motion, useScroll, useTransform } from "motion/react";
import { Button, type ButtonProps } from "@/components/ui/button";
import { ChevronRight } from "relume-icons";
import { AssetAnchor, includes, oneOf } from "../../editing";

type ImageProps = {
  src: string;
  alt?: string;
};

type FeatureSection = {
  tagline: string;
  heading: string;
  description: string;
  buttons: ButtonProps[];
  image: ImageProps;
};

type Props = {
  featureSections: FeatureSection[];
  backgroundImage: ImageProps;
  images: ImageProps[];
};

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

export const Layout411 = (props: Layout411Props) => {
  const { featureSections, backgroundImage, images } = {
    ...Layout411Defaults,
    ...props,
  };

  const sectionRef = useRef<HTMLDivElement>(null);

  const { scrollYProgress } = useScroll({
    target: sectionRef,
    offset: ["start start", "end end"],
  });

  const imageSlideTop = useTransform(scrollYProgress, [0, 1], ["0%", `-75%`]);

  return (
    <section className="px-[5%]">
      <div className="container">
        <div className="relative grid auto-cols-fr grid-cols-1 gap-x-12 gap-y-12 md:grid-cols-2 md:gap-y-16 lg:gap-x-20">
          <div className="flex flex-col items-start" ref={sectionRef}>
            {featureSections.map((featureSection, index) => (
              <div
                key={index}
                className="flex flex-col items-start justify-center py-8 md:h-screen md:py-0"
              >
                <p className="mb-3 font-semibold md:mb-4">{featureSection.tagline}</p>
                <h1 className="mb-5 text-h2 font-bold md:mb-6">{featureSection.heading}</h1>
                <p className="text-medium">{featureSection.description}</p>
                <div className="mt-6 flex items-center gap-4 md:mt-8">
                  {featureSection.buttons.map((button, buttonIndex) => (
                    <Button key={buttonIndex} {...button}>
                      {button.title}
                    </Button>
                  ))}
                </div>
                <div
                  className="mt-10 block w-full bg-neutral-darkest/50 bg-cover bg-center p-[10%] md:hidden"
                  style={{ backgroundImage: backgroundImage.src }}
                >
                  <AssetAnchor match={includes(`layout411_mobile-image-${index + 1}`)}>
                    <img
                      src={featureSection.image.src}
                      alt={featureSection.image.alt}
                      className="static size-full rounded-image"
                    />
                  </AssetAnchor>
                </div>
              </div>
            ))}
          </div>
          <div className="sticky top-0 hidden h-screen md:block">
            <div className="relative top-[10%] h-4/5 overflow-hidden">
              <motion.div className="relative z-10" style={{ y: imageSlideTop }}>
                {images.map((image, index) => (
                  <div
                    key={index}
                    className="flex h-screen w-full items-center justify-center pb-[20vh]"
                  >
                    <AssetAnchor match={includes(`layout411_image-${index + 1}`)}>
                      <img src={image.src} alt={image.alt} className="w-4/5 rounded-image" />
                    </AssetAnchor>
                  </div>
                ))}
              </motion.div>
              <img
                src={backgroundImage.src}
                alt={backgroundImage.alt}
                className="absolute inset-0 -z-10 size-full object-cover brightness-50"
              />
            </div>
          </div>
        </div>
      </div>
    </section>
  );
};

export const Layout411Defaults: Props = {
  featureSections: [
    {
      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 />,
        },
      ],
      image: {
        src: "https://d22po4pjz3o32e.cloudfront.net/placeholder-image-1.svg",
        alt: "Relume placeholder image 1",
      },
    },
    {
      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 />,
        },
      ],
      image: {
        src: "https://d22po4pjz3o32e.cloudfront.net/placeholder-image-2.svg",
        alt: "Relume placeholder image 2",
      },
    },
    {
      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 />,
        },
      ],
      image: {
        src: "https://d22po4pjz3o32e.cloudfront.net/placeholder-image-3.svg",
        alt: "Relume placeholder image 3",
      },
    },
    {
      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 />,
        },
      ],
      image: {
        src: "https://d22po4pjz3o32e.cloudfront.net/placeholder-image-4.svg",
        alt: "Relume placeholder image 4",
      },
    },
  ],
  images: [
    {
      src: "https://d22po4pjz3o32e.cloudfront.net/placeholder-image-1.svg",
      alt: "Relume placeholder image 1",
    },
    {
      src: "https://d22po4pjz3o32e.cloudfront.net/placeholder-image-2.svg",
      alt: "Relume placeholder image 2",
    },
    {
      src: "https://d22po4pjz3o32e.cloudfront.net/placeholder-image-3.svg",
      alt: "Relume placeholder image 3",
    },
    {
      src: "https://d22po4pjz3o32e.cloudfront.net/placeholder-image-4.svg",
      alt: "Relume placeholder image 4",
    },
  ],
  backgroundImage: {
    src: "https://d22po4pjz3o32e.cloudfront.net/placeholder-image-portrait.svg",
    alt: "Relume placeholder background image",
  },
};

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