Blocks

Header 143

off_grid
Text Align Left2 ColumnsImage/Video BottomButtonsVideo Lightbox
Header 143

Code

relume-header143.tsx
"use client";

import { motion, useScroll, useTransform } from "motion/react";
import { useMediaQuery } from "@/hooks/use-media-query";
import { Button, type ButtonProps } from "@/components/ui/button";
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
import { PlayCircle, ProgressActivity } from "relume-icons";
import clsx from "clsx";
import { useRef, useState } from "react";

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

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

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

export const Header143 = (props: Header143Props) => {
  const { heading, description, buttons, video, image } = {
    ...Header143Defaults,
    ...props,
  };
  const [isIframeLoaded, setIsIframeLoaded] = useState(false);
  const isMobile = useMediaQuery("(max-width: 767px)");
  const sectionRef = useRef<HTMLElement | null>(null);
  const { scrollYProgress } = useScroll({
    target: sectionRef,
    offset: ["start start", "center 25%"],
  });
  const imageScale = useTransform(scrollYProgress, [0, 1], [0.5, 1]);
  const finalScale = isMobile ? 1 : imageScale;

  return (
    <section className="relative md:min-h-screen">
      <div className="px-[5%] py-16 md:py-24 lg:py-28">
        <div className="container">
          <div className="mb-12 grid grid-cols-1 items-start gap-y-5 md:mb-18 md:grid-cols-2 md:gap-x-12 md:gap-y-8 lg:mb-20 lg:gap-x-20 lg:gap-y-16">
            <h1 className="text-h1 font-bold">{heading}</h1>
            <div className="self-end md:mt-48">
              <p className="text-medium">{description}</p>
              <div className="mt-6 flex flex-wrap gap-4 md:mt-8">
                {buttons.map((button, index) => (
                  <Button key={index} {...button}>
                    {button.title}
                  </Button>
                ))}
              </div>
            </div>
          </div>

          <div className="flex flex-col items-end justify-center md:h-[60vh] lg:h-[80vh] lg:justify-start">
            <Dialog>
              <DialogTrigger asChild>
                <motion.button
                  className="relative flex size-full origin-top-right items-center justify-center overflow-hidden rounded-image"
                  style={{ scale: finalScale }}
                >
                  <img src={image.src} alt={image.alt} className="size-full object-cover" />
                  <PlayCircle className="absolute z-20 size-20 text-white" />
                  <span className="absolute inset-0 z-10 bg-neutral-darkest/50" />
                </motion.button>
              </DialogTrigger>
              <DialogContent>
                {!isIframeLoaded && (
                  <ProgressActivity className="mx-auto size-16 animate-spin text-white" />
                )}
                <iframe
                  className={clsx("z-0 mx-auto aspect-video size-full md:w-[738px] lg:w-[940px]", {
                    visible: isIframeLoaded,
                    hidden: !isIframeLoaded,
                  })}
                  src={video}
                  allow="autoplay; encrypted-media; picture-in-picture"
                  allowFullScreen
                  onLoad={() => setIsIframeLoaded(true)}
                />
              </DialogContent>
            </Dialog>
          </div>
        </div>
      </div>
    </section>
  );
};

export const Header143Defaults: Props = {
  heading: "Medium length hero 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" }, { title: "Button", variant: "secondary" }],
  video: "https://www.youtube.com/embed/8DKLYsikxTs?si=Ch9W0KrDWWUiCMMW",
  image: {
    src: "https://d22po4pjz3o32e.cloudfront.net/placeholder-video-thumbnail-landscape.svg",
    alt: "Relume placeholder image",
  },
};

npm i @radix-ui/react-accordion @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