Docs
Comment Reply Card
This new React component allows users to submit comments through an input field. Once a comment is submitted, it dynamically appears at the top of the comment list.
Installation
Install dependencies
npm install framer-motion lucide-react
Run the following command
It will create a new file comment-reply-card.tsx
inside the components/animata/card
directory.
mkdir -p components/animata/card && touch components/animata/card/comment-reply-card.tsx
Paste the code
Open the newly created file and paste the following code:
"use client";
import React, { useEffect, useRef, useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { Check, X } from "lucide-react";
interface Comment {
id: number;
user: string;
text: string[];
time: string;
avatarColor: string;
}
const containerVariants = {
hidden: { height: "auto" },
visible: { height: "auto", transition: { duration: 0.5, ease: "easeInOut" } },
};
const commentVariants = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0, transition: { duration: 0.3 } },
exit: { opacity: 0, y: -20, transition: { duration: 0.3 } },
};
export default function CommentReplyCard({ initialComments }: { initialComments: Comment[] }) {
const [comments, setComments] = useState<Comment[]>([...initialComments]);
const [newComment, setNewComment] = useState<string>("");
const [isAnimating, setIsAnimating] = useState(false);
const containerRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
const handleAddComment = () => {
if (newComment.trim() === "") return;
setIsAnimating(true);
const newCommentData: Comment = {
id: comments.length + 1,
user: "Emily",
text: [newComment],
time: "now",
avatarColor: "#e84b9d",
};
setTimeout(() => {
setComments((prevComments) => [...prevComments, newCommentData]);
setNewComment("");
setTimeout(() => {
setIsAnimating(false);
if (inputRef.current) {
inputRef.current.focus();
}
}, 300);
}, 300);
};
useEffect(() => {
if (containerRef.current) {
containerRef.current.scrollTop = containerRef.current.scrollHeight;
}
}, [comments]);
return (
<div className="mx-auto max-h-full min-h-96 w-full max-w-md">
<div
className="relative overflow-hidden rounded-lg bg-[#202020] shadow-md"
style={{ paddingBottom: "10px" }}
>
<div className="m-2 flex items-center justify-between rounded-3xl bg-[#3d3d40] px-4 py-1 shadow-2xl">
<div className="flex items-center space-x-2">
<CommentIcon />
<span className="text-lg font-semibold text-white">Comment</span>
</div>
<div className="flex space-x-2">
<button className="rounded-full p-1 text-gray-400 hover:bg-gray-700 hover:text-white">
<Check size={20} />
</button>
<button className="rounded-full p-1 text-gray-400 hover:bg-gray-700 hover:text-white">
<X size={20} />
</button>
</div>
</div>
{/* Comment Section */}
<motion.div
variants={containerVariants}
initial="hidden"
animate="visible"
style={{ maxHeight: "60vh" }}
className="relative overflow-hidden"
>
<motion.div
ref={containerRef}
className="no-scrollbar max-h-60 overflow-y-auto p-2"
initial={{ y: 0 }}
animate={{ y: 0 }}
transition={{ duration: 0.5, ease: "easeInOut" }}
>
<AnimatePresence initial={false}>
{comments.map((comment) => (
<motion.div
key={comment.id}
variants={commentVariants}
initial="hidden"
animate="visible"
exit="exit"
className="mb-4"
layout
>
<div className="flex items-center">
<div
className="mr-3 h-8 w-8 overflow-hidden rounded-full"
style={{ backgroundColor: comment.avatarColor }}
>
<svg
className="h-full w-full text-white opacity-70"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z"
clipRule="evenodd"
></path>
</svg>
</div>
<div>
<span className="font-semibold text-white">{comment.user}</span>
<span className="ml-2 text-sm text-gray-400">{comment.time}</span>
</div>
</div>
<div className="mt-1 pl-11 text-white">
{comment.text.map((text, textIndex) => (
<motion.p
key={`${comment.id}-${textIndex}`}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.3, delay: 0.1 }}
>
{text}
</motion.p>
))}
</div>
</motion.div>
))}
</AnimatePresence>
</motion.div>
</motion.div>
</div>
{/* Input Box */}
<AnimatePresence>
{!isAnimating && (
<motion.div
className="absolute bottom-[-10] left-0 right-0 mx-auto mt-8 w-full max-w-[470px] rounded-lg p-2"
initial={{ opacity: 0, y: 60 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -60 }}
transition={{ duration: 0.3 }}
>
<div className="relative flex items-center rounded-3xl bg-[#3d3d40] p-1">
<div className="mr-3 h-8 w-8 overflow-hidden rounded-full bg-[#e84b9d]">
<svg
className="h-full w-full text-white opacity-70"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z"
clipRule="evenodd"
></path>
</svg>
</div>
<input
ref={inputRef}
type="text"
className="flex-grow bg-transparent text-white placeholder-gray-400 focus:outline-none"
placeholder="Reply"
value={newComment}
onChange={(e) => setNewComment(e.target.value)}
onKeyPress={(e) => {
if (e.key === "Enter") {
handleAddComment();
}
}}
/>
<button
onClick={handleAddComment}
className="ml-2 rounded-3xl bg-yellow-400 px-4 py-1 font-semibold text-black transition hover:bg-yellow-500"
>
Send
</button>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
);
}
const CommentIcon: React.FC = () => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="h-6 w-6 text-white"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M12 20.25c4.97 0 9-3.694 9-8.25s-4.03-8.25-9-8.25S3 7.444 3 12c0 2.104.859 4.023 2.273 5.48.432.447.74 1.04.586 1.641a4.483 4.483 0 0 1-.923 1.785A5.969 5.969 0 0 0 6 21c1.282 0 2.47-.402 3.445-1.087.81.22 1.668.337 2.555.337Z"
/>
</svg>
);
};
Credits
Built by Prince Yadav