DotGrid.tsx66 lines · main
| 1 | import { motion } from 'framer-motion' |
| 2 | |
| 3 | interface DotGridProps { |
| 4 | rows: number |
| 5 | columns: number |
| 6 | count: number |
| 7 | } |
| 8 | |
| 9 | export const DotGrid = ({ rows, columns, count }: DotGridProps) => { |
| 10 | const container = { |
| 11 | hidden: { opacity: 1 }, |
| 12 | visible: { |
| 13 | opacity: 1, |
| 14 | transition: { |
| 15 | staggerChildren: 0.05, |
| 16 | delayChildren: 0.05, |
| 17 | }, |
| 18 | }, |
| 19 | } |
| 20 | |
| 21 | const item = { |
| 22 | hidden: { opacity: 0 }, |
| 23 | visible: { |
| 24 | opacity: [1, 0.5, 1], |
| 25 | transition: { |
| 26 | repeat: Infinity, |
| 27 | duration: 0.5, |
| 28 | repeatDelay: 1.5, |
| 29 | ease: 'easeInOut', |
| 30 | }, |
| 31 | }, |
| 32 | } |
| 33 | |
| 34 | return ( |
| 35 | <div |
| 36 | className="relative w-full h-full" |
| 37 | style={{ |
| 38 | maskImage: 'linear-gradient(to bottom, black, transparent)', |
| 39 | WebkitMaskImage: 'linear-gradient(to bottom, black, transparent)', |
| 40 | }} |
| 41 | > |
| 42 | <motion.div |
| 43 | className="grid w-full h-full justify-between items-space-between items-start" |
| 44 | style={{ |
| 45 | gridTemplateColumns: `repeat(${columns}, 1px)`, |
| 46 | gridTemplateRows: `repeat(${rows}, 1fr)`, |
| 47 | rowGap: 'auto', |
| 48 | }} |
| 49 | variants={container} |
| 50 | initial="hidden" |
| 51 | animate="visible" |
| 52 | aria-label={`Grid of ${rows * columns} dots, ${count} highlighted`} |
| 53 | > |
| 54 | {Array.from({ length: rows * columns }).map((_, index) => { |
| 55 | return ( |
| 56 | <motion.div |
| 57 | key={index} |
| 58 | variants={item} |
| 59 | className={`w-px h-px rounded-full bg-foreground`} |
| 60 | /> |
| 61 | ) |
| 62 | })} |
| 63 | </motion.div> |
| 64 | </div> |
| 65 | ) |
| 66 | } |