11import { motion , useMotionValue , useTransform } from 'motion/react' ;
2- import { useState } from 'react' ;
2+ import { useState , useEffect } from 'react' ;
33import './Stack.css' ;
44
5- function CardRotate ( { children, onSendToBack, sensitivity} ) {
5+ function CardRotate ( { children, onSendToBack, sensitivity, disableDrag = false } ) {
66const x = useMotionValue ( 0 ) ;
77const y = useMotionValue ( 0 ) ;
88const rotateX = useTransform ( y , [ - 100 , 100 ] , [ 60 , - 60 ] ) ;
@@ -17,6 +17,17 @@ function CardRotate({ children, onSendToBack, sensitivity }) {
1717}
1818}
1919
20+ if ( disableDrag ) {
21+ return (
22+ < motion . div
23+ className = "card-rotate-disabled"
24+ style = { { x :0 , y :0 } }
25+ >
26+ { children }
27+ </ motion . div >
28+ ) ;
29+ }
30+
2031return (
2132< motion . div
2233className = "card-rotate"
@@ -35,70 +46,144 @@ function CardRotate({ children, onSendToBack, sensitivity }) {
3546export default function Stack ( {
3647 randomRotation= false ,
3748 sensitivity= 200 ,
38- cardDimensions= { width :208 , height :208 } ,
39- cardsData= [ ] ,
49+ cards= [ ] ,
4050 animationConfig= { stiffness :260 , damping :20 } ,
41- sendToBackOnClick= false
51+ sendToBackOnClick= false ,
52+ autoplay= false ,
53+ autoplayDelay= 3000 ,
54+ pauseOnHover= false ,
55+ mobileClickOnly= false ,
56+ mobileBreakpoint= 768 ,
4257} ) {
43- const [ cards , setCards ] = useState (
44- cardsData . length
45- ?cardsData
46- :[
47- { id :1 , img :'https://images.unsplash.com/photo-1480074568708-e7b720bb3f09?q=80&w=500&auto=format' } ,
48- { id :2 , img :'https://images.unsplash.com/photo-1449844908441-8829872d2607?q=80&w=500&auto=format' } ,
49- { id :3 , img :'https://images.unsplash.com/photo-1452626212852-811d58933cae?q=80&w=500&auto=format' } ,
50- { id :4 , img :'https://images.unsplash.com/photo-1572120360610-d971b9d7767c?q=80&w=500&auto=format' }
51- ]
58+ const [ isMobile , setIsMobile ] = useState ( false ) ;
59+ const [ isPaused , setIsPaused ] = useState ( false ) ;
60+
61+ useEffect ( ( ) => {
62+ const checkMobile = ( ) => {
63+ setIsMobile ( window . innerWidth < mobileBreakpoint ) ;
64+ } ;
65+
66+ checkMobile ( ) ;
67+ window . addEventListener ( 'resize' , checkMobile ) ;
68+ return ( ) => window . removeEventListener ( 'resize' , checkMobile ) ;
69+ } , [ mobileBreakpoint ] ) ;
70+
71+ const shouldDisableDrag = mobileClickOnly && isMobile ;
72+ const shouldEnableClick = sendToBackOnClick || shouldDisableDrag ;
73+
74+ const [ stack , setStack ] = useState (
75+ ( ) => {
76+ if ( cards . length ) {
77+ return cards . map ( ( content , index ) => ( { id :index + 1 , content} ) ) ;
78+ } else {
79+ return [
80+ {
81+ id :1 ,
82+ content :(
83+ < img
84+ src = "https://images.unsplash.com/photo-1480074568708-e7b720bb3f09?q=80& w = 500 & auto = format "
85+ alt = "card-1"
86+ className = "card-image"
87+ />
88+ ) ,
89+ } ,
90+ {
91+ id :2 ,
92+ content :(
93+ < img
94+ src = "https://images.unsplash.com/photo-1449844908441-8829872d2607?q=80& w = 500 & auto = format "
95+ alt = "card-2"
96+ className = "card-image"
97+ />
98+ ) ,
99+ } ,
100+ {
101+ id :3 ,
102+ content :(
103+ < img
104+ src = "https://images.unsplash.com/photo-1452626212852-811d58933cae?q=80& w = 500 & auto = format "
105+ alt = "card-3"
106+ className = "card-image"
107+ />
108+ ) ,
109+ } ,
110+ {
111+ id :4 ,
112+ content :(
113+ < img
114+ src = "https://images.unsplash.com/photo-1572120360610-d971b9d7767c?q=80& w = 500 & auto = format "
115+ alt = "card-4"
116+ className = "card-image"
117+ />
118+ ) ,
119+ } ,
120+ ] ;
121+ }
122+ }
52123) ;
53124
54- const sendToBack = id => {
55- setCards ( prev => {
56- const newCards = [ ...prev ] ;
57- const index = newCards . findIndex ( card => card . id === id ) ;
58- const [ card ] = newCards . splice ( index , 1 ) ;
59- newCards . unshift ( card ) ;
60- return newCards ;
125+ useEffect ( ( ) => {
126+ if ( cards . length ) {
127+ setStack ( cards . map ( ( content , index ) => ( { id :index + 1 , content} ) ) ) ;
128+ }
129+ } , [ cards ] ) ;
130+
131+ const sendToBack = ( id ) => {
132+ setStack ( ( prev ) => {
133+ const newStack = [ ...prev ] ;
134+ const index = newStack . findIndex ( ( card ) => card . id === id ) ;
135+ const [ card ] = newStack . splice ( index , 1 ) ;
136+ newStack . unshift ( card ) ;
137+ return newStack ;
61138} ) ;
62139} ;
63140
141+ useEffect ( ( ) => {
142+ if ( autoplay && stack . length > 1 && ! isPaused ) {
143+ const interval = setInterval ( ( ) => {
144+ const topCardId = stack [ stack . length - 1 ] . id ;
145+ sendToBack ( topCardId ) ;
146+ } , autoplayDelay ) ;
147+
148+ return ( ) => clearInterval ( interval ) ;
149+ }
150+ } , [ autoplay , autoplayDelay , stack , isPaused ] ) ;
151+
64152return (
65153< div
66154className = "stack-container"
67- style = { {
68- width :cardDimensions . width ,
69- height :cardDimensions . height ,
70- perspective :600
71- } }
155+ onMouseEnter = { ( ) => pauseOnHover && setIsPaused ( true ) }
156+ onMouseLeave = { ( ) => pauseOnHover && setIsPaused ( false ) }
72157>
73- { cards . map ( ( card , index ) => {
158+ { stack . map ( ( card , index ) => {
74159const randomRotate = randomRotation ?Math . random ( ) * 10 - 5 :0 ;
75-
76160return (
77- < CardRotate key = { card . id } onSendToBack = { ( ) => sendToBack ( card . id ) } sensitivity = { sensitivity } >
161+ < CardRotate
162+ key = { card . id }
163+ onSendToBack = { ( ) => sendToBack ( card . id ) }
164+ sensitivity = { sensitivity }
165+ disableDrag = { shouldDisableDrag }
166+ >
78167< motion . div
79168className = "card"
80- onClick = { ( ) => sendToBackOnClick && sendToBack ( card . id ) }
169+ onClick = { ( ) => shouldEnableClick && sendToBack ( card . id ) }
81170animate = { {
82- rotateZ :( cards . length - index - 1 ) * 4 + randomRotate ,
83- scale :1 + index * 0.06 - cards . length * 0.06 ,
84- transformOrigin :'90% 90%'
171+ rotateZ :( stack . length - index - 1 ) * 4 + randomRotate ,
172+ scale :1 + index * 0.06 - stack . length * 0.06 ,
173+ transformOrigin :'90% 90%' ,
85174} }
86175initial = { false }
87176transition = { {
88177type :'spring' ,
89178stiffness :animationConfig . stiffness ,
90- damping :animationConfig . damping
91- } }
92- style = { {
93- width :cardDimensions . width ,
94- height :cardDimensions . height
179+ damping :animationConfig . damping ,
95180} }
96181>
97- < img src = { card . img } alt = { `card- ${ card . id } ` } className = "card-image" />
182+ { card . content }
98183</ motion . div >
99184</ CardRotate >
100185) ;
101186} ) }
102187</ div >
103188) ;
104- }
189+ }