1import React, { useEffect, useState } from 'react';
2import { motion, AnimatePresence } from 'framer-motion';
3import { Box } from '@mui/system';
4
5export interface StarRatingProps {
6 rating?: number;
7 size?: number;
8}
9
10const StarRating = (props: StarRatingProps) => {
11 const { rating = 0, size = 16 } = props;
12 const [animated, setAnimated] = useState(false);
13 const totalStars = 5;
14
15 useEffect(() => {
16 const timer = setTimeout(() => setAnimated(true), 500);
17 return () => clearTimeout(timer);
18 }, []);
19
20 const Particle = ({ delay }: { delay: number }) => (
21 <Box sx={{ position: 'absolute', top: 2, left: 2 }}>
22 <Box
23 component={motion.div}
24 sx={{
25 position: 'absolute',
26 width: 2,
27 height: 2,
28 background: '#FFED76',
29 borderRadius: '50%',
30 }}
31 initial={{ scale: 0, opacity: 1 }}
32 animate={{
33 scale: [0, 1, 0],
34 opacity: [1, 1, 0],
35 x: [0, (Math.random() - 0.5) * 40],
36 y: [0, (Math.random() - 0.5) * 40],
37 }}
38 transition={{
39 duration: 0.6,
40 delay,
41 ease: 'easeOut',
42 }}
43 />
44 </Box>
45 );
46
47 const AnimatedStar = ({
48 filled,
49 index,
50 }: {
51 filled: boolean;
52 index: number;
53 }) => {
54 return (
55 <Box sx={{ position: 'relative' }}>
56 <motion.div
57 initial={{ scale: 1 }}
58 animate={
59 filled && animated
60 ? {
61 scale: [1, 1.2, 1],
62 rotate: [0, 10, -10, 0],
63 }
64 : {}
65 }
66 transition={{
67 duration: 0.3,
68 delay: index * 0.1,
69 ease: 'easeOut',
70 }}
71 >
72 <Box
73 component={motion.svg}
74 sx={{
75 width: size,
76 height: size,
77 color: '#FFED76',
78 }}
79 xmlns="http://www.w3.org/2000/svg"
80 width="24"
81 height="24"
82 viewBox="0 0 24 24"
83 stroke="currentColor"
84 strokeWidth="2"
85 strokeLinecap="round"
86 strokeLinejoin="round"
87 >
88 <motion.path
89 d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z"
90 initial={{ fill: 'transparent' }}
91 animate={
92 filled && animated
93 ? {
94 fill: ['transparent', '#FFED76'],
95 }
96 : {}
97 }
98 transition={{
99 duration: 0.2,
100 delay: index * 0.1,
101 ease: 'easeOut',
102 }}
103 />
104 </Box>
105 </motion.div>
106
107 {filled && animated && (
108 <AnimatePresence>
109 {Array.from({ length: 6 }).map((_, i) => (
110 <Particle key={i} delay={index * 0.1 + i * 0.05 + 0.2} />
111 ))}
112 </AnimatePresence>
113 )}
114 </Box>
115 );
116 };
117
118 return (
119 <Box sx={{ display: 'flex', gap: '4px' }}>
120 {Array.from({ length: totalStars }).map((_, index) => (
121 <AnimatedStar key={index} filled={index < rating} index={index} />
122 ))}
123 </Box>
124 );
125};
126
127export default StarRating;