{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "hero-video-dialog",
  "type": "registry:ui",
  "title": "Hero Video Dialog",
  "description": "Poster image with a play button that opens an embedded video in a modal. Landing-page hero staple.",
  "dependencies": [
    "@saasflare/ui",
    "motion"
  ],
  "files": [
    {
      "path": "components/ui/hero-video-dialog.tsx",
      "type": "registry:ui",
      "content": "\"use client\"\n\n/**\n * @fileoverview Click-to-play video modal with poster thumbnail.\n * @author Saasflare™\n * Renders a poster image with a play button overlay. On click, opens a\n * dialog with the video playing. Supports YouTube, Vimeo, and direct URLs.\n * @module packages/ui/components/ui/hero-video-dialog\n * @package ui\n *\n * @component\n * @example\n * import { HeroVideoDialog } from '@saasflare/ui';\n * <HeroVideoDialog\n *   videoSrc=\"https://www.youtube.com/embed/dQw4w9WgXcQ\"\n *   thumbnailSrc=\"/product-demo-poster.jpg\"\n *   thumbnailAlt=\"Product demo video\"\n * />\n *\n * @example\n * // With custom aspect ratio\n * <HeroVideoDialog\n *   videoSrc=\"https://player.vimeo.com/video/123456789\"\n *   thumbnailSrc=\"/poster.jpg\"\n *   thumbnailAlt=\"Demo\"\n *   aspectRatio=\"4/3\"\n * />\n */\n\nimport * as React from \"react\"\nimport { useState, useCallback, useEffect } from \"react\"\nimport { AnimatePresence, m } from \"motion/react\"\nimport { PlayIcon, XIcon } from \"@saasflare/ui\"\nimport { cn } from \"@saasflare/ui\"\nimport { useSaasflareProps, type SaasflareComponentProps } from \"@saasflare/ui\"\nimport { useSaasflareMotion, springBouncy } from \"@saasflare/ui\"\nimport { useFocusTrap } from \"@saasflare/ui\"\nimport { useScrollLock } from \"@saasflare/ui\"\n\n/** Props for the HeroVideoDialog component. */\nexport interface HeroVideoDialogProps\n  extends Omit<React.ComponentProps<\"button\">, keyof SaasflareComponentProps>,\n    SaasflareComponentProps {\n  /** Video embed URL (YouTube/Vimeo embed or direct video URL). */\n  videoSrc: string\n  /** Poster thumbnail image URL. */\n  thumbnailSrc: string\n  /** Alt text for the thumbnail image. */\n  thumbnailAlt: string\n  /** CSS aspect-ratio for the thumbnail. Default: `\"16/9\"` */\n  aspectRatio?: string\n}\n\n/**\n * Hero video section with click-to-play dialog.\n *\n * - Shows a thumbnail with a centered play button\n * - Opens a modal dialog with the embedded video on click\n * - Animated with spring physics (scale + fade), gated on the `animated` axis\n * - Accessible: focus trap + restoration, body-scroll lock, keyboard close, aria labels\n *\n * @component\n * @package ui\n */\nexport function HeroVideoDialog({\n  videoSrc,\n  thumbnailSrc,\n  thumbnailAlt,\n  aspectRatio = \"16/9\",\n  className,\n  surface,\n  radius,\n  animated,\n  iconWeight,\n  ...props\n}: HeroVideoDialogProps) {\n  const sf = useSaasflareProps({ surface, radius, animated, iconWeight })\n  const motion = useSaasflareMotion(sf.animated, springBouncy)\n  const [open, setOpen] = useState(false)\n\n  const close = useCallback(() => setOpen(false), [])\n\n  const overlayRef = useFocusTrap<HTMLDivElement>(open)\n  useScrollLock(open)\n\n  // Document-level Escape so close works regardless of which element holds focus.\n  useEffect(() => {\n    if (!open) return\n    const onKeyDown = (e: globalThis.KeyboardEvent) => {\n      if (e.key === \"Escape\") close()\n    }\n    document.addEventListener(\"keydown\", onKeyDown)\n    return () => document.removeEventListener(\"keydown\", onKeyDown)\n  }, [open, close])\n\n  return (\n    <>\n      {/* Thumbnail with play button */}\n      <button\n        {...props}\n        type=\"button\"\n        onClick={() => setOpen(true)}\n        className={cn(\n          \"group relative w-full cursor-pointer overflow-hidden rounded-xl border shadow-lg\",\n          className,\n        )}\n        style={{ aspectRatio }}\n        aria-label={`Play video: ${thumbnailAlt}`}\n        data-slot=\"hero-video-dialog\"\n        data-surface={sf.surface}\n        data-radius={sf.radius}\n        data-animated={String(sf.animated)}\n      >\n        <img\n          src={thumbnailSrc}\n          alt={thumbnailAlt}\n          className=\"size-full object-cover transition-transform duration-300 group-hover:scale-105\"\n        />\n        {/* Play button overlay */}\n        <div className=\"absolute inset-0 flex items-center justify-center bg-black/20 transition-colors group-hover:bg-black/30\">\n          <div className=\"flex size-16 items-center justify-center rounded-full bg-white/90 shadow-xl backdrop-blur-sm transition-transform group-hover:scale-110\">\n            <PlayIcon weight={sf.iconWeight} className=\"ml-1 size-7 text-foreground\" />\n          </div>\n        </div>\n      </button>\n\n      {/* Video dialog */}\n      <AnimatePresence>\n        {open && (\n          <m.div\n            ref={overlayRef}\n            initial={motion.disabled ? { opacity: 1 } : { opacity: 0 }}\n            animate={{ opacity: 1 }}\n            exit={{ opacity: 0 }}\n            transition={motion.transition}\n            className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm\"\n            onClick={close}\n            role=\"dialog\"\n            aria-modal=\"true\"\n            aria-label=\"Video player\"\n            tabIndex={-1}\n          >\n            {/* Close button */}\n            <button\n              type=\"button\"\n              onClick={close}\n              className=\"absolute right-4 top-4 z-10 rounded-full bg-white/10 p-2 text-white transition-colors hover:bg-white/20\"\n              aria-label=\"Close video\"\n            >\n              <XIcon weight={sf.iconWeight} className=\"size-6\" />\n            </button>\n\n            {/* Video container */}\n            <m.div\n              initial={motion.disabled ? false : { scale: 0.9, opacity: 0 }}\n              animate={{ scale: 1, opacity: 1 }}\n              exit={motion.disabled ? { opacity: 0 } : { scale: 0.9, opacity: 0 }}\n              transition={motion.transition}\n              className=\"w-full max-w-5xl px-4\"\n              onClick={(e) => e.stopPropagation()}\n            >\n              <div className=\"overflow-hidden rounded-xl\" style={{ aspectRatio }}>\n                <iframe\n                  src={videoSrc}\n                  className=\"size-full\"\n                  allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\"\n                  allowFullScreen\n                  title={thumbnailAlt}\n                />\n              </div>\n            </m.div>\n          </m.div>\n        )}\n      </AnimatePresence>\n    </>\n  )\n}\n",
      "target": "components/ui/hero-video-dialog.tsx"
    }
  ],
  "$meta": {
    "source": "https://ui.saasflare.io/r/hero-video-dialog.json"
  }
}
