Menu

Change language

GitHub repo

Logo Ticker Infinite

blockmarketing

Infinite marquee brand partner ticker with sector category filters, speed and pause controls, and interactive telemetry hover cards.

Installation

$npx shadcn@latest add https://uipkge.dev/r/react/logo-ticker-infinite.json
Named registry:npx shadcn@latest add @uipkge-react/logo-ticker-infiniteInstalls to:components/blocks/

Variants

Loading interactive previews…

Props

NameType / ValuesDefaultRequired
classNamestring—optional

Schema

Type aliases exported from this item's source. Use these to shape the data you pass in.

BrandPartner
interface BrandPartner {
  id: string
  name: string
  category: 'devtools' | 'fintech' | 'enterprise'
  role: string
  metric: string
  region: string
}

npm dependencies

Includes

Files installed (1)

  • components/blocks/LogoTickerInfinite.tsx8.5 kB
    'use client'
    
    import * as React from 'react'
    import { Pause, Play } from 'lucide-react'
    import { Badge } from '@/components/ui/badge'
    import { cn } from '@/lib/utils'
    
    interface BrandPartner {
      id: string
      name: string
      category: 'devtools' | 'fintech' | 'enterprise'
      role: string
      metric: string
      region: string
    }
    
    const partners: BrandPartner[] = [
      {
        id: 'vercel',
        name: 'Vercel Edge',
        category: 'devtools',
        role: 'Global Edge Ingestion',
        metric: '99.95% SLA',
        region: 'Global',
      },
      {
        id: 'supabase',
        name: 'Supabase Postgres',
        category: 'devtools',
        role: 'Real-Time Replication',
        metric: '1.2M QPS',
        region: 'US-East',
      },
      {
        id: 'linear',
        name: 'Linear Method',
        category: 'enterprise',
        role: 'Issue Sync Engine',
        metric: '< 20ms Sync',
        region: 'Global',
      },
      {
        id: 'stripe',
        name: 'Stripe Billing',
        category: 'fintech',
        role: 'Usage Metering Gateway',
        metric: '$120M+ Processed',
        region: 'Global',
      },
      {
        id: 'resend',
        name: 'Resend SMTP',
        category: 'devtools',
        role: 'Transactional Dispatch',
        metric: '200M msgs/mo',
        region: 'US-West',
      },
      {
        id: 'cloudflare',
        name: 'Cloudflare Workers',
        category: 'devtools',
        role: 'Zero-Trust Isolation',
        metric: '300+ Cities',
        region: 'Global',
      },
      {
        id: 'planetscale',
        name: 'PlanetScale DB',
        category: 'enterprise',
        role: 'Branching Schema',
        metric: 'Zero Downtime',
        region: 'EU-Central',
      },
      {
        id: 'brex',
        name: 'Brex Treasury',
        category: 'fintech',
        role: 'Ledger Reconciliation',
        metric: 'Real-time API',
        region: 'US-East',
      },
    ]
    
    type CategoryFilter = 'all' | 'devtools' | 'fintech' | 'enterprise'
    
    export interface LogoTickerInfiniteProps {
      className?: string
    }
    
    export function LogoTickerInfinite({ className }: LogoTickerInfiniteProps) {
      const [selectedCategory, setSelectedCategory] = React.useState<CategoryFilter>('all')
      const [isPaused, setIsPaused] = React.useState(false)
      const [isFastSpeed, setIsFastSpeed] = React.useState(false)
      const [hoveredBrand, setHoveredBrand] = React.useState<BrandPartner | null>(null)
    
      const filteredPartners = React.useMemo(() => {
        if (selectedCategory === 'all') return partners
        return partners.filter((p) => p.category === selectedCategory)
      }, [selectedCategory])
    
      return (
        <section
          data-slot="logo-ticker-infinite"
          className={cn(
            'border-border/80 bg-muted/20 relative overflow-hidden border-y px-4 py-14 sm:px-6 sm:py-20 lg:px-8',
            className,
          )}
        >
          <div className="mx-auto max-w-7xl space-y-8 text-center">
            {/* Section Header & Category Filter Controls */}
            <div className="border-border/60 flex flex-col items-center justify-between gap-4 border-b pb-6 text-left md:flex-row">
              <div className="space-y-1">
                <div className="flex items-center gap-2">
                  <span className="bg-success size-2 rounded-full" />
                  <p className="text-foreground font-mono text-xs font-semibold tracking-wider uppercase">
                    Production Verified Infrastructure
                  </p>
                </div>
                <p className="text-muted-foreground text-xs">
                  Trusted by modern engineering teams shipping high-throughput production workloads.
                </p>
              </div>
    
              {/* Filter & Control Buttons */}
              <div className="flex flex-wrap items-center gap-2">
                {/* Category Filter Pills */}
                <div className="bg-background border-border flex items-center gap-1 rounded-lg border p-0.5">
                  {(['all', 'devtools', 'fintech', 'enterprise'] as CategoryFilter[]).map((cat) => (
                    <button
                      key={cat}
                      type="button"
                      className={cn(
                        'rounded-md px-2.5 py-1 font-mono text-xs capitalize transition-colors',
                        selectedCategory === cat
                          ? 'bg-primary text-primary-foreground font-semibold shadow-xs'
                          : 'text-muted-foreground hover:text-foreground',
                      )}
                      onClick={() => setSelectedCategory(cat)}
                    >
                      {cat}
                    </button>
                  ))}
                </div>
    
                {/* Marquee Speed / Pause Controls */}
                <div className="flex items-center gap-1">
                  <button
                    type="button"
                    className="border-border bg-background text-muted-foreground hover:text-foreground rounded-lg border p-1.5 transition-colors"
                    title={isPaused ? 'Resume Ticker' : 'Pause Ticker'}
                    onClick={() => setIsPaused(!isPaused)}
                  >
                    {isPaused ? <Play className="size-3.5 fill-current" /> : <Pause className="size-3.5" />}
                  </button>
                  <button
                    type="button"
                    className="border-border bg-background text-muted-foreground hover:text-foreground rounded-lg border px-2 py-1 font-mono text-xs transition-colors"
                    onClick={() => setIsFastSpeed(!isFastSpeed)}
                  >
                    {isFastSpeed ? '2x' : '1x'}
                  </button>
                </div>
              </div>
            </div>
    
            {/* Infinite Marquee Track Viewport with Gradient Fade Edges */}
            <div className="group relative overflow-hidden">
              {/* Left & Right Gradient Shadows */}
              <div className="from-background via-background/80 pointer-events-none absolute inset-y-0 left-0 z-10 w-16 bg-gradient-to-r to-transparent sm:w-28" />
              <div className="from-background via-background/80 pointer-events-none absolute inset-y-0 right-0 z-10 w-16 bg-gradient-to-l to-transparent sm:w-28" />
    
              {/* Double Track Container for Seamless Infinite Scrolling */}
              <div
                className={cn(
                  'flex w-max items-center gap-4 py-3',
                  !isPaused && 'animate-marquee',
                  isFastSpeed ? '[animation-duration:15s]' : '[animation-duration:32s]',
                )}
                style={{
                  animationPlayState: isPaused ? 'paused' : 'running',
                }}
              >
                {/* Repeat partners array twice for infinite seamless loop */}
                {[...filteredPartners, ...filteredPartners].map((item, idx) => (
                  <div
                    key={`${item.id}-${idx}`}
                    className="border-border bg-card/80 hover:bg-card hover:border-primary/50 relative flex shrink-0 cursor-pointer items-center gap-3 rounded-xl border px-4 py-2.5 shadow-xs backdrop-blur-md transition-colors duration-200"
                    onMouseEnter={() => setHoveredBrand(item)}
                    onMouseLeave={() => setHoveredBrand(null)}
                  >
                    <div className="bg-primary/10 text-primary flex size-7 items-center justify-center rounded-lg font-mono text-xs font-bold">
                      {item.name.charAt(0)}
                    </div>
    
                    <div className="text-left">
                      <p className="text-foreground font-mono text-xs font-semibold tracking-tight">{item.name}</p>
                      <p className="text-muted-foreground text-xs">{item.role}</p>
                    </div>
    
                    <Badge
                      variant="outline"
                      className="border-success/20 bg-success/10 text-success ml-1 font-mono text-xs"
                    >
                      {item.metric}
                    </Badge>
                  </div>
                ))}
              </div>
            </div>
    
            {/* Live Hover Telemetry Card */}
            {hoveredBrand && (
              <div className="border-border bg-card animate-in fade-in-0 mx-auto max-w-md rounded-xl border p-3 text-left shadow-lg duration-150">
                <div className="flex items-center justify-between font-mono text-xs">
                  <span className="text-foreground font-bold">{hoveredBrand.name} Architecture Integration</span>
                  <span className="text-success">{hoveredBrand.region}</span>
                </div>
                <p className="text-muted-foreground mt-1 text-xs">
                  Connected through {hoveredBrand.role} &bull; Monitored with {hoveredBrand.metric} verified telemetry.
                </p>
              </div>
            )}
          </div>
    
          <style>{`
            @keyframes marquee {
              0% { transform: translateX(0%); }
              100% { transform: translateX(-50%); }
            }
            .animate-marquee {
              animation: marquee 32s linear infinite;
            }
          `}</style>
        </section>
      )
    }
    export default LogoTickerInfinite
    

Raw manifest:https://uipkge.dev/r/react/logo-ticker-infinite.json