@coreui/react-pro 4.1.3 → 4.2.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +1 -1
  2. package/dist/components/calendar/CCalendar.d.ts +106 -0
  3. package/dist/components/calendar/CCalendar.utils.d.ts +21 -0
  4. package/dist/components/datepicker/CDatePicker.d.ts +13 -0
  5. package/dist/components/daterangepicker/CDateRangePicker.d.ts +65 -0
  6. package/dist/components/dropdown/CDropdown.d.ts +10 -1
  7. package/dist/components/dropdown/CDropdownToggle.d.ts +4 -0
  8. package/dist/components/dropdown/CDropdownToggleCustom.d.ts +15 -0
  9. package/dist/components/picker/CPicker.d.ts +78 -0
  10. package/dist/components/smart-table/CSmartTableInterface.d.ts +0 -9
  11. package/dist/components/timepicker/CTimePicker.d.ts +45 -0
  12. package/dist/components/timepicker/CTimePicker.utils.d.ts +17 -0
  13. package/dist/components/timepicker/CTimePickerRollCol.d.ts +11 -0
  14. package/dist/index.d.ts +5 -1
  15. package/dist/index.es.js +2568 -718
  16. package/dist/index.es.js.map +1 -1
  17. package/dist/index.js +2849 -995
  18. package/dist/index.js.map +1 -1
  19. package/dist/utils/calendar.d.ts +21 -0
  20. package/dist/utils/time.d.ts +17 -0
  21. package/package.json +11 -8
  22. package/src/components/accordion/CAccordionButton.tsx +2 -1
  23. package/src/components/accordion/__tests__/__snapshots__/CAccordionButton.spec.tsx.snap +2 -0
  24. package/src/components/accordion/__tests__/__snapshots__/CAccordionHeader.spec.tsx.snap +2 -0
  25. package/src/components/calendar/CCalendar.tsx +528 -0
  26. package/src/components/calendar/__tests__/CCalendar.spec.tsx +9 -0
  27. package/src/components/calendar/__tests__/__snapshots__/CCalendar.spec.tsx.snap +573 -0
  28. package/src/components/datepicker/CDatePicker.tsx +50 -0
  29. package/src/components/datepicker/__tests__/CDatePicker.spec.tsx +9 -0
  30. package/src/components/datepicker/__tests__/__snapshots__/CDatePicker.spec.tsx.snap +617 -0
  31. package/src/components/daterangepicker/CDateRangePicker.tsx +427 -0
  32. package/src/components/daterangepicker/__tests__/CDateRangePicker.spec.tsx +9 -0
  33. package/src/components/daterangepicker/__tests__/__snapshots__/CDateRangePicker.spec.tsx.snap +1201 -0
  34. package/src/components/dropdown/CDropdown.tsx +19 -24
  35. package/src/components/dropdown/CDropdownMenu.tsx +57 -3
  36. package/src/components/dropdown/CDropdownToggle.tsx +28 -5
  37. package/src/components/picker/CPicker.tsx +207 -0
  38. package/src/components/picker/__tests__/CPicker.spec.tsx +9 -0
  39. package/src/components/picker/__tests__/__snapshots__/CPicker.spec.tsx.snap +23 -0
  40. package/src/components/smart-table/CSmartTable.tsx +44 -96
  41. package/src/components/smart-table/CSmartTableCleaner.tsx +3 -16
  42. package/src/components/smart-table/CSmartTableInterface.tsx +0 -9
  43. package/src/components/timepicker/CTimePicker.tsx +320 -0
  44. package/src/components/timepicker/CTimePickerRollCol.tsx +63 -0
  45. package/src/components/timepicker/__tests__/CTimePicker.spec.tsx +9 -0
  46. package/src/index.ts +8 -0
  47. package/src/utils/calendar.ts +193 -0
  48. package/src/utils/time.ts +58 -0
@@ -0,0 +1,427 @@
1
+ import React, { forwardRef, ReactNode, useEffect, useRef, useState } from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import classNames from 'classnames'
4
+ import { isMobile } from 'react-device-detect'
5
+
6
+ import { CButton } from '../button/CButton'
7
+ import { CCalendar, CCalendarProps } from '../calendar/CCalendar'
8
+ import { CFormInput } from '../form/CFormInput'
9
+ import { CInputGroup } from '../form/CInputGroup'
10
+ import { CInputGroupText } from '../form/CInputGroupText'
11
+ import { CPicker, CPickerProps } from '../picker/CPicker'
12
+ import { CTimePicker } from '../timepicker/CTimePicker'
13
+
14
+ import { isValidDate } from '../../utils/calendar'
15
+
16
+ import { Colors } from '../Types'
17
+
18
+ export interface CDateRangePickerProps
19
+ extends Omit<CPickerProps, 'autoClose' | 'component' | 'dark' | 'placeholder' | 'variant'>,
20
+ Omit<CCalendarProps, 'onCalendarCellHover' | 'onCalendarDateChange'> {
21
+ /**
22
+ * The number of calendars that render on desktop devices.
23
+ */
24
+ calendars?: number
25
+ /**
26
+ * A string of all className you want applied to the component.
27
+ */
28
+ className?: string
29
+ /**
30
+ * Toggle visibility or set the content of the cleaner button.
31
+ */
32
+ cleaner?: boolean
33
+ /**
34
+ * Toggle visibility or set the content of the input indicator.
35
+ */
36
+ indicator?: ReactNode | boolean
37
+ /**
38
+ * Toggle the readonly state for the component.
39
+ */
40
+ inputReadOnly?: boolean
41
+ /**
42
+ * Specifies short hints that are visible in start date and end date inputs.
43
+ */
44
+ placeholder?: string | string[]
45
+ /**
46
+ * @ignore
47
+ */
48
+ range?: boolean
49
+ /**
50
+ * Predefined date ranges the user can select from.
51
+ */
52
+ ranges?: object
53
+ /**
54
+ * Sets the color context of the cancel button to one of CoreUI’s themed colors.
55
+ *
56
+ * @type 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info' | 'dark' | 'light' | string
57
+ */
58
+ rangesButtonsColor?: Colors
59
+ /**
60
+ * Size the ranges button small or large.
61
+ */
62
+ rangesButtonsSize?: 'sm' | 'lg'
63
+ /**
64
+ * Set the ranges button variant to an outlined button or a ghost button.
65
+ */
66
+ rangesButtonsVariant?: 'outline' | 'ghost'
67
+ /**
68
+ * Default icon or character character that separates two dates.
69
+ */
70
+ separator?: ReactNode | boolean
71
+ /**
72
+ * Size the component small or large.
73
+ */
74
+ size?: 'sm' | 'lg'
75
+ /**
76
+ * Provide an additional time selection by adding select boxes to choose times.
77
+ */
78
+ timepicker?: boolean
79
+ }
80
+
81
+ export const CDateRangePicker = forwardRef<HTMLDivElement | HTMLLIElement, CDateRangePickerProps>(
82
+ (
83
+ {
84
+ calendars = 2,
85
+ className,
86
+ cleaner = true,
87
+ calendarDate,
88
+ indicator = true,
89
+ disabled,
90
+ disabledDates,
91
+ endDate,
92
+ firstDayOfWeek,
93
+ footer,
94
+ inputReadOnly,
95
+ locale = 'default',
96
+ maxDate,
97
+ minDate,
98
+ navigation,
99
+ onEndDateChange,
100
+ onHide,
101
+ onStartDateChange,
102
+ onShow,
103
+ placeholder = ['Start date', 'End date'],
104
+ range = true,
105
+ ranges,
106
+ rangesButtonsColor = 'secondary',
107
+ rangesButtonsSize,
108
+ rangesButtonsVariant = 'ghost',
109
+ separator = true,
110
+ size,
111
+ startDate,
112
+ timepicker,
113
+ toggler,
114
+ visible,
115
+ ...rest
116
+ },
117
+ ref,
118
+ ) => {
119
+ const [_visible, setVisible] = useState(visible)
120
+
121
+ const inputStartRef = useRef<HTMLInputElement>(null)
122
+ const inputEndRef = useRef<HTMLInputElement>(null)
123
+ const [inputStartHoverValue, setInputStartHoverValue] = useState<Date | null>(null)
124
+ const [inputEndHoverValue, setInputEndHoverValue] = useState<Date | null>(null)
125
+
126
+ const [_calendarDate, setCalendarDate] = useState<Date>(
127
+ calendarDate ? new Date(calendarDate) : new Date(),
128
+ )
129
+ const [_startDate, setStartDate] = useState<Date | null>(startDate ? new Date(startDate) : null)
130
+ const [_endDate, setEndDate] = useState<Date | null>(endDate ? new Date(endDate) : null)
131
+ const [initialStartDate, setInitialStartDate] = useState<Date | null>(
132
+ startDate ? new Date(startDate) : null,
133
+ )
134
+ const [initialEndDate, setInitialEndDate] = useState<Date | null>(
135
+ endDate ? new Date(endDate) : null,
136
+ )
137
+ const [selectEndDate, setSelectEndDate] = useState(false)
138
+
139
+ useEffect(() => {
140
+ startDate && setCalendarDate(new Date(startDate))
141
+ }, [startDate])
142
+
143
+ useEffect(() => {
144
+ endDate && setCalendarDate(new Date(endDate))
145
+ }, [endDate])
146
+
147
+ useEffect(() => {
148
+ if (inputStartHoverValue) {
149
+ setInputValue(inputStartRef.current, inputStartHoverValue, timepicker)
150
+ return
151
+ }
152
+
153
+ setInputValue(inputStartRef.current, _startDate, timepicker)
154
+ }, [inputStartHoverValue, _startDate])
155
+
156
+ useEffect(() => {
157
+ if (inputEndHoverValue) {
158
+ setInputValue(inputEndRef.current, inputEndHoverValue, timepicker)
159
+ return
160
+ }
161
+
162
+ setInputValue(inputEndRef.current, _endDate, timepicker)
163
+ }, [inputEndHoverValue, _endDate])
164
+
165
+ const setInputValue = (
166
+ el: HTMLInputElement | null,
167
+ date: Date | null,
168
+ timepicker?: boolean,
169
+ ) => {
170
+ if (!el) {
171
+ return
172
+ }
173
+
174
+ if (date) {
175
+ el.value = timepicker ? date.toLocaleString(locale) : date.toLocaleDateString(locale)
176
+ return
177
+ }
178
+
179
+ el.value = ''
180
+ }
181
+
182
+ const handleCalendarCellHover = (date: Date | null) => {
183
+ selectEndDate ? setInputEndHoverValue(date) : setInputStartHoverValue(date)
184
+ }
185
+
186
+ const handleCalendarDateChange = (date: Date, difference?: number) => {
187
+ difference
188
+ ? setCalendarDate(new Date(date.getFullYear(), date.getMonth() - difference, 1))
189
+ : setCalendarDate(date)
190
+ }
191
+
192
+ const handleStartDateChange = (date: Date | null) => {
193
+ setStartDate(date)
194
+ range && setSelectEndDate(true)
195
+ onStartDateChange && onStartDateChange(date)
196
+ setInputStartHoverValue(null)
197
+ }
198
+
199
+ const handleEndDateChange = (date: Date | null) => {
200
+ setEndDate(date)
201
+ setSelectEndDate(false)
202
+ onEndDateChange && onEndDateChange(date)
203
+ setInputEndHoverValue(null)
204
+
205
+ if (timepicker !== true && footer !== true) {
206
+ _startDate !== null && setVisible(false)
207
+ }
208
+ }
209
+
210
+ const handleClear = (event: React.MouseEvent<HTMLElement>) => {
211
+ event.stopPropagation()
212
+ setStartDate(initialStartDate)
213
+ setEndDate(initialEndDate)
214
+ setInputStartHoverValue(null)
215
+ setInputEndHoverValue(null)
216
+ }
217
+
218
+ const _className = classNames('date-picker', className)
219
+
220
+ const InputGroup = () => (
221
+ <CInputGroup className="picker-input-group" size={size}>
222
+ <CFormInput
223
+ className={classNames({
224
+ hover: inputStartHoverValue,
225
+ })}
226
+ disabled={disabled}
227
+ placeholder={Array.isArray(placeholder) ? placeholder[0] : placeholder}
228
+ readOnly={inputReadOnly}
229
+ onChange={(event) => {
230
+ if (isValidDate(event.target.value)) {
231
+ setCalendarDate(new Date(event.target.value))
232
+ setStartDate(new Date(event.target.value))
233
+ }
234
+ }}
235
+ onClick={() => setSelectEndDate(false)}
236
+ ref={inputStartRef}
237
+ />
238
+ {range && separator !== false && (
239
+ <CInputGroupText>
240
+ {typeof separator === 'boolean' ? (
241
+ <span className="picker-input-group-icon date-picker-arrow-icon" />
242
+ ) : (
243
+ separator
244
+ )}
245
+ </CInputGroupText>
246
+ )}
247
+ {range && (
248
+ <CFormInput
249
+ className={classNames({
250
+ hover: inputEndHoverValue,
251
+ })}
252
+ disabled={disabled}
253
+ placeholder={placeholder[1]}
254
+ readOnly={inputReadOnly}
255
+ onChange={(event) => {
256
+ if (isValidDate(event.target.value)) {
257
+ setCalendarDate(new Date(event.target.value))
258
+ setEndDate(new Date(event.target.value))
259
+ }
260
+ }}
261
+ onClick={() => setSelectEndDate(true)}
262
+ ref={inputEndRef}
263
+ />
264
+ )}
265
+ {(indicator || cleaner) && (
266
+ <CInputGroupText>
267
+ {indicator && (
268
+ <span className="picker-input-group-indicator">
269
+ {typeof indicator === 'boolean' ? (
270
+ <span className="picker-input-group-icon date-picker-input-icon" />
271
+ ) : (
272
+ indicator
273
+ )}
274
+ </span>
275
+ )}
276
+ {cleaner && _startDate && (
277
+ <span
278
+ className="picker-input-group-cleaner"
279
+ role="button"
280
+ onClick={(event) => handleClear(event)}
281
+ >
282
+ {typeof cleaner === 'boolean' ? (
283
+ <span className="picker-input-group-icon date-picker-cleaner-icon" />
284
+ ) : (
285
+ cleaner
286
+ )}
287
+ </span>
288
+ )}
289
+ </CInputGroupText>
290
+ )}
291
+ </CInputGroup>
292
+ )
293
+
294
+ return (
295
+ <CPicker
296
+ className={_className}
297
+ disabled={disabled}
298
+ footer={footer || timepicker}
299
+ toggler={toggler ? toggler : InputGroup()}
300
+ onCancel={() => {
301
+ handleStartDateChange(initialStartDate)
302
+ handleEndDateChange(initialEndDate)
303
+ }}
304
+ onHide={() => {
305
+ setInputStartHoverValue(null)
306
+ setInputEndHoverValue(null)
307
+ onHide && onHide()
308
+ }}
309
+ onShow={() => {
310
+ setInitialStartDate(_startDate)
311
+ setInitialEndDate(_endDate)
312
+ onShow && onShow()
313
+ }}
314
+ visible={_visible}
315
+ {...rest}
316
+ ref={ref}
317
+ >
318
+ <div className="date-picker-body">
319
+ {ranges && (
320
+ <div className="date-picker-ranges">
321
+ {Object.keys(ranges).map((key: string, index: number) => (
322
+ <CButton
323
+ color={rangesButtonsColor}
324
+ key={index}
325
+ onClick={() => {
326
+ setStartDate(ranges[key][0])
327
+ setEndDate(ranges[key][1])
328
+ }}
329
+ size={rangesButtonsSize}
330
+ variant={rangesButtonsVariant}
331
+ >
332
+ {key}
333
+ </CButton>
334
+ ))}
335
+ </div>
336
+ )}
337
+ <div className="date-picker-calendars">
338
+ {[...Array(isMobile ? 1 : calendars)].map((_, index) => (
339
+ <div className="date-picker-calendar" key={index}>
340
+ <CCalendar
341
+ calendarDate={
342
+ new Date(_calendarDate.getFullYear(), _calendarDate.getMonth() + index, 1)
343
+ }
344
+ disabledDates={disabledDates}
345
+ endDate={_endDate}
346
+ firstDayOfWeek={firstDayOfWeek}
347
+ locale={locale}
348
+ maxDate={maxDate}
349
+ minDate={minDate}
350
+ navigation={navigation}
351
+ range={range}
352
+ selectEndDate={selectEndDate}
353
+ startDate={_startDate}
354
+ onCalendarCellHover={(date) => handleCalendarCellHover(date)}
355
+ onCalendarDateChange={(date) => handleCalendarDateChange(date, index)}
356
+ onStartDateChange={(date) => handleStartDateChange(date)}
357
+ onEndDateChange={(date) => handleEndDateChange(date)}
358
+ />
359
+ {!isMobile && timepicker && (index === 0 || calendars - index === 1) && (
360
+ <CTimePicker
361
+ container="inline"
362
+ disabled={
363
+ index === 0
364
+ ? _startDate === null
365
+ ? true
366
+ : false
367
+ : _endDate === null
368
+ ? true
369
+ : false
370
+ }
371
+ locale={locale}
372
+ onTimeChange={(_, __, date) =>
373
+ index === 0 ? handleStartDateChange(date) : handleEndDateChange(date)
374
+ }
375
+ time={index === 0 ? _startDate : _endDate}
376
+ variant="select"
377
+ />
378
+ )}
379
+ {isMobile && timepicker && (
380
+ <>
381
+ <CTimePicker
382
+ container="inline"
383
+ disabled={_startDate === null ? true : false}
384
+ locale={locale}
385
+ onTimeChange={(_, __, date) => handleStartDateChange(date)}
386
+ time={_startDate}
387
+ variant="select"
388
+ />
389
+ <CTimePicker
390
+ container="inline"
391
+ disabled={_endDate === null ? true : false}
392
+ locale={locale}
393
+ onTimeChange={(_, __, date) => handleEndDateChange(date)}
394
+ time={_endDate}
395
+ variant="select"
396
+ />
397
+ </>
398
+ )}
399
+ </div>
400
+ ))}
401
+ </div>
402
+ </div>
403
+ </CPicker>
404
+ )
405
+ },
406
+ )
407
+
408
+ CDateRangePicker.displayName = 'CDateRangePicker'
409
+
410
+ CDateRangePicker.propTypes = {
411
+ ...CCalendar.propTypes,
412
+ ...CPicker.propTypes,
413
+ calendars: PropTypes.number,
414
+ className: PropTypes.string,
415
+ cleaner: PropTypes.bool,
416
+ indicator: PropTypes.oneOfType([PropTypes.bool, PropTypes.node]),
417
+ inputReadOnly: PropTypes.bool,
418
+ placeholder: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.any)]),
419
+ range: PropTypes.bool,
420
+ ranges: PropTypes.object,
421
+ rangesButtonsColor: CButton.propTypes?.color,
422
+ rangesButtonsSize: CButton.propTypes?.size,
423
+ rangesButtonsVariant: CButton.propTypes?.variant,
424
+ separator: PropTypes.oneOfType([PropTypes.bool, PropTypes.node]),
425
+ size: PropTypes.oneOf(['sm', 'lg']),
426
+ timepicker: PropTypes.bool,
427
+ }
@@ -0,0 +1,9 @@
1
+ import * as React from 'react'
2
+ import { render } from '@testing-library/react'
3
+ import '@testing-library/jest-dom/extend-expect'
4
+ import { CDateRangePicker } from '../CDateRangePicker'
5
+
6
+ test('loads and displays CDateRangePicker component', async () => {
7
+ const { container } = render(<CDateRangePicker />)
8
+ expect(container).toMatchSnapshot()
9
+ })