Blocks

Layout 517

card
Text Align LeftImage/Video CenterButtonsCardsBackground Image
Layout 517

Code

relume-layout517.tsx
"use client";

import React, { useRef } from "react";
import { motion, MotionStyle, 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 } from "relume-icons";

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

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

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

export const Layout517 = (props: Layout517Props) => {
  const { image, tagline, heading, description, buttons } = {
    ...Layout517Defaults,
    ...props,
  };
  const isMobile = useMediaQuery("(max-width: 991px)");
  const containerRef = useRef<HTMLDivElement>(null);
  const { scrollYProgress } = useScroll({
    target: containerRef,
    offset: ["10% start", "end end"],
  });
  const initialImageWidth = isMobile ? "60%" : "20%";
  const initialImageHeight = "40%";
  const imageWidth = useTransform(scrollYProgress, [0, 1], [initialImageWidth, "100%"]);
  const imageHeight = useTransform(scrollYProgress, [0, 1], [initialImageHeight, "100%"]);
  const imageYPosition = useTransform(scrollYProgress, [0, 1], ["0%", "0%"]);
  const cardYPosition = useTransform(scrollYProgress, [0, 0.5, 1], ["100%", "100%", "0%"]);
  const imageStyle = {
    width: imageWidth,
    height: imageHeight,
    y: imageYPosition,
  } as MotionStyle;

  const cardStyle = {
    y: cardYPosition,
  } as MotionStyle;
  return (
    <section ref={containerRef} className="h-[200vh]">
      <div className="sticky top-0 flex h-screen w-full items-center justify-center overflow-hidden">
        <motion.img
          src={image.src}
          alt={image.alt}
          className="size-full object-cover"
          style={imageStyle}
        />
        <motion.div
          className="max-w-xxl absolute inset-0 mx-auto flex size-full items-center justify-end px-[5%]"
          style={cardStyle}
        >
          <Card className="flex w-[90%] flex-col p-6 md:max-w-[658px] md:p-12">
            <p className="mb-3 font-semibold md:mb-4">{tagline}</p>
            <h2 className="text-h2 mb-5 font-bold md:mb-6">{heading}</h2>
            <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>
          </Card>
        </motion.div>
      </div>
    </section>
  );
};

export const Layout517Defaults: Props = {
  image: {
    src: "https://d22po4pjz3o32e.cloudfront.net/placeholder-image-landscape.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 className="text-scheme-text" />,
    },
  ],
};

npm i @radix-ui/react-dialog @radix-ui/react-slot @radix-ui/react-tabs class-variance-authority@0.7.1 clsx@^2.1.1 motion@^12.15.0 relume-icons@1.3.0 tailwind-merge@^2.2.2