bigbullui logobigbullui
Componentmedia

Thumbnail Strip

Horizontal thumbnail strip with animated selection frame.

Preview

Live Preview

Installation

Tokens guide →

Add to your project via CLI (zero dependencies, code you own):

npx bigbullui add thumbnail-strip

Or install the full npm package:

npm install bigbullui

Usage

import { ThumbnailStrip } from "@/components/ui/thumbnail-strip";

<ThumbnailStrip />

Source code

thumbnail-strip.tsx

Zero 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 ThumbnailItem {
  id: string;
  src: string;
  alt?: string;
}

export interface ThumbnailStripProps extends React.HTMLAttributes<HTMLDivElement> {
  images: ThumbnailItem[];
  value?: string;
  defaultValue?: string;
  onValueChange?: (id: string) => void;
}

/** Yatay küçük resim şeridi: seçili çerçeve animasyonlu. */
export function ThumbnailStrip({ images, value, defaultValue, onValueChange, className, ...props }: ThumbnailStripProps) {
  const [internal, setInternal] = React.useState(defaultValue ?? images[0]?.id);
  const active = value ?? internal;

  return (
    <div className={cn("flex gap-2 overflow-x-auto pb-1", className)} role="radiogroup" aria-label="Küçük resimler" {...props}>
      <style>{`@keyframes tsSel { from { transform: scale(0.92); } to { transform: scale(1); } }`}</style>
      {images.map((img, idx) => {
        const selected = active === img.id;
        return (
          <button
            key={img.id}
            role="radio"
            aria-checked={selected}
            onClick={() => { setInternal(img.id); onValueChange?.(img.id); }}
            className={cn(
              "relative shrink-0 overflow-hidden rounded-md border-2 transition-all duration-200 motion-reduce:transition-none",
              "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
              selected ? "border-accent animate-[tsSel_0.2s_ease-out] motion-reduce:animate-none" : "border-transparent opacity-70 hover:opacity-100"
            )}
          >
            {/* eslint-disable-next-line @next/next/no-img-element */}
            <img src={img.src} alt={img.alt ?? ""} className="size-16 object-cover" loading="lazy" />
            {selected && <span className="absolute inset-x-0 bottom-0 h-0.5 bg-accent" aria-hidden="true" />}
          </button>
        );
      })}
    </div>
  );
}

Props

PropTypeDescription
imagesThumbnailItem[]
valuestring
defaultValuestring
onValueChange(id: string) => void