NSA

Nur Shah Alam

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

Marron 5 Concert
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; 85

2. 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 Sekarang

Hanya 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

Let's collaborate and make your project stand out!


Email Me