Notification Badge
Notification Badge é 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 é
Notification Badge é 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.
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.
Notification Badge
"use client";
import { cn } from "@/lib/utils";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
import type { ReactNode } from "react";
import { useEffect, useRef, useState } from "react";
export interface NotificationBadgeProps {
children?: ReactNode;
className?: string;
count?: number;
max?: number;
ping?: boolean;
position?: "top-right" | "top-left" | "bottom-right" | "bottom-left";
showZero?: boolean;
status?: "online" | "offline" | "busy" | "away";
variant?: "dot" | "count" | "status";
}
const statusColors = {
away: "bg-amber-500",
busy: "bg-red-500",
offline: "bg-gray-400",
online: "bg-emerald-500",
};
const positionClasses = {
"bottom-left": "-bottom-1 -left-1",
"bottom-right": "-bottom-1 -right-1",
"top-left": "-top-1 -left-1",
"top-right": "-top-1 -right-1",
};
const AnimatedCount = ({
value,
max,
shouldReduceMotion,
}: {
value: number;
max: number;
shouldReduceMotion: boolean | null;
}) => {
const displayValue = value > max ? `${max}+` : value.toString();
const prevValueRef = useRef(value);
const direction = value > prevValueRef.current ? 1 : -1;
useEffect(() => {
prevValueRef.current = value;
}, [value]);
if (shouldReduceMotion) {
return <span className="font-medium leading-none">{displayValue}</span>;
}
return (
<span className="relative overflow-hidden font-medium leading-none">
<AnimatePresence initial={false} mode="popLayout">
<motion.span
animate={{ opacity: 1, y: 0 }}
className="inline-block"
exit={{ opacity: 0, y: direction * -12 }}
initial={{ opacity: 0, y: direction * 12 }}
key={value}
transition={{ bounce: 0.1, duration: 0.3, type: "spring" as const }}
>
{displayValue}
</motion.span>
</AnimatePresence>
</span>
);
};
const NotificationBadge = ({
variant = "dot",
count = 0,
max = 99,
status = "online",
showZero = false,
ping = false,
position = "top-right",
children,
className,
}: NotificationBadgeProps) => {
const shouldReduceMotion = useReducedMotion();
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
const shouldShow =
variant === "dot" ||
variant === "status" ||
(variant === "count" && (count > 0 || showZero));
setIsVisible(shouldShow);
}, [variant, count, showZero]);
const getBadgeClasses = () => {
if (variant === "dot") {
return "h-2.5 w-2.5";
}
if (variant === "status") {
return "h-3 w-3";
}
const displayValue = count > max ? `${max}+` : count.toString();
if (displayValue.length === 1) {
return "h-5 w-5 text-xs";
}
if (displayValue.length === 2) {
return "h-5 min-w-5 px-1 text-xs";
}
return "h-5 min-w-6 px-1 text-xs";
};
const getBackgroundColor = () => {
if (variant === "status") {
return statusColors[status];
}
return "bg-brand";
};
const badgeElement = (
<AnimatePresence mode="wait">
{isVisible ? (
<motion.span
animate={{ opacity: 1, scale: 1 }}
className={cn(
"absolute flex items-center justify-center rounded-full text-white",
getBackgroundColor(),
getBadgeClasses(),
positionClasses[position],
variant === "status" && "ring-2 ring-white dark:ring-gray-900",
className
)}
exit={
shouldReduceMotion
? { opacity: 0, transition: { duration: 0 } }
: { opacity: 0, scale: 0, transition: { duration: 0.15 } }
}
initial={
shouldReduceMotion ? { opacity: 1 } : { opacity: 0, scale: 0 }
}
transition={
shouldReduceMotion
? { duration: 0 }
: { bounce: 0.2, duration: 0.25, type: "spring" as const }
}
>
{variant === "count" && (
<AnimatedCount
max={max}
shouldReduceMotion={shouldReduceMotion}
value={count}
/>
)}
{ping && !shouldReduceMotion && (
<span
aria-hidden="true"
className={cn(
"absolute inset-0 animate-ping rounded-full opacity-75",
getBackgroundColor()
)}
/>
)}
</motion.span>
) : null}
</AnimatePresence>
);
if (!children) {
return (
<span className="relative inline-flex">
<span className="h-4 w-4" />
{badgeElement}
</span>
);
}
return (
<span className="relative inline-flex">
{children}
{badgeElement}
</span>
);
};
export default NotificationBadge;