![]() Server : Apache/2 System : Linux server-15-235-50-60 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64 User : gositeme ( 1004) PHP Version : 8.2.29 Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname Directory : /home/gositeme/domains/lavocat.quebec/private_html/src/components/ |
import React, { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { Smile, Heart, Laugh, Frown, Angry, Zap, ThumbsUp, ThumbsDown, Star, Shield, Scale, Gavel } from 'lucide-react';
interface Reaction {
type: string;
emoji: string;
icon: React.ReactNode;
color: string;
label: string;
}
interface ReactionPickerProps {
onReaction: (reactionType: string) => void;
currentReactions: Array<{
reactionType: string;
count: number;
userReacted: boolean;
}>;
commentId: string;
className?: string;
}
const REACTIONS: Reaction[] = [
{
type: 'like',
emoji: '👍',
icon: <ThumbsUp className="h-4 w-4" />,
color: 'bg-blue-500 hover:bg-blue-600',
label: 'Like'
},
{
type: 'love',
emoji: '❤️',
icon: <Heart className="h-4 w-4" />,
color: 'bg-red-500 hover:bg-red-600',
label: 'Love'
},
{
type: 'laugh',
emoji: '😂',
icon: <Laugh className="h-4 w-4" />,
color: 'bg-yellow-500 hover:bg-yellow-600',
label: 'Laugh'
},
{
type: 'wow',
emoji: '😮',
icon: <Zap className="h-4 w-4" />,
color: 'bg-purple-500 hover:bg-purple-600',
label: 'Wow'
},
{
type: 'sad',
emoji: '😢',
icon: <Frown className="h-4 w-4" />,
color: 'bg-gray-500 hover:bg-gray-600',
label: 'Sad'
},
{
type: 'angry',
emoji: '😠',
icon: <Angry className="h-4 w-4" />,
color: 'bg-orange-500 hover:bg-orange-600',
label: 'Angry'
},
{
type: 'dislike',
emoji: '👎',
icon: <ThumbsDown className="h-4 w-4" />,
color: 'bg-red-600 hover:bg-red-700',
label: 'Dislike'
},
{
type: 'star',
emoji: '⭐',
icon: <Star className="h-4 w-4" />,
color: 'bg-yellow-400 hover:bg-yellow-500',
label: 'Star'
},
{
type: 'justice',
emoji: '⚖️',
icon: <Scale className="h-4 w-4" />,
color: 'bg-green-600 hover:bg-green-700',
label: 'Justice'
},
{
type: 'gavel',
emoji: '🔨',
icon: <Gavel className="h-4 w-4" />,
color: 'bg-gray-700 hover:bg-gray-800',
label: 'Gavel'
},
{
type: 'shield',
emoji: '🛡️',
icon: <Shield className="h-4 w-4" />,
color: 'bg-blue-700 hover:bg-blue-800',
label: 'Protect'
}
];
const ReactionPicker: React.FC<ReactionPickerProps> = ({
onReaction,
currentReactions,
commentId,
className = ''
}) => {
const [isOpen, setIsOpen] = useState(false);
const [hoveredReaction, setHoveredReaction] = useState<string | null>(null);
const handleReaction = (reactionType: string) => {
onReaction(reactionType);
setIsOpen(false);
};
const getReactionCount = (type: string) => {
const reaction = currentReactions.find(r => r.reactionType === type);
return reaction?.count || 0;
};
const hasUserReacted = (type: string) => {
const reaction = currentReactions.find(r => r.reactionType === type);
return reaction?.userReacted || false;
};
const getTopReactions = () => {
return currentReactions
.filter(r => r.count > 0)
.sort((a, b) => b.count - a.count)
.slice(0, 3);
};
return (
<div className={`relative ${className}
{/* Reaction Display */}
<div className="flex items-center gap-1">
{getTopReactions().map((reaction) => {
const reactionData = REACTIONS.find(r => r.type === reaction.reactionType);
if (!reactionData) return null;
return (
<motion.button
key={reaction.reactionType}
onClick={() => handleReaction(reaction.reactionType)}
className={
reaction.userReacted
? 'bg-blue-100 text-blue-700 border border-blue-200'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200'
}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
<span className="text-sm">{reactionData.emoji}</span>
<span className="font-medium">{reaction.count}</span>
</motion.button>
);
})}
{/* Reaction Picker Toggle */}
<motion.button
onClick={() => setIsOpen(!isOpen)}
className="flex items-center gap-1 px-2 py-1 rounded-full text-gray-500 hover:text-gray-700 hover:bg-gray-100 transition-colors"
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
<Smile className="h-4 w-4" />
</motion.button>
</div>
{/* Reaction Picker Popup */}
<AnimatePresence>
{isOpen && (
<motion.div
initial={{ opacity: 0, scale: 0.8, y: 10 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.8, y: 10 }}
className="absolute bottom-full left-0 mb-2 bg-white rounded-lg shadow-lg border border-gray-200 p-2 z-50"
>
<div className="grid grid-cols-4 gap-1">
{REACTIONS.map((reaction) => (
<motion.button
key={reaction.type}
onClick={() => handleReaction(reaction.type)}
onMouseEnter={() => setHoveredReaction(reaction.type)}
onMouseLeave={() => setHoveredReaction(null)}
className={
hasUserReacted(reaction.type)
? 'bg-blue-100 text-blue-700'
: 'hover:bg-gray-100 text-gray-600'
}
whileHover={{ scale: 1.2 }}
whileTap={{ scale: 0.9 }}
>
<span className="text-lg">{reaction.emoji}</span>
{/* Tooltip */}
<AnimatePresence>
{hoveredReaction === reaction.type && (
<motion.div
initial={{ opacity: 0, y: 5 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 5 }}
className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-1 px-2 py-1 bg-gray-900 text-white text-xs rounded whitespace-nowrap z-10"
>
{reaction.label}
{getReactionCount(reaction.type) > 0 && (
<span className="ml-1">({getReactionCount(reaction.type)})</span>
)}
</motion.div>
)}
</AnimatePresence>
</motion.button>
))}
</div>
</motion.div>
)}
</AnimatePresence>
{/* Backdrop to close picker */}
{isOpen && (
<div
className="fixed inset-0 z-40"
onClick={() => setIsOpen(false)}
/>
)}
</div>
);
};
export default ReactionPicker;