Componentmedia1.01 KB gzipZero dependencies
VideoThumbnail
Poster frame with duration badge, play overlay circle that scales on hover with ripple ring, and title strip
Preview
Live Preview
120 min
Midnight Movie
Installation
Tokens guide →Add to your project via CLI (zero dependencies, code you own):
npx bigbullui add video-thumbnailOr install the full npm package:
npm install bigbulluiUsage
Open in StackBlitzimport { VideoThumbnail } from "@/components/ui/video-thumbnail"
<VideoThumbnail poster={poster} duration={120} title="Video Title" />Usage guidance
Use VideoThumbnail when a ticket stub surface needs Poster frame with duration badge, play overlay circle that scales on hover with ripple ring, and title strip Keep props minimal and prefer composition with sibling stubs.
Avoid VideoThumbnail for long form flows or dense data grids where a dedicated layout block fits better.
Source code
video-thumbnail.tsxZero dependencies (only React + utils). Copy and paste directly into your project:
"use client";
import * as React from "react";
import { cn } from "./lib/utils";
export interface VideoThumbnailProps {
poster?: React.ReactNode;
duration?: number;
title?: string;
className?: string;
}
export function VideoThumbnail({
poster,
duration,
title,
className,
...props
}: VideoThumbnailProps) {
const motionReduceClass = "motion-reduce:transition-none";
return (
<div
className={cn(
"rounded-lg overflow-hidden bg-card border border-border p-6",
className
)}
{...props}
>
{/* Poster frame */}
<div
className={cn(
"relative w-full h-48 mb-3",
"motion-reduce:transition-none"
)}
>
{poster && (
<div className="relative w-full h-48">
{typeof poster === "string" ? (
<img
src={poster}
alt={title || "Video"}
className="w-full h-48 object-cover"
/>
) : (
<div
className="w-full h-48 bg-muted flex items-center justify-center"
>
{/* Poster placeholder */}
</div>
)}
</div>
)}
{/* Duration badge */}
{duration && (
<div
className={cn(
"absolute top-2 left-2 flex items-center gap-1 bg-background rounded-full px-2 py-0.5 text-xs font-mono uppercase tracking-[0.1em] text-muted-foreground",
motionReduceClass
)}
>
<span className="font-mono">
{duration} {
duration === 1 ? "min" : "min"
}
</span>
</div>
)}
{/* Play overlay circle - scales on hover with ripple ring */}
<div
className={cn(
"absolute inset-0 flex items-center justify-center rounded-full border-2 border-border/60",
"motion-reduce:transition-none",
"hover:scale-105",
"transition-transform duration-200 ease-out"
)}
onMouseEnter={() => {}}
onMouseLeave={() => {}}
>
<svg
className={cn("w-12 h-12 text-primary", motionReduceClass)}
viewBox="0 0 24 24"
>
<circle
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="1.5"
fill="none"
/>
<path
d="M8 12l4 4L20 4"
stroke="currentColor"
strokeWidth="1.5"
fill="none"
/>
</svg>
</div>
</div>
{/* Title strip bottom gradient */}
{title && (
<div className="pt-3 border-t border-border/50">
<p className="font-mono text-[10px] uppercase tracking-[0.15em] text-muted-foreground">
{title}
</p>
</div>
)}
</div>
);
}Props
| Prop | Type | Description |
|---|---|---|
| poster? | React.ReactNode | |
| duration? | number | |
| title? | string | |
| className? | string |