Todos os componentes

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.

Abrir no catálogo

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

index.tsx
"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;

Componentes parecidos