Logo Cloud 4
Logo Cloud 4 é um componente de marquee para React e Tailwind CSS, da biblioteca SmoothUI, com licença MIT. Copia e cola no teu projeto.
SmoothUI
MIT
marquee
O que é
Logo Cloud 4 é um componente de marquee para React e Tailwind CSS, da biblioteca SmoothUI, com licença MIT. Copia e cola no teu projeto.
Como usar
Abre o componente no catálogo, carrega em copiar, e cola no teu projeto. O prompt copiado leva o código e as regras para o teu agente o reproduzir sem alterar nada.
Cinco cópias grátis por mês. Sem cartão.
Precisa de
npm i motion
Licença
Este componente vem de SmoothUI e é redistribuído sob a licença MIT, que permite uso comercial. O aviso de copyright viaja com o código.
Logo Cloud 4
"use client";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
import type React from "react";
import { useEffect, useState } from "react";
import {
Canpoy,
Canva,
Casetext,
Clearbit,
Descript,
Duolingo,
Faire,
Strava,
} from "@/components/smoothui/shared";
export interface LogoGridProps {
columns?: 3 | 4 | 5 | 6;
description?: string;
logos?: Array<{
name: string;
logo: React.ReactNode;
href?: string;
}>;
title?: string;
}
const DEFAULT_LOGOS = [
{ logo: <Canpoy />, name: "Canpoy" },
{ logo: <Canva />, name: "Canva" },
{ logo: <Casetext />, name: "Casetext" },
{ logo: <Strava />, name: "Strava" },
{ logo: <Descript />, name: "Descript" },
{ logo: <Duolingo />, name: "Duolingo" },
{ logo: <Faire />, name: "Faire" },
{ logo: <Clearbit />, name: "Clearbit" },
];
const COLUMN_CLASSES = {
3: "grid-cols-2 sm:grid-cols-3",
4: "grid-cols-2 sm:grid-cols-4",
5: "grid-cols-2 sm:grid-cols-3 lg:grid-cols-5",
6: "grid-cols-2 sm:grid-cols-3 lg:grid-cols-6",
};
// Animation constants
const HOVER_LIFT_OFFSET = -4;
const INACTIVE_OPACITY = 0.6;
interface LogoItemProps {
isHoverDevice: boolean;
logo: {
name: string;
logo: React.ReactNode;
href?: string;
};
shouldReduceMotion: boolean | null;
}
function LogoItem({ logo, isHoverDevice, shouldReduceMotion }: LogoItemProps) {
const [isHovered, setIsHovered] = useState(false);
const content = (
<motion.div
animate={
shouldReduceMotion
? {}
: {
y: isHovered ? HOVER_LIFT_OFFSET : 0,
}
}
className="group relative flex items-center justify-center p-6"
onMouseEnter={() => isHoverDevice && setIsHovered(true)}
onMouseLeave={() => isHoverDevice && setIsHovered(false)}
transition={
shouldReduceMotion
? { duration: 0 }
: {
bounce: 0.05,
duration: 0.25,
type: "spring" as const,
}
}
>
{/* Logo with grayscale effect */}
<div
className="transition-all duration-200 *:fill-foreground"
style={{
filter: isHovered ? "grayscale(0)" : "grayscale(1)",
opacity: isHovered ? 1 : INACTIVE_OPACITY,
}}
>
{logo.logo}
</div>
{/* Tooltip */}
<AnimatePresence>
{isHovered ? (
<motion.div
animate={{ opacity: 1, scale: 1, x: "-50%", y: 0 }}
className="pointer-events-none absolute -top-2 left-1/2 z-10 whitespace-nowrap rounded-md bg-foreground px-3 py-1.5 font-medium text-background text-sm shadow-lg"
exit={
shouldReduceMotion
? { opacity: 0, transition: { duration: 0 } }
: { opacity: 0, scale: 0.95, y: 4 }
}
initial={
shouldReduceMotion
? { opacity: 1, x: "-50%", y: 0 }
: { opacity: 0, scale: 0.95, x: "-50%", y: 4 }
}
transition={
shouldReduceMotion
? { duration: 0 }
: {
bounce: 0.05,
duration: 0.25,
type: "spring" as const,
}
}
>
{logo.name}
{/* Tooltip arrow */}
<div className="absolute -bottom-1 left-1/2 h-2 w-2 -translate-x-1/2 rotate-45 bg-foreground" />
</motion.div>
) : null}
</AnimatePresence>
</motion.div>
);
if (logo.href) {
return (
<a
className="focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
href={logo.href}
rel="noopener noreferrer"
target="_blank"
>
{content}
</a>
);
}
return content;
}
export function LogoGrid({
title = "Trusted by innovative companies",
description = "Leading organizations rely on our platform to power their success",
logos = DEFAULT_LOGOS,
columns = 4,
}: LogoGridProps) {
const shouldReduceMotion = useReducedMotion();
const [isHoverDevice, setIsHoverDevice] = useState(false);
useEffect(() => {
const mediaQuery = window.matchMedia("(hover: hover) and (pointer: fine)");
setIsHoverDevice(mediaQuery.matches);
const handleChange = (e: MediaQueryListEvent) => {
setIsHoverDevice(e.matches);
};
mediaQuery.addEventListener("change", handleChange);
return () => mediaQuery.removeEventListener("change", handleChange);
}, []);
return (
<section className="bg-background py-20">
<div className="mx-auto max-w-6xl px-6">
<div className="mb-12 text-center">
<h2 className="mb-4 font-bold text-2xl text-foreground lg:text-3xl">
{title}
</h2>
<p className="text-foreground/70 text-lg">{description}</p>
</div>
<div
className={`grid ${COLUMN_CLASSES[columns]} gap-4 rounded-xl border border-border/50 bg-muted/30 p-4`}
>
{logos.map((logo, index) => (
<LogoItem
isHoverDevice={isHoverDevice}
key={`${logo.name}-${index}`}
logo={logo}
shouldReduceMotion={shouldReduceMotion}
/>
))}
</div>
</div>
</section>
);
}
export default LogoGrid;