Sharing Components
Build UIs using a collection of components I developed. Designed to be modular, lightweight, and highly customizable — perfect for both personal and professional projects. Each component is built with Tailwind CSS for flexible styling, and motion.dev (Framer Motion) for smooth, responsive animations.
1. Card Event
Event Popular

Dec
24
Marron 5 Concert
event-card.tsx
1 import { AiOutlineHeart } from "react-icons/ai";
2 import { motion } from "framer-motion";
3
4 type EventCardProps = {
5 imageUrl: string;
6 eventMonth: string;
7 eventDay: string | number;
8 eventName: string;
9 eventCategory?: string;
10 isPopular?: boolean;
11 onFavoriteClick?: () => void;
12 };
13
14 const EventCard = ({
15 imageUrl,
16 eventMonth,
17 eventDay,
18 eventName,
19 eventCategory = "Event Popular",
20 isPopular = false,
21 onFavoriteClick,
22 }: EventCardProps) => {
23 return (
24 <div className="relative w-72 overflow-hidden rounded-[2rem] bg-white shadow-md">
25 <div className="relative h-full">
26 {/* Heart Icon Top Right */}
27 <button
28 className="absolute right-4 top-4 z-10 rounded-full p-2 text-white shadow"
29 aria-label="Add to favorites"
30 type="button"
31 onClick={onFavoriteClick}
32 >
33 <AiOutlineHeart size={24} />
34 </button>
35
36 {isPopular && (
37 <div className="absolute bottom-4 left-4 flex flex-row items-center gap-2 rounded-full p-1">
38 {/* Animated Circle */}
39 <div className="rounded-full bg-white/10 p-1">
40 <motion.div
41 className="h-4 w-4 rounded-full bg-white"
42 animate={{
43 scale: [0.7, 0.8, 0.7],
44 }}
45 transition={{
46 duration: 1.5,
47 repeat: Infinity,
48 }}
49 />
50 </div>
51 {/* Category Label */}
52 <p className="text-xs font-medium text-white">{eventCategory}</p>
53 </div>
54 )}
55
56 {/* Image Event */}
57 <img
58 src={imageUrl}
59 alt={eventName}
60 className="h-full w-full rounded-[2rem] object-cover shadow-lg shadow-slate-800/30"
61 />
62 </div>
63
64 {/* Date and Title */}
65 <div className="flex items-center justify-center gap-2 py-4">
66 {/* Date */}
67 <div className="text-center">
68 <div className="text-sm uppercase tracking-wide text-gray-500">
69 {eventMonth}
70 </div>
71 <div className="text-3xl font-bold text-gray-800">{eventDay}</div>
72 </div>
73
74 {/* Vertical Line */}
75 <div className="mx-2 h-10 w-px bg-gray-300"></div>
76
77 {/* Event Name */}
78 <div className="text-xl font-medium text-gray-800">{eventName}</div>
79 </div>
80 </div>
81 );
82 };
83
84 export default EventCard;
852. Card Banner Pop Up
Liburan Impian di Villa Eksklusif!
Nikmati penginapan mewah di villa dengan kolam renang pribadi, mulai dari Rp1.500.000 per malam untuk kenyamanan istimewa.
Pesan SekarangHanya 3 Hari Lagi untuk Diskon 25%!
banner-pop-up.tsx
1 import { useEffect, useRef } from "react";
2 import { motion, AnimatePresence } from "framer-motion";
3 import { X } from "lucide-react";
4
5 interface Props {
6 isOpen: boolean;
7 headline: string;
8 description: string;
9 buttonText: string;
10 footerText: string;
11 onClose: () => void;
12 }
13
14 const BannerPopup = ({
15 isOpen,
16 headline,
17 description,
18 buttonText,
19 footerText,
20 onClose,
21 }: Props) => {
22 const modalRef = useRef<HTMLDivElement>(null);
23
24 useEffect(() => {
25 if (isOpen) {
26 if (modalRef.current) modalRef.current.focus();
27
28 // lock scroll body
29 document.body.classList.add("overflow-hidden");
30 } else {
31 // unlock scroll body
32 document.body.classList.remove("overflow-hidden");
33 }
34
35 // Cleanup if the component unmounts or is closed
36 return () => {
37 document.body.classList.remove("overflow-hidden");
38 };
39 }, [isOpen]);
40
41 return (
42 <AnimatePresence>
43 {isOpen && (
44 <motion.div
45 initial={{ opacity: 0 }}
46 animate={{ opacity: 1 }}
47 exit={{ opacity: 0 }}
48 transition={{ duration: 0.3 }}
49 role="dialog"
50 aria-modal="true"
51 aria-labelledby="popup-title"
52 className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm"
53 >
54 <motion.div
55 ref={modalRef}
56 initial={{ opacity: 0, y: 80, scale: 0.95 }}
57 animate={{ opacity: 1, y: 0, scale: 1 }}
58 exit={{ opacity: 0, y: 80, scale: 0.95 }}
59 transition={{
60 duration: 0.5,
61 ease: [0.4, 0, 0.2, 1], // Custom easing for smooth animation
62 }}
63 tabIndex={-1}
64 className="relative mx-4 w-full max-w-lg transform rounded-xl bg-gradient-to-br from-[#0D7834] to-[#1CD44E] p-2 shadow-lg focus:outline-none md:mx-0"
65 style={{ boxShadow: "8px 8px 2px #0C7B47" }}
66 >
67 <div className="relative w-full rounded-xl bg-gradient-to-br from-[#1C5452] to-[#1CD94E] p-4">
68 {/* Button Close */}
69 <button
70 onClick={onClose}
71 aria-label="Close pop-up"
72 className="text-md absolute -right-4 -top-4 rounded-full bg-[#E9E9E9] p-2 text-green-500 hover:text-green-600 focus:outline-none"
73 >
74 <X size={18} />
75 </button>
76
77 {/* Modal Content */}
78 <div className="my-4 flex flex-col items-center gap-4 text-center">
79 <h2
80 id="popup-title"
81 className="text-2xl font-bold text-white md:text-3xl"
82 >
83 {headline}
84 </h2>
85 <p
86 className="text-white"
87 dangerouslySetInnerHTML={{ __html: description }}
88 />
89 <motion.a
90 // href="#" // Adjust the link as needed
91 className="inline-block cursor-pointer rounded-full border border-gray-200 bg-white px-4 py-2 text-base font-medium text-green-500"
92 style={{ boxShadow: "4px 4px 0 #E9E9E9" }}
93 whileHover={{
94 scale: 1.05,
95 boxShadow: "6px 6px 0 #E9E9E9",
96 }}
97 whileTap={{
98 scale: 0.98,
99 boxShadow: "2px 2px 0 #E9E9E9",
100 }}
101 transition={{
102 type: "spring",
103 stiffness: 400,
104 damping: 25,
105 }}
106 >
107 {buttonText}
108 </motion.a>
109
110 <p className="text-sm text-white/80">{footerText}</p>
111 </div>
112 </div>
113 </motion.div>
114 </motion.div>
115 )}
116 </AnimatePresence>
117 );
118 };
119
120 export default BannerPopup;
121