"use client"; import { motion, useReducedMotion } from "motion/react"; import type { ReactNode } from "react"; import { Marquee } from "@/components/motion/marquee"; import { EASE_OUT } from "@/lib/ease"; import { cn } from "@/lib/utils"; export interface LogoCloudItem { id: string; /** Accessible name of the company or product. */ name: string; /** SVG or any node. Use `currentColor` so it follows the muted/hover tint. */ logo: ReactNode; href?: string; } export interface LogoCloudProps { logos: LogoCloudItem[]; /** "marquee" scrolls one infinite row; "grid" lays logos out and fades them in. Default "marquee". */ variant?: "marquee" | "grid"; eyebrow?: ReactNode; heading?: ReactNode; description?: ReactNode; /** Seconds for one marquee loop. Default 30. */ speed?: number; /** Marquee direction. Default "left". */ direction?: "left" | "right"; /** Pause the marquee on hover or keyboard focus. Default true. */ pauseOnHover?: boolean; /** Logos start muted (grayscale, dimmed) and take their own colors on hover. Default true. */ colorize?: boolean; /** Grid columns at the widest container size. Default 4. */ columns?: 3 | 4 | 5 | 6; className?: string; /** Classes for each logo tile (sizing, padding). */ itemClassName?: string; } // Static class maps: Tailwind only ships classes it can see written out. const GRID_COLUMNS: Record<3 | 4 | 5 | 6, string> = { 3: "grid-cols-2 @md:grid-cols-3", 4: "grid-cols-2 @md:grid-cols-4", 5: "grid-cols-2 @md:grid-cols-3 @2xl:grid-cols-5", 6: "grid-cols-3 @md:grid-cols-4 @2xl:grid-cols-6", }; const STAGGER = 0.05; function LogoMark({ item, colorize, lift, className, }: { item: LogoCloudItem; colorize: boolean; lift: boolean; className?: string; }) { const classes = cn( "group/logo flex h-12 items-center justify-center px-3 text-muted-foreground outline-none transition-[color,filter,opacity,translate,border-color,box-shadow] duration-300 [&_svg]:h-7 [&_svg]:w-auto [&_svg]:max-w-full", colorize && "opacity-70 grayscale hover:text-foreground hover:opacity-100 hover:grayscale-0 focus-visible:text-foreground focus-visible:opacity-100 focus-visible:grayscale-0", // Tailwind's hover variant only applies on hover-capable pointers, so // touch devices never get a stuck lift. lift && "hover:-translate-y-1 motion-reduce:hover:translate-y-0", item.href && "rounded-xl focus-visible:ring-2 focus-visible:ring-ring/50", className, ); const content = ( <> {item.logo} {item.name} > ); return item.href ? ( {content} ) : (
{eyebrow}
) : null} {heading ? ({description}
) : null}