1
- import { useCallback , useEffect , useState } from "react" ;
1
+ import {
2
+ Dispatch ,
3
+ MouseEvent ,
4
+ SetStateAction ,
5
+ useCallback ,
6
+ useEffect ,
7
+ useState ,
8
+ } from "react" ;
2
9
import { isSameDate , UseCalendarOptions } from "@h6s/calendar" ;
3
10
import { styled } from "styled-components" ;
4
11
import Dropdown from "../Dropdown/Dropdown" ;
5
12
import { Body , CalendarRenderer , DateRangePickerInput , DateTableCell } from "./Common" ;
13
+ import { Container } from "../Container/Container" ;
14
+ import dayjs from "dayjs" ;
15
+ import { Panel } from "../Panel/Panel" ;
16
+ import { Icon } from "../Icon/Icon" ;
6
17
7
18
const DateRangeTableCell = styled ( DateTableCell ) < {
8
19
$shouldShowRangeIndicator ?: boolean ;
@@ -16,6 +27,14 @@ const DateRangeTableCell = styled(DateTableCell)<{
16
27
` }
17
28
` ;
18
29
30
+ const PredefinedDatesContainer = styled ( Container ) `
31
+ width: 275px;
32
+ ` ;
33
+
34
+ const StyledDropdownItem = styled ( Dropdown . Item ) `
35
+ min-height: 24px;
36
+ ` ;
37
+
19
38
interface CalendarProps {
20
39
calendarBody : Body ;
21
40
closeDatepicker : ( ) => void ;
@@ -91,7 +110,7 @@ const Calendar = ({
91
110
return (
92
111
< DateRangeTableCell
93
112
$shouldShowRangeIndicator = {
94
- shouldShowRangeIndicator || isBetweenStartAndEndDates
113
+ ! isSelected && ( shouldShowRangeIndicator || isBetweenStartAndEndDates )
95
114
}
96
115
$isCurrentMonth = { isCurrentMonth }
97
116
$isDisabled = { isDisabled }
@@ -111,12 +130,108 @@ const Calendar = ({
111
130
} ) ;
112
131
} ;
113
132
114
- export interface DatePickerProps {
133
+ const locale = "en-US" ;
134
+ const monthFormatter = new Intl . DateTimeFormat ( locale , {
135
+ month : "short" ,
136
+ year : "numeric" ,
137
+ } ) ;
138
+
139
+ interface DateRange {
140
+ startDate : Date ;
141
+ endDate : Date ;
142
+ }
143
+
144
+ const getLastMonths = ( numberOfMonths : number = 6 ) : Array < DateRange > => {
145
+ const now = dayjs ( ) ;
146
+
147
+ const lastSixMonths : Array < DateRange > = [ ] ;
148
+ for ( let i = 0 ; i < numberOfMonths ; i ++ ) {
149
+ const date = now . subtract ( i , "month" ) ;
150
+ lastSixMonths . push ( {
151
+ startDate : date . startOf ( "month" ) . toDate ( ) ,
152
+ endDate : i === 0 ? now . toDate ( ) : date . endOf ( "month" ) . toDate ( ) ,
153
+ } ) ;
154
+ }
155
+
156
+ return lastSixMonths ;
157
+ } ;
158
+
159
+ interface PredefinedDatesProps {
160
+ onSelectDateRange : ( selectedStartDate : Date , selectedEndDate : Date ) => void ;
161
+ predefinedDatesCount : number ;
162
+ selectedEndDate : Date | undefined ;
163
+ selectedStartDate : Date | undefined ;
164
+ setEndDate : Dispatch < SetStateAction < Date | undefined > > ;
165
+ setStartDate : Dispatch < SetStateAction < Date | undefined > > ;
166
+ shouldShowCustomRange : boolean ;
167
+ showCustomDateRange : Dispatch < SetStateAction < boolean > > ;
168
+ }
169
+
170
+ const PredefinedDates = ( {
171
+ onSelectDateRange,
172
+ predefinedDatesCount,
173
+ selectedEndDate,
174
+ selectedStartDate,
175
+ setEndDate,
176
+ setStartDate,
177
+ shouldShowCustomRange,
178
+ showCustomDateRange,
179
+ } : PredefinedDatesProps ) => {
180
+ const pastSixMonths = getLastMonths ( predefinedDatesCount ) ;
181
+
182
+ const handleCustomTimePeriodClick = ( event : MouseEvent ) => {
183
+ event . preventDefault ( ) ;
184
+ showCustomDateRange ( ! shouldShowCustomRange ) ;
185
+ } ;
186
+
187
+ return (
188
+ < PredefinedDatesContainer
189
+ isResponsive = { false }
190
+ orientation = "vertical"
191
+ >
192
+ { pastSixMonths . map ( ( { startDate, endDate } ) => {
193
+ const handleItemClick = ( ) => {
194
+ setStartDate ( startDate ) ;
195
+ setEndDate ( endDate ) ;
196
+ onSelectDateRange ( startDate , endDate ) ;
197
+ } ;
198
+ return (
199
+ < StyledDropdownItem
200
+ key = { startDate . toISOString ( ) }
201
+ onClick = { handleItemClick }
202
+ >
203
+ < Container
204
+ justifyContent = "space-between"
205
+ orientation = "horizontal"
206
+ >
207
+ { monthFormatter . format ( startDate ) }
208
+ { selectedEndDate &&
209
+ isSameDate ( selectedEndDate , endDate ) &&
210
+ selectedStartDate &&
211
+ isSameDate ( selectedStartDate , startDate ) && < Icon name = "check" /> }
212
+ </ Container >
213
+ </ StyledDropdownItem >
214
+ ) ;
215
+ } ) }
216
+ < StyledDropdownItem onClick = { handleCustomTimePeriodClick } >
217
+ < Container
218
+ justifyContent = "space-between"
219
+ orientation = "horizontal"
220
+ >
221
+ Custom time period < Icon name = "chevron-right" />
222
+ </ Container >
223
+ </ StyledDropdownItem >
224
+ </ PredefinedDatesContainer >
225
+ ) ;
226
+ } ;
227
+
228
+ export interface DateRangePickerProps {
115
229
endDate ?: Date ;
116
230
disabled ?: boolean ;
117
231
futureDatesDisabled ?: boolean ;
118
232
onSelectDateRange : ( selectedStartDate : Date , selectedEndDate : Date ) => void ;
119
233
placeholder ?: string ;
234
+ predefinedDatesCount ?: number ;
120
235
startDate ?: Date ;
121
236
}
122
237
@@ -127,10 +242,12 @@ export const DateRangePicker = ({
127
242
futureDatesDisabled = false ,
128
243
onSelectDateRange,
129
244
placeholder = "start date – end date" ,
130
- } : DatePickerProps ) => {
245
+ predefinedDatesCount,
246
+ } : DateRangePickerProps ) => {
131
247
const [ isOpen , setIsOpen ] = useState < boolean > ( false ) ;
132
248
const [ selectedStartDate , setSelectedStartDate ] = useState < Date > ( ) ;
133
249
const [ selectedEndDate , setSelectedEndDate ] = useState < Date > ( ) ;
250
+ const [ shouldShowCustomRange , setShouldShowCustomRange ] = useState < boolean > ( false ) ;
134
251
135
252
const calendarOptions : UseCalendarOptions = { } ;
136
253
@@ -153,8 +270,17 @@ export const DateRangePicker = ({
153
270
154
271
const closeDatePicker = useCallback ( ( ) : void => {
155
272
setIsOpen ( false ) ;
273
+ setShouldShowCustomRange ( false ) ;
156
274
} , [ ] ) ;
157
275
276
+ const handleOpenChange = ( isOpen : boolean ) : void => {
277
+ setIsOpen ( isOpen ) ;
278
+
279
+ if ( ! isOpen ) {
280
+ setShouldShowCustomRange ( false ) ;
281
+ }
282
+ } ;
283
+
158
284
const handleSelectDate = useCallback (
159
285
( selectedDate : Date ) : void => {
160
286
// Start date and end date are selected, user clicks any date.
@@ -183,6 +309,7 @@ export const DateRangePicker = ({
183
309
// Otherwise, set the end date to the date the user clicked.
184
310
setSelectedEndDate ( selectedDate ) ;
185
311
onSelectDateRange ( selectedStartDate , selectedDate ) ;
312
+ setShouldShowCustomRange ( false ) ;
186
313
return ;
187
314
}
188
315
@@ -193,7 +320,7 @@ export const DateRangePicker = ({
193
320
194
321
return (
195
322
< Dropdown
196
- onOpenChange = { setIsOpen }
323
+ onOpenChange = { handleOpenChange }
197
324
open = { isOpen }
198
325
>
199
326
< Dropdown . Trigger disabled = { disabled } >
@@ -207,18 +334,51 @@ export const DateRangePicker = ({
207
334
/>
208
335
</ Dropdown . Trigger >
209
336
< Dropdown . Content align = "start" >
210
- < CalendarRenderer calendarOptions = { calendarOptions } >
211
- { body => (
212
- < Calendar
213
- calendarBody = { body }
214
- closeDatepicker = { closeDatePicker }
215
- futureDatesDisabled = { futureDatesDisabled }
216
- setSelectedDate = { handleSelectDate }
217
- startDate = { selectedStartDate }
218
- endDate = { selectedEndDate }
337
+ { predefinedDatesCount !== undefined && predefinedDatesCount > 0 ? (
338
+ < Panel
339
+ orientation = "horizontal"
340
+ padding = "none"
341
+ >
342
+ < PredefinedDates
343
+ onSelectDateRange = { onSelectDateRange }
344
+ predefinedDatesCount = { Math . min ( 6 , predefinedDatesCount ) }
345
+ selectedEndDate = { selectedEndDate }
346
+ selectedStartDate = { selectedStartDate }
347
+ setEndDate = { setSelectedEndDate }
348
+ setStartDate = { setSelectedStartDate }
349
+ shouldShowCustomRange = { shouldShowCustomRange }
350
+ showCustomDateRange = { setShouldShowCustomRange }
219
351
/>
220
- ) }
221
- </ CalendarRenderer >
352
+
353
+ { shouldShowCustomRange && (
354
+ < CalendarRenderer calendarOptions = { calendarOptions } >
355
+ { body => (
356
+ < Calendar
357
+ calendarBody = { body }
358
+ closeDatepicker = { closeDatePicker }
359
+ futureDatesDisabled = { futureDatesDisabled }
360
+ setSelectedDate = { handleSelectDate }
361
+ startDate = { selectedStartDate }
362
+ endDate = { selectedEndDate }
363
+ />
364
+ ) }
365
+ </ CalendarRenderer >
366
+ ) }
367
+ </ Panel >
368
+ ) : (
369
+ < CalendarRenderer calendarOptions = { calendarOptions } >
370
+ { body => (
371
+ < Calendar
372
+ calendarBody = { body }
373
+ closeDatepicker = { closeDatePicker }
374
+ futureDatesDisabled = { futureDatesDisabled }
375
+ setSelectedDate = { handleSelectDate }
376
+ startDate = { selectedStartDate }
377
+ endDate = { selectedEndDate }
378
+ />
379
+ ) }
380
+ </ CalendarRenderer >
381
+ ) }
222
382
</ Dropdown . Content >
223
383
</ Dropdown >
224
384
) ;
0 commit comments