Todos os componentes

Basic Toast

Basic Toast é um componente de feedback para React e Tailwind CSS, da biblioteca SmoothUI, com licença MIT. Copia e cola no teu projeto.

SmoothUI MIT feedback

O que é

Basic Toast é um componente de feedback 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 lucide-react

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.

Basic Toast

index.tsx
"use client";

import { AlertCircle, CheckCircle, Info, X, XCircle } from "lucide-react";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
import { useEffect, useState } from "react";
import { createPortal } from "react-dom";

export type ToastType = "success" | "error" | "info" | "warning";

export interface ToastProps {
  className?: string;
  duration?: number;
  isVisible?: boolean;
  message: string;
  onClose?: () => void;
  type?: ToastType;
}

const toastIcons = {
  error: <XCircle className="h-5 w-5 text-red-500" />,
  info: <Info className="h-5 w-5 text-blue-500" />,
  success: <CheckCircle className="h-5 w-5 text-emerald-500" />,
  warning: <AlertCircle className="h-5 w-5 text-amber-500" />,
};

const toastClasses = {
  error: "border-red-100 bg-red-50 dark:border-red-900 dark:bg-red-950",
  info: "border-blue-100 bg-blue-50 dark:border-blue-900 dark:bg-blue-950",
  success:
    "border-emerald-100 bg-emerald-50 dark:border-emerald-900 dark:bg-emerald-950",
  warning:
    "border-amber-100 bg-amber-50 dark:border-amber-900 dark:bg-amber-950",
};

export default function BasicToast({
  message,
  type = "info",
  duration = 3000,
  onClose,
  isVisible = true,
  className = "",
}: ToastProps) {
  const [visible, setVisible] = useState(isVisible);
  const [mounted, setMounted] = useState(false);
  const shouldReduceMotion = useReducedMotion();

  useEffect(() => {
    setMounted(true);
  }, []);

  useEffect(() => {
    setVisible(isVisible);
  }, [isVisible]);

  useEffect(() => {
    if (visible && duration > 0) {
      const timer = setTimeout(() => {
        setVisible(false);
        onClose?.();
      }, duration);
      return () => clearTimeout(timer);
    }
  }, [visible, duration, onClose]);

  if (!mounted) {
    return null;
  }

  const toastContent = (
    <AnimatePresence>
      {visible ? (
        <motion.div
          animate={
            shouldReduceMotion ? { opacity: 1 } : { opacity: 1, scale: 1, x: 0 }
          }
          className={`fixed top-4 right-4 z-50 flex w-80 items-center gap-3 rounded-lg border p-4 shadow-lg ${toastClasses[type]} ${className}`}
          exit={
            shouldReduceMotion
              ? { opacity: 0, transition: { duration: 0 } }
              : {
                  opacity: 0,
                  scale: 0.8,
                  transition: { duration: 0.15 },
                  x: 50,
                }
          }
          initial={
            shouldReduceMotion
              ? { opacity: 1 }
              : { opacity: 0, scale: 0.8, x: 50 }
          }
          transition={
            shouldReduceMotion
              ? { duration: 0 }
              : { bounce: 0.1, duration: 0.25, type: "spring" as const }
          }
        >
          <div className="flex-shrink-0">{toastIcons[type]}</div>
          <p className="flex-1 text-sm">{message}</p>
          <button
            className="flex-shrink-0 cursor-pointer rounded-full p-1 transition-colors hover:bg-black/5 dark:hover:bg-white/10"
            onClick={() => {
              setVisible(false);
              onClose?.();
            }}
            type="button"
          >
            <X className="h-4 w-4" />
          </button>
        </motion.div>
      ) : null}
    </AnimatePresence>
  );

  return createPortal(toastContent, document.body);
}

// Example of how to use this component:
export function ToastDemo() {
  const [showToast, setShowToast] = useState(false);
  const [toastType, setToastType] = useState<ToastType>("success");

  const handleShowToast = (type: ToastType) => {
    setToastType(type);
    setShowToast(true);
  };

  return (
    <div className="flex flex-col gap-4 p-4">
      <div className="flex flex-wrap gap-2">
        <button
          className="cursor-pointer rounded-md bg-emerald-500 px-3 py-1.5 text-sm text-white hover:bg-emerald-600"
          onClick={() => handleShowToast("success")}
          type="button"
        >
          Success Toast
        </button>
        <button
          className="cursor-pointer rounded-md bg-red-500 px-3 py-1.5 text-sm text-white hover:bg-red-600"
          onClick={() => handleShowToast("error")}
          type="button"
        >
          Error Toast
        </button>
        <button
          className="cursor-pointer rounded-md bg-amber-500 px-3 py-1.5 text-sm text-white hover:bg-amber-600"
          onClick={() => handleShowToast("warning")}
          type="button"
        >
          Warning Toast
        </button>
        <button
          className="cursor-pointer rounded-md bg-blue-500 px-3 py-1.5 text-sm text-white hover:bg-blue-600"
          onClick={() => handleShowToast("info")}
          type="button"
        >
          Info Toast
        </button>
      </div>

      <AnimatePresence>
        {showToast ? (
          <BasicToast
            duration={3000}
            message={`This is a ${toastType} message example!`}
            onClose={() => setShowToast(false)}
            type={toastType}
          />
        ) : null}
      </AnimatePresence>
    </div>
  );
}

Componentes parecidos