| // src/components/article/ArticleStats.tsx | |
| import { TrendingUp, Award, Star, Users, Eye, Heart, MessageCircle, Bookmark, Share2, Activity, BarChart3 } from "lucide-react"; | |
| import { motion } from "framer-motion"; | |
| interface ArticleStatsProps { | |
| views: number; | |
| likes: number; | |
| comments: number; | |
| bookmarks: number; | |
| shares: number; | |
| citations: number; | |
| impactScore?: number; | |
| percentile?: number; | |
| trending?: boolean; | |
| className?: string; | |
| variant?: "grid" | "list" | "minimal"; | |
| showProgress?: boolean; | |
| } | |
| export function ArticleStats({ | |
| views, | |
| likes, | |
| comments, | |
| bookmarks, | |
| shares, | |
| citations, | |
| impactScore, | |
| percentile, | |
| trending, | |
| className = "", | |
| variant = "grid", | |
| showProgress = false | |
| }: ArticleStatsProps) { | |
| const getScoreColor = (score: number) => { | |
| if (score >= 80) return "text-green-600"; | |
| if (score >= 60) return "text-blue-600"; | |
| if (score >= 40) return "text-yellow-600"; | |
| return "text-red-600"; | |
| }; | |
| const statsItems = [ | |
| { icon: Eye, label: "Views", value: views.toLocaleString(), color: "blue" }, | |
| { icon: Heart, label: "Likes", value: likes.toLocaleString(), color: "pink" }, | |
| { icon: MessageCircle, label: "Comments", value: comments.toLocaleString(), color: "green" }, | |
| { icon: Bookmark, label: "Bookmarks", value: bookmarks.toLocaleString(), color: "purple" }, | |
| { icon: Share2, label: "Shares", value: shares.toLocaleString(), color: "orange" }, | |
| { icon: TrendingUp, label: "Citations", value: citations.toLocaleString(), color: "teal" }, | |
| ]; | |
| if (variant === "list") { | |
| return ( | |
| <div className={`space-y-3 ${className}`}> | |
| {statsItems.map((stat) => ( | |
| <div key={stat.label} className="flex items-center justify-between"> | |
| <div className="flex items-center gap-2 text-gray-600 dark:text-gray-400"> | |
| <stat.icon className="h-4 w-4" /> | |
| <span className="text-sm">{stat.label}</span> | |
| </div> | |
| <span className="font-semibold text-gray-900 dark:text-white">{stat.value}</span> | |
| </div> | |
| ))} | |
| {impactScore && ( | |
| <div className="pt-2 border-t border-gray-100 dark:border-gray-800"> | |
| <div className="flex items-center justify-between mb-1"> | |
| <div className="flex items-center gap-2 text-gray-600 dark:text-gray-400"> | |
| <Award className="h-4 w-4" /> | |
| <span className="text-sm">Impact Score</span> | |
| </div> | |
| <span className={`font-bold ${getScoreColor(impactScore)}`}>{impactScore}</span> | |
| </div> | |
| {showProgress && ( | |
| <div className="h-1.5 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden"> | |
| <div | |
| className="h-full bg-gradient-to-r from-primary to-secondary rounded-full transition-all" | |
| style={{ width: `${impactScore}%` }} | |
| /> | |
| </div> | |
| )} | |
| </div> | |
| )} | |
| {percentile && ( | |
| <div className="text-xs text-gray-500"> | |
| <TrendingUp className="inline h-3 w-3 mr-1" /> | |
| Top {percentile}% of articles | |
| </div> | |
| )} | |
| {trending && ( | |
| <div className="text-xs text-orange-500 flex items-center gap-1"> | |
| <Activity className="h-3 w-3 animate-pulse" /> | |
| Trending today | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| } | |
| if (variant === "minimal") { | |
| return ( | |
| <div className={`flex items-center gap-4 text-sm text-gray-500 ${className}`}> | |
| <span className="flex items-center gap-1"> | |
| <Eye className="h-4 w-4" /> | |
| {views} | |
| </span> | |
| <span className="flex items-center gap-1"> | |
| <Heart className="h-4 w-4" /> | |
| {likes} | |
| </span> | |
| <span className="flex items-center gap-1"> | |
| <MessageCircle className="h-4 w-4" /> | |
| {comments} | |
| </span> | |
| </div> | |
| ); | |
| } | |
| // Grid variant (default) | |
| return ( | |
| <div className={`grid grid-cols-3 gap-4 ${className}`}> | |
| {statsItems.map((stat) => ( | |
| <motion.div | |
| key={stat.label} | |
| whileHover={{ y: -2 }} | |
| className="text-center p-3 rounded-xl bg-gray-50 dark:bg-gray-800/50 hover:bg-gray-100 dark:hover:bg-gray-800 transition" | |
| > | |
| <stat.icon className={`h-5 w-5 mx-auto mb-1 text-${stat.color}-500`} /> | |
| <div className="text-lg font-bold text-gray-900 dark:text-white">{stat.value}</div> | |
| <div className="text-xs text-gray-500">{stat.label}</div> | |
| </motion.div> | |
| ))} | |
| </div> | |
| ); | |
| } |
Xet Storage Details
- Size:
- 4.6 kB
- Xet hash:
- 19caf6df22c3735f5824f415d8eed70ef503d702f87bb0a39326a9c0eb4381ca
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.