@@ -6,7 +6,8 @@ import React from 'react'
66import { resolveLocale } from '../../utils/locale'
77import { Button } from '../Button'
88import { Typo } from '../Typo'
9- import { DateSelectAnnotation , DateSelectProps } from './DateSelect'
9+ import { DateSelectAnnotation , DateSelectCursor } from './DateSelect'
10+ import { useDateSelectContext } from './DateSelectContext'
1011
1112const DateSelectContainer = styled . div `
1213 display: flex;
@@ -30,6 +31,43 @@ const DateContainer = styled.div<{ maxAnnotationsPerDay?: number }>`
3031 flex-direction: column;
3132 justify-content: flex-start;
3233 align-items: center;
34+ position: relative;
35+ `
36+
37+ const DateIndicatorBackground = styled . div `
38+ position: absolute;
39+ left: 50%;
40+ top: 50%;
41+ width: 2em;
42+ height: 2em;
43+ border-radius: 9999px;
44+ transform: translate(-50%, -50%);
45+ `
46+
47+ const DateHoverIndicator = styled ( DateIndicatorBackground ) `
48+ background-color: ${ ( { theme } ) => theme . color . background . card . dark } ;
49+ opacity: 0.5;
50+ `
51+
52+ const DateSelectedIndicator = styled ( DateIndicatorBackground ) `
53+ background-color: ${ ( { theme } ) => theme . color . solvedAc } ;
54+ `
55+
56+ const DateHoverRangeIndicator = styled ( DateIndicatorBackground ) < {
57+ side ?: 'left' | 'right'
58+ } > `
59+ width: ${ ( { side } ) => ( side ? '50%' : '100%' ) } ;
60+ left: ${ ( { side } ) => ( side === 'right' ? '0' : 'unset' ) } ;
61+ right: ${ ( { side } ) => ( side === 'right' ? 'unset' : '0' ) } ;
62+ transform: translate(0, -50%);
63+ border-radius: 0;
64+ background-color: ${ ( { theme } ) => theme . color . background . card . main } ;
65+ `
66+
67+ const DateIndicator = styled ( Typo ) `
68+ display: block;
69+ position: relative;
70+ width: 100%;
3371`
3472
3573const AnnotationContainer = styled . div `
@@ -79,28 +117,37 @@ const MonthNavigationButton = styled(Button)`
79117
80118const DAY = 24 * 60 * 60 * 1000
81119
82- type DateSelectMonthView = DateSelectProps & {
83- selectedDate : Date
84- setSelectedDate : ( date : Date ) => void
120+ export interface DateSelectMonthView {
121+ cursorDate : Date
122+ setCursorDate : ( date : Date ) => void
85123 setModeToMonth : ( ) => void
86124 firstMonth : boolean
87125 lastMonth : boolean
88126 offset : number
127+ selectState : DateSelectCursor
128+ setSelectState : React . Dispatch < React . SetStateAction < DateSelectCursor > >
89129}
90130
91131export const DateSelectMonthView = (
92132 props : DateSelectMonthView
93133) : JSX . Element => {
134+ const context = useDateSelectContext ( )
135+
94136 const {
95- // value,
96- // onChange,
137+ value,
138+ onChange,
97139 annotations = [ ] ,
98140 maxAnnotationsPerDay = annotations . length ? 3 : 0 ,
99141 weekStartsOn = 0 ,
100- selectedDate,
101- setSelectedDate,
102- setModeToMonth,
103142 locale,
143+ } = context
144+
145+ const {
146+ cursorDate,
147+ setCursorDate,
148+ selectState,
149+ setSelectState,
150+ setModeToMonth,
104151 firstMonth,
105152 lastMonth,
106153 offset,
@@ -111,8 +158,8 @@ export const DateSelectMonthView = (
111158 const resolvedLocale = resolveLocale ( locale )
112159
113160 const renderDateObject = new Date (
114- selectedDate . getFullYear ( ) ,
115- selectedDate . getMonth ( ) + offset ,
161+ cursorDate . getFullYear ( ) ,
162+ cursorDate . getMonth ( ) + offset ,
116163 1
117164 )
118165 const renderYear = renderDateObject . getFullYear ( )
@@ -146,10 +193,26 @@ export const DateSelectMonthView = (
146193 . split ( 'T' ) [ 0 ]
147194 const lastDateString = lastDate . toISOString ( ) . split ( 'T' ) [ 0 ]
148195
196+ const hoveredDate = selectState . hover ?. toISOString ( ) . split ( 'T' ) [ 0 ]
197+ const inputSelectedRangeA =
198+ ( selectState . mode === 'selectEnd' ? selectState . valueStart : null )
199+ ?. toISOString ( )
200+ . split ( 'T' ) [ 0 ] || null
201+ const inputSelectedRangeB =
202+ selectState . mode === 'selectEnd' ? hoveredDate || null : null
203+ const inputSelectedRangeStart =
204+ ( inputSelectedRangeA || '0' ) < ( inputSelectedRangeB || '0' )
205+ ? inputSelectedRangeA
206+ : inputSelectedRangeB
207+ const inputSelectedRangeEnd =
208+ ( inputSelectedRangeA || '0' ) > ( inputSelectedRangeB || '0' )
209+ ? inputSelectedRangeA
210+ : inputSelectedRangeB
211+
149212 const handleNavigateMonth = ( delta : number ) : void => {
150213 const destinationMonth1stDate = new Date (
151- selectedDate . getFullYear ( ) ,
152- selectedDate . getMonth ( ) + delta ,
214+ cursorDate . getFullYear ( ) ,
215+ cursorDate . getMonth ( ) + delta ,
153216 1
154217 )
155218 const destinationMonthLastDate = new Date (
@@ -158,15 +221,44 @@ export const DateSelectMonthView = (
158221 0
159222 )
160223 const destinationDate = Math . min (
161- selectedDate . getDate ( ) ,
224+ cursorDate . getDate ( ) ,
162225 destinationMonthLastDate . getDate ( )
163226 )
164227 const destination = new Date (
165228 destinationMonth1stDate . getFullYear ( ) ,
166229 destinationMonth1stDate . getMonth ( ) ,
167230 destinationDate
168231 )
169- setSelectedDate ( destination )
232+ setCursorDate ( destination )
233+ }
234+
235+ const handleSelectDate = ( date : string ) : void => {
236+ if ( context . type === 'date' ) {
237+ if ( context . onChange ) {
238+ context . onChange ( date )
239+ }
240+ return
241+ }
242+
243+ const { onChange : onChangeRange } = context
244+ if ( selectState . mode === 'selectStart' ) {
245+ setSelectState ( ( prev ) => ( {
246+ ...prev ,
247+ mode : 'selectEnd' ,
248+ valueStart : new Date ( date ) ,
249+ } ) )
250+ } else if ( selectState . mode === 'selectEnd' && inputSelectedRangeStart ) {
251+ if ( onChangeRange ) {
252+ onChangeRange ( {
253+ start : inputSelectedRangeStart ,
254+ end : date ,
255+ } )
256+ }
257+ setSelectState ( ( prev ) => ( {
258+ mode : 'selectStart' ,
259+ hover : prev . hover ,
260+ } ) )
261+ }
170262 }
171263
172264 const annotationsInRenderMonth = annotations
@@ -231,19 +323,50 @@ export const DateSelectMonthView = (
231323 { new Array ( 42 ) . fill ( undefined ) . map ( ( _ , dateOffset ) => {
232324 const date = new Date ( firstWeekFirstDate . getTime ( ) + dateOffset * DAY )
233325 const dateString = date . toISOString ( ) . split ( 'T' ) [ 0 ]
326+
327+ const currentDateInHoverRange =
328+ inputSelectedRangeStart && inputSelectedRangeEnd
329+ ? dateString >= inputSelectedRangeStart &&
330+ dateString <= inputSelectedRangeEnd
331+ : false
332+
234333 return (
235334 < DateContainer
236335 key = { date . toISOString ( ) }
237336 maxAnnotationsPerDay = { maxAnnotationsPerDay }
337+ onMouseEnter = { ( ) =>
338+ setSelectState ( ( prev ) => ( {
339+ ...prev ,
340+ hover : date ,
341+ } ) )
342+ }
343+ onClick = { ( ) => handleSelectDate ( dateString ) }
238344 >
239- < Typo
240- description = { date . getMonth ( ) !== renderMonth }
241- style = { {
242- opacity : date . getMonth ( ) !== renderMonth ? 0.5 : undefined ,
243- } }
244- >
245- { date . getDate ( ) }
246- </ Typo >
345+ < DateIndicator >
346+ { currentDateInHoverRange && (
347+ < DateHoverRangeIndicator
348+ side = {
349+ dateString === inputSelectedRangeStart
350+ ? 'left'
351+ : dateString === inputSelectedRangeEnd
352+ ? 'right'
353+ : undefined
354+ }
355+ />
356+ ) }
357+ { inputSelectedRangeA === dateString && (
358+ < DateSelectedIndicator />
359+ ) }
360+ { hoveredDate === dateString && < DateHoverIndicator /> }
361+ < DateIndicator
362+ description = { date . getMonth ( ) !== renderMonth }
363+ style = { {
364+ opacity : date . getMonth ( ) !== renderMonth ? 0.5 : undefined ,
365+ } }
366+ >
367+ { date . getDate ( ) }
368+ </ DateIndicator >
369+ </ DateIndicator >
247370 { annotationsGreedilyBucketed . map ( ( annotationsInDay , index ) => {
248371 const annotation = annotationsInDay . find (
249372 ( { start, end } ) => start <= dateString && end >= dateString ,
0 commit comments