11'use client'
22
3- import { useState , useMemo , useRef } from 'react'
3+ import { useState , useMemo , useRef , forwardRef } from 'react'
44import { startOfMonth , subMonths , addMonths , format } from 'date-fns'
55import { ko } from 'date-fns/locale'
66import { useSpring , animated } from '@react-spring/web'
@@ -17,129 +17,132 @@ type Props = {
1717 selectedDate : string
1818}
1919
20- export default function ExerciseCalendar ( {
21- onDateSelect,
22- selectedDate,
23- } : Props ) {
24- const today = new Date ( )
25- const [ currentMonth , setCurrentMonth ] = useState < Date > ( startOfMonth ( today ) )
26- const prevMonth = useMemo ( ( ) => subMonths ( currentMonth , 1 ) , [ currentMonth ] )
27- const nextMonth = useMemo ( ( ) => addMonths ( currentMonth , 1 ) , [ currentMonth ] )
28- const containerRef = useRef < HTMLDivElement > ( null )
29- const [ { x } , api ] = useSpring ( ( ) => ( { x : 0 } ) )
20+ const ExerciseCalendar = forwardRef < HTMLDivElement , Props > (
21+ ( { onDateSelect, selectedDate } , ref ) => {
22+ const today = new Date ( )
23+ const [ currentMonth , setCurrentMonth ] = useState < Date > ( startOfMonth ( today ) )
24+ const prevMonth = useMemo ( ( ) => subMonths ( currentMonth , 1 ) , [ currentMonth ] )
25+ const nextMonth = useMemo ( ( ) => addMonths ( currentMonth , 1 ) , [ currentMonth ] )
26+ const containerRef = useRef < HTMLDivElement > ( null )
27+ const [ { x } , api ] = useSpring ( ( ) => ( { x : 0 } ) )
3028
31- const bind = useDrag (
32- ( { down, movement : [ mx ] } ) => {
33- if ( down ) {
34- api . start ( { x : mx , immediate : true } )
35- return
36- }
29+ const bind = useDrag (
30+ ( { down, movement : [ mx ] } ) => {
31+ if ( down ) {
32+ api . start ( { x : mx , immediate : true } )
33+ return
34+ }
3735
38- const containerWidth = containerRef . current ?. clientWidth || 0
39- const threshold = containerWidth / 4
36+ const containerWidth = containerRef . current ?. clientWidth || 0
37+ const threshold = containerWidth / 4
4038
41- if ( Math . abs ( mx ) <= threshold ) {
42- api . start ( { x : 0 } )
43- return
44- }
39+ if ( Math . abs ( mx ) <= threshold ) {
40+ api . start ( { x : 0 } )
41+ return
42+ }
4543
46- const newMonth = mx > 0 ? prevMonth : nextMonth
47- const isMovingToFuture =
48- newMonth . getTime ( ) > startOfMonth ( today ) . getTime ( )
49- if ( isMovingToFuture ) {
50- api . start ( { x : 0 } )
51- return
52- }
44+ const newMonth = mx > 0 ? prevMonth : nextMonth
45+ const isMovingToFuture =
46+ newMonth . getTime ( ) > startOfMonth ( today ) . getTime ( )
47+ if ( isMovingToFuture ) {
48+ api . start ( { x : 0 } )
49+ return
50+ }
5351
54- const direction = mx > 0 ? 1 : - 1
55- const targetX = direction * containerWidth
52+ const direction = mx > 0 ? 1 : - 1
53+ const targetX = direction * containerWidth
5654
57- api . start ( {
58- x : targetX ,
59- config : { tension : 250 , friction : 30 } ,
60- onRest : ( ) => {
61- setCurrentMonth ( newMonth )
62- } ,
63- } )
64- } ,
65- {
66- axis : 'x' ,
67- filterTaps : true ,
68- } ,
69- )
70- const handlePrev = ( ) => setCurrentMonth ( subMonths ( currentMonth , 1 ) )
71- const handleNext = ( ) => {
72- if ( currentMonth . getTime ( ) >= startOfMonth ( today ) . getTime ( ) ) return
73- setCurrentMonth ( addMonths ( currentMonth , 1 ) )
74- }
55+ api . start ( {
56+ x : targetX ,
57+ config : { tension : 250 , friction : 30 } ,
58+ onRest : ( ) => {
59+ setCurrentMonth ( newMonth )
60+ } ,
61+ } )
62+ } ,
63+ {
64+ axis : 'x' ,
65+ filterTaps : true ,
66+ } ,
67+ )
68+ const handlePrev = ( ) => setCurrentMonth ( subMonths ( currentMonth , 1 ) )
69+ const handleNext = ( ) => {
70+ if ( currentMonth . getTime ( ) >= startOfMonth ( today ) . getTime ( ) ) return
71+ setCurrentMonth ( addMonths ( currentMonth , 1 ) )
72+ }
7573
76- // 애니메이션이 종료되면서 currentMonth 상태가 변경되면, x축을 즉각적으로 맞춰줍니다.
77- useMemo ( ( ) => {
78- api . start ( { x : 0 , immediate : true } )
79- } , [ currentMonth ] )
74+ // 애니메이션이 종료되면서 currentMonth 상태가 변경되면, x축을 즉각적으로 맞춰줍니다.
75+ useMemo ( ( ) => {
76+ api . start ( { x : 0 , immediate : true } )
77+ } , [ currentMonth ] )
8078
81- return (
82- < div className = { styles [ 'container' ] } >
83- < div className = { styles [ 'header' ] } >
84- < button onClick = { handlePrev } className = { styles [ 'nav-button' ] } >
85- < LeftArrow />
86- </ button >
87- < Typography as = "span" variant = "text15" weight = "bold" >
88- { format ( currentMonth , 'yyyy년 M월' , { locale : ko } ) }
89- </ Typography >
90- < button
91- onClick = { handleNext }
92- className = { styles [ 'nav-button' ] }
93- disabled = { currentMonth . getTime ( ) >= startOfMonth ( today ) . getTime ( ) }
94- >
95- < RightArrow />
96- </ button >
97- </ div >
79+ return (
80+ < div className = { styles [ 'container' ] } ref = { ref } >
81+ < div className = { styles [ 'header' ] } >
82+ < button onClick = { handlePrev } className = { styles [ 'nav-button' ] } >
83+ < LeftArrow />
84+ </ button >
85+ < Typography as = "span" variant = "text15" weight = "bold" >
86+ { format ( currentMonth , 'yyyy년 M월' , { locale : ko } ) }
87+ </ Typography >
88+ < button
89+ onClick = { handleNext }
90+ className = { styles [ 'nav-button' ] }
91+ disabled = { currentMonth . getTime ( ) >= startOfMonth ( today ) . getTime ( ) }
92+ >
93+ < RightArrow />
94+ </ button >
95+ </ div >
9896
99- < div className = { styles [ 'weekdays' ] } >
100- { [ '일' , '월' , '화' , '수' , '목' , '금' , '토' ] . map ( ( weekday ) => (
101- < div key = { weekday } className = { styles [ 'weekday' ] } >
102- < Typography as = "span" variant = "text14" weight = "bold" >
103- { weekday }
104- </ Typography >
105- </ div >
106- ) ) }
107- </ div >
97+ < div className = { styles [ 'weekdays' ] } >
98+ { [ '일' , '월' , '화' , '수' , '목' , '금' , '토' ] . map ( ( weekday ) => (
99+ < div key = { weekday } className = { styles [ 'weekday' ] } >
100+ < Typography as = "span" variant = "text14" weight = "bold" >
101+ { weekday }
102+ </ Typography >
103+ </ div >
104+ ) ) }
105+ </ div >
108106
109- < div
110- className = { styles [ 'calendar-carousel-container' ] }
111- ref = { containerRef }
112- { ...bind ( ) }
113- >
114- < animated . div
115- className = { styles [ 'calendar-carousel-view' ] }
116- style = { {
117- transform : x . to ( ( val ) => `translateX(calc(-100% / 3 + ${ val } px))` ) ,
118- } }
107+ < div
108+ className = { styles [ 'calendar-carousel-container' ] }
109+ ref = { containerRef }
110+ { ...bind ( ) }
119111 >
120- < MonthViewWithData
121- today = { today }
122- monthToShow = { prevMonth }
123- isActive = { false }
124- onDateSelect = { onDateSelect }
125- selectedDate = { selectedDate }
126- />
127- < MonthViewWithData
128- today = { today }
129- monthToShow = { currentMonth }
130- isActive = { true }
131- onDateSelect = { onDateSelect }
132- selectedDate = { selectedDate }
133- />
134- < MonthViewWithData
135- today = { today }
136- monthToShow = { nextMonth }
137- isActive = { false }
138- onDateSelect = { onDateSelect }
139- selectedDate = { selectedDate }
140- />
141- </ animated . div >
112+ < animated . div
113+ className = { styles [ 'calendar-carousel-view' ] }
114+ style = { {
115+ transform : x . to (
116+ ( val ) => `translateX(calc(-100% / 3 + ${ val } px))` ,
117+ ) ,
118+ } }
119+ >
120+ < MonthViewWithData
121+ today = { today }
122+ monthToShow = { prevMonth }
123+ isActive = { false }
124+ onDateSelect = { onDateSelect }
125+ selectedDate = { selectedDate }
126+ />
127+ < MonthViewWithData
128+ today = { today }
129+ monthToShow = { currentMonth }
130+ isActive = { true }
131+ onDateSelect = { onDateSelect }
132+ selectedDate = { selectedDate }
133+ />
134+ < MonthViewWithData
135+ today = { today }
136+ monthToShow = { nextMonth }
137+ isActive = { false }
138+ onDateSelect = { onDateSelect }
139+ selectedDate = { selectedDate }
140+ />
141+ </ animated . div >
142+ </ div >
142143 </ div >
143- </ div >
144- )
145- }
144+ )
145+ } ,
146+ )
147+ ExerciseCalendar . displayName = 'ExerciseCalendar'
148+ export default ExerciseCalendar
0 commit comments