{ "name": "testimonial-marquee", "type": "registry:ui", "dependencies": [ "framer-motion" ], "registryDependencies": [], "description": "A smooth, infinite scrolling marquee component for showcasing social proof and testimonials.", "files": [ { "path": "components/ui/testimonial-marquee.tsx", "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cn } from \"@workspace/ui/lib/utils\"\n\nexport interface Testimonial {\n name: string\n text: string\n avatar: string\n role?: string\n username?: string\n profileLink?: string\n}\n\nexport interface TestimonialMarqueeProps {\n items: Testimonial[]\n variant?: \"default\" | \"stacked\" | \"dual\" | \"flush\" | \"flush-dual\"\n className?: string\n speed?: number\n containerClassName?: string\n}\n\n\nconst MarqueeStyles = React.memo(() => (\n \n))\nMarqueeStyles.displayName = \"MarqueeStyles\"\n\nconst MarqueeRow = React.memo(({\n children,\n direction = \"left\",\n speed = 40,\n className,\n pauseOnHover = true\n}: {\n children: React.ReactNode,\n direction?: \"left\" | \"right\"\n speed?: number,\n className?: string,\n pauseOnHover?: boolean\n}) => {\n return (\n
\n \n {children}\n
\n \n {children}\n \n \n )\n})\nMarqueeRow.displayName = \"MarqueeRow\"\n\n\nconst TestimonialCard = React.memo(({ item, variant = \"default\" }: { item: Testimonial, variant?: \"default\" | \"flush\" }) => {\n if (variant === \"flush\") {\n return (\n
\n
\n\n
\n

\n "{item.text}"\n

\n\n
\n
\n {item.name}\n
\n
\n {item.name}\n {item.username && (\n @{item.username}\n )}\n
\n
\n
\n
\n )\n }\n\n return (\n
\n
\n\n
\n

\n "{item.text}"\n

\n\n
\n
\n {item.name}\n
\n
\n {item.name}\n {item.username && (\n @{item.username}\n )}\n
\n
\n
\n
\n )\n})\nTestimonialCard.displayName = \"TestimonialCard\"\n\nexport function TestimonialMarquee({ items, variant = \"default\", className, speed = 30, containerClassName }: TestimonialMarqueeProps) {\n // Combine custom className with container styling if needed\n const cnContainer = cn(containerClassName, className)\n\n const itemsToDisplay = React.useMemo(() => {\n let result = [...items]\n // Ensure we have enough items to fill the width for smooth animation\n // 10 items is a safe heuristic for most screen sizes with 350px cards\n while (result.length < 10) {\n result = [...result, ...items]\n }\n return result\n }, [items])\n\n return (\n \n \n {variant === \"dual\" ? (\n
\n \n {itemsToDisplay.slice(0, Math.ceil(itemsToDisplay.length / 2)).map((item, i) => )}\n \n \n {itemsToDisplay.slice(Math.ceil(itemsToDisplay.length / 2)).map((item, i) => )}\n \n
\n ) : variant === \"stacked\" ? (\n
\n
\n \n {itemsToDisplay.slice(0, Math.ceil(itemsToDisplay.length / 3)).map((item, i) => )}\n \n \n {itemsToDisplay.slice(Math.ceil(itemsToDisplay.length / 3), Math.ceil(itemsToDisplay.length / 3) * 2).map((item, i) => )}\n \n \n {itemsToDisplay.slice(Math.ceil(itemsToDisplay.length / 3) * 2).map((item, i) => )}\n \n
\n ) : variant === \"flush\" ? (\n
\n \n {itemsToDisplay.map((item, i) => )}\n \n
\n
\n
\n ) : variant === \"flush-dual\" ? (\n
\n \n {itemsToDisplay.slice(0, Math.ceil(itemsToDisplay.length / 2)).map((item, i) => )}\n \n \n {itemsToDisplay.slice(Math.ceil(itemsToDisplay.length / 2)).map((item, i) => )}\n \n
\n
\n
\n ) : (\n
\n \n {itemsToDisplay.map((item, i) => )}\n \n
\n )}\n \n )\n}\n", "type": "registry:ui" } ] }