E-Commerce
Review Ratings Card - Customer Feedback
Monitor product reviews and customer ratings. Track review trends and customer sentiment.
Review Ratings Card
The Review Ratings Card Monitor product reviews and customer ratings.
Preview
Installation
ash npx shadcn@latest add https://vectormotion.vercel.app/registry/review-ratings-card.json
Review Ratings Card
'use client';import React from "react";import { Star, TrendingUp } from "lucide-react";import { motion } from "motion/react";import { clsx, type ClassValue } from "clsx";import { twMerge } from "tailwind-merge";function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs));}interface Rating { stars: number; count: number; percentage: number; [key: string]: any;}interface ReviewRatingsCardProps { className?: string; title?: string; description?: string; avgRating?: number; ratings?: Rating[];}const DEFAULT_TITLE = "Review Ratings";const DEFAULT_DESCRIPTION = "Customer Feedback";const DEFAULT_AVG_RATING = 4.4;const DEFAULT_RATINGS: Rating[] = [ { stars: 5, count: 1250, percentage: 62 }, { stars: 4, count: 480, percentage: 24 }, { stars: 3, count: 180, percentage: 9 }, { stars: 2, count: 60, percentage: 3 }, { stars: 1, count: 40, percentage: 2 },];export const ReviewRatingsCard: React.FC<ReviewRatingsCardProps> = ({ className = "", title = DEFAULT_TITLE, description = DEFAULT_DESCRIPTION, avgRating = DEFAULT_AVG_RATING, ratings = DEFAULT_RATINGS,}) => { const isInteractive = true; const index = 30; const totalReviews = ratings.reduce((sum, r) => sum + r.count, 0); return ( <motion.div layoutId={isInteractive ? `card-${index}-${title}` : undefined} transition={{ duration: 0.4, ease: "easeOut" }} className={cn( "relative overflow-hidden rounded-xl border border-border bg-card text-card-foreground p-6 shadow-sm transition-all flex flex-col group", isInteractive ? "cursor-pointer hover:border-zinc-300 dark:hover:border-zinc-700" : "", className, )} > <div className="mb-4 flex items-start justify-between relative z-10"> <div> <h3 className="font-semibold text-lg tracking-tight text-foreground"> {title} </h3> {description && ( <p className="text-sm text-muted-foreground mt-1">{description}</p> )} </div> <div className="rounded-full bg-zinc-100 dark:bg-zinc-800 p-2 text-foreground flex items-center justify-center"> <Star className="h-5 w-5 text-amber-500 fill-amber-500" /> </div> </div> <div className="relative z-10 flex-1"> <div className="flex items-baseline gap-2 mb-2"> <div className="text-3xl font-bold text-foreground">{avgRating}</div> <div className="flex gap-0.5"> {[1, 2, 3, 4, 5].map((star) => ( <Star key={star} className={`h-4 w-4 ${star <= Math.floor(avgRating) ? "text-amber-500 fill-amber-500" : "text-zinc-300 dark:text-zinc-600" }`} /> ))} </div> </div> <p className="text-sm text-muted-foreground mb-4"> {totalReviews.toLocaleString()} reviews </p> <div className="space-y-2"> {ratings.map((rating, idx) => ( <div key={idx} className="flex items-center gap-2"> <div className="flex items-center gap-1 w-12"> <span className="text-xs text-zinc-600 dark:text-muted-foreground"> {rating.stars} </span> <Star className="h-3 w-3 text-amber-500 fill-amber-500" /> </div> <div className="flex-1 bg-zinc-100 dark:bg-zinc-800 rounded-full h-2 overflow-hidden"> <motion.div initial={{ width: 0 }} animate={{ width: `${rating.percentage}%` }} transition={{ duration: 1, delay: idx * 0.1 }} className="h-full bg-amber-500 rounded-full" /> </div> <span className="text-xs text-muted-foreground w-12 text-right"> {rating.count} </span> </div> ))} </div> </div> </motion.div> );};Usage
This component is a data-rich dashboard card displaying e-commerce metrics with animated visualizations and dark mode support. Perfect for dashboards, landing pages, and analytics interfaces.
Prop
Type