@coreui/react-pro 4.13.1 → 4.14.0

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coreui/react-pro",
3
- "version": "4.13.1",
3
+ "version": "4.14.0",
4
4
  "description": "UI Components Library for React.js",
5
5
  "keywords": [
6
6
  "react",
@@ -11,6 +11,7 @@ import { CFormControlWrapper, CFormControlWrapperProps } from '../form/CFormCont
11
11
  import { CPicker, CPickerProps } from '../picker/CPicker'
12
12
  import { CTimePicker } from '../time-picker/CTimePicker'
13
13
 
14
+ import { useDebouncedCallback } from '../../hooks'
14
15
  import { Colors } from '../../types'
15
16
  import { getLocalDateFromString } from './utils'
16
17
 
@@ -101,6 +102,24 @@ export interface CDateRangePickerProps
101
102
  * Toggle visibility or set the content of the input indicator.
102
103
  */
103
104
  indicator?: ReactNode | boolean
105
+ /**
106
+ * Custom function to format the selected date into a string according to a custom format.
107
+ *
108
+ * @since v4.14.0
109
+ */
110
+ inputDateFormat?: (date: Date) => string
111
+ /**
112
+ * Custom function to parse the input value into a valid Date object.
113
+ *
114
+ * @since v4.14.0
115
+ */
116
+ inputDateParse?: (date: string | Date) => Date
117
+ /**
118
+ * Defines the delay (in milliseconds) for the input field's onChange event.
119
+ *
120
+ * @since v4.14.0
121
+ */
122
+ inputOnChangeDelay?: number
104
123
  /**
105
124
  * Toggle the readonly state for the component.
106
125
  */
@@ -190,7 +209,6 @@ export const CDateRangePicker = forwardRef<HTMLDivElement | HTMLLIElement, CDate
190
209
  confirmButtonColor = 'primary',
191
210
  confirmButtonSize = 'sm',
192
211
  confirmButtonVariant,
193
- indicator = true,
194
212
  dayFormat,
195
213
  disabled,
196
214
  disabledDates,
@@ -202,6 +220,10 @@ export const CDateRangePicker = forwardRef<HTMLDivElement | HTMLLIElement, CDate
202
220
  format,
203
221
  footer,
204
222
  id,
223
+ indicator = true,
224
+ inputDateFormat,
225
+ inputDateParse,
226
+ inputOnChangeDelay = 750,
205
227
  inputReadOnly,
206
228
  invalid,
207
229
  label,
@@ -270,17 +292,13 @@ export const CDateRangePicker = forwardRef<HTMLDivElement | HTMLLIElement, CDate
270
292
  }, [valid, invalid])
271
293
 
272
294
  useEffect(() => {
273
- if (startDate) {
274
- setStartDate(new Date(startDate))
275
- setCalendarDate(new Date(startDate))
276
- }
295
+ setStartDate(startDate ? new Date(startDate) : null)
296
+ setCalendarDate(startDate ? new Date(startDate) : new Date())
277
297
  }, [startDate])
278
298
 
279
299
  useEffect(() => {
280
- if (endDate) {
281
- setEndDate(new Date(endDate))
282
- setCalendarDate(new Date(endDate))
283
- }
300
+ setEndDate(endDate ? new Date(endDate) : null)
301
+ setCalendarDate(endDate ? new Date(endDate) : new Date())
284
302
  }, [endDate])
285
303
 
286
304
  useEffect(() => {
@@ -326,7 +344,9 @@ export const CDateRangePicker = forwardRef<HTMLDivElement | HTMLLIElement, CDate
326
344
  }, [formRef, _startDate, _endDate])
327
345
 
328
346
  const formatDate = (date: Date) => {
329
- return format
347
+ return inputDateFormat
348
+ ? inputDateFormat(date)
349
+ : format
330
350
  ? dateFormat(date, format)
331
351
  : timepicker
332
352
  ? date.toLocaleString(locale)
@@ -402,6 +422,16 @@ export const CDateRangePicker = forwardRef<HTMLDivElement | HTMLLIElement, CDate
402
422
  }
403
423
  }
404
424
 
425
+ const handleOnChange = useDebouncedCallback((value: string, input: string) => {
426
+ const date = inputDateParse
427
+ ? inputDateParse(value)
428
+ : getLocalDateFromString(value, locale, timepicker)
429
+ if (date instanceof Date && date.getTime()) {
430
+ setCalendarDate(date)
431
+ input === 'start' ? setStartDate(date) : setEndDate(date)
432
+ }
433
+ }, inputOnChangeDelay)
434
+
405
435
  const InputGroup = () => (
406
436
  <div
407
437
  className={classNames('input-group', 'picker-input-group', {
@@ -419,11 +449,7 @@ export const CDateRangePicker = forwardRef<HTMLDivElement | HTMLLIElement, CDate
419
449
  readOnly={inputReadOnly || typeof format === 'string'}
420
450
  required={required}
421
451
  onChange={(event) => {
422
- const date = getLocalDateFromString(event.target.value, locale, timepicker)
423
- if (date instanceof Date && date.getTime()) {
424
- setCalendarDate(date)
425
- setStartDate(date)
426
- }
452
+ handleOnChange(event.target.value, 'start')
427
453
  }}
428
454
  onClick={() => setSelectEndDate(false)}
429
455
  ref={inputStartRef}
@@ -439,18 +465,13 @@ export const CDateRangePicker = forwardRef<HTMLDivElement | HTMLLIElement, CDate
439
465
  className={classNames('form-control date-picker-input', {
440
466
  hover: inputEndHoverValue,
441
467
  })}
442
-
443
468
  disabled={disabled}
444
469
  {...(id && { name: `${id}-end-date` })}
445
470
  placeholder={placeholder[1]}
446
471
  readOnly={inputReadOnly || typeof format === 'string'}
447
472
  required={required}
448
473
  onChange={(event) => {
449
- const date = getLocalDateFromString(event.target.value, locale, timepicker)
450
- if (date instanceof Date && date.getTime()) {
451
- setCalendarDate(date)
452
- setEndDate(date)
453
- }
474
+ handleOnChange(event.target.value, 'end')
454
475
  }}
455
476
  onClick={() => setSelectEndDate(true)}
456
477
  ref={inputEndRef}
@@ -692,6 +713,9 @@ CDateRangePicker.propTypes = {
692
713
  confirmButtonVariant: CButton.propTypes?.variant,
693
714
  id: PropTypes.string,
694
715
  indicator: PropTypes.oneOfType([PropTypes.bool, PropTypes.node]),
716
+ inputDateFormat: PropTypes.func,
717
+ inputDateParse: PropTypes.func,
718
+ inputOnChangeDelay: PropTypes.number,
695
719
  inputReadOnly: PropTypes.bool,
696
720
  placeholder: PropTypes.oneOfType([
697
721
  PropTypes.string,
@@ -1,10 +1,10 @@
1
- import React, { FC, HTMLAttributes, ReactNode, useRef, useEffect, useState } from 'react'
1
+ import React, { forwardRef, HTMLAttributes, ReactNode, useRef, useEffect, useState } from 'react'
2
2
  import { createPortal } from 'react-dom'
3
3
  import classNames from 'classnames'
4
4
  import PropTypes from 'prop-types'
5
5
  import { Transition } from 'react-transition-group'
6
6
 
7
- import { usePopper } from '../../hooks'
7
+ import { useForkedRef, usePopper } from '../../hooks'
8
8
  import { fallbackPlacementsPropType, triggerPropType } from '../../props'
9
9
  import type { Placements, Triggers } from '../../types'
10
10
  import { getRTLPlacement, getTransitionDurationFromElement } from '../../utils'
@@ -68,132 +68,141 @@ export interface CPopoverProps extends Omit<HTMLAttributes<HTMLDivElement>, 'tit
68
68
  visible?: boolean
69
69
  }
70
70
 
71
- export const CPopover: FC<CPopoverProps> = ({
72
- children,
73
- animation = true,
74
- className,
75
- content,
76
- delay = 0,
77
- fallbackPlacements = ['top', 'right', 'bottom', 'left'],
78
- offset = [0, 8],
79
- onHide,
80
- onShow,
81
- placement = 'top',
82
- title,
83
- trigger = 'click',
84
- visible,
85
- ...rest
86
- }) => {
87
- const popoverRef = useRef(null)
88
- const togglerRef = useRef(null)
89
- const { initPopper, destroyPopper } = usePopper()
90
- const [_visible, setVisible] = useState(visible)
71
+ export const CPopover = forwardRef<HTMLDivElement, CPopoverProps>(
72
+ (
73
+ {
74
+ children,
75
+ animation = true,
76
+ className,
77
+ content,
78
+ delay = 0,
79
+ fallbackPlacements = ['top', 'right', 'bottom', 'left'],
80
+ offset = [0, 8],
81
+ onHide,
82
+ onShow,
83
+ placement = 'top',
84
+ title,
85
+ trigger = 'click',
86
+ visible,
87
+ ...rest
88
+ },
89
+ ref,
90
+ ) => {
91
+ const popoverRef = useRef(null)
92
+ const togglerRef = useRef(null)
93
+ const forkedRef = useForkedRef(ref, popoverRef)
91
94
 
92
- const _delay = typeof delay === 'number' ? { show: delay, hide: delay } : delay
95
+ const { initPopper, destroyPopper } = usePopper()
96
+ const [_visible, setVisible] = useState(visible)
93
97
 
94
- const popperConfig = {
95
- modifiers: [
96
- {
97
- name: 'arrow',
98
- options: {
99
- element: '.popover-arrow',
98
+ const _delay = typeof delay === 'number' ? { show: delay, hide: delay } : delay
99
+
100
+ const popperConfig = {
101
+ modifiers: [
102
+ {
103
+ name: 'arrow',
104
+ options: {
105
+ element: '.popover-arrow',
106
+ },
100
107
  },
101
- },
102
- {
103
- name: 'flip',
104
- options: {
105
- fallbackPlacements: fallbackPlacements,
108
+ {
109
+ name: 'flip',
110
+ options: {
111
+ fallbackPlacements: fallbackPlacements,
112
+ },
106
113
  },
107
- },
108
- {
109
- name: 'offset',
110
- options: {
111
- offset: offset,
114
+ {
115
+ name: 'offset',
116
+ options: {
117
+ offset: offset,
118
+ },
112
119
  },
113
- },
114
- ],
115
- placement: getRTLPlacement(placement, togglerRef.current),
116
- }
120
+ ],
121
+ placement: getRTLPlacement(placement, togglerRef.current),
122
+ }
117
123
 
118
- useEffect(() => {
119
- setVisible(visible)
120
- }, [visible])
124
+ useEffect(() => {
125
+ setVisible(visible)
126
+ }, [visible])
121
127
 
122
- useEffect(() => {
123
- if (_visible && togglerRef.current && popoverRef.current) {
124
- initPopper(togglerRef.current, popoverRef.current, popperConfig)
125
- }
128
+ useEffect(() => {
129
+ if (_visible && togglerRef.current && popoverRef.current) {
130
+ initPopper(togglerRef.current, popoverRef.current, popperConfig)
131
+ }
126
132
 
127
- return () => {
128
- destroyPopper()
129
- }
130
- }, [_visible])
133
+ return () => {
134
+ destroyPopper()
135
+ }
136
+ }, [_visible])
131
137
 
132
- const toggleVisible = (visible: boolean) => {
133
- if (visible) {
134
- setTimeout(() => setVisible(true), _delay.show)
135
- return
136
- }
138
+ const toggleVisible = (visible: boolean) => {
139
+ if (visible) {
140
+ setTimeout(() => setVisible(true), _delay.show)
141
+ return
142
+ }
137
143
 
138
- setTimeout(() => setVisible(false), _delay.hide)
139
- }
144
+ setTimeout(() => setVisible(false), _delay.hide)
145
+ }
140
146
 
141
- return (
142
- <>
143
- {React.cloneElement(children as React.ReactElement<any>, {
144
- ref: togglerRef,
145
- ...((trigger === 'click' || trigger.includes('click')) && {
146
- onClick: () => toggleVisible(!_visible),
147
- }),
148
- ...((trigger === 'focus' || trigger.includes('focus')) && {
149
- onFocus: () => toggleVisible(true),
150
- onBlur: () => toggleVisible(false),
151
- }),
152
- ...((trigger === 'hover' || trigger.includes('hover')) && {
153
- onMouseEnter: () => toggleVisible(true),
154
- onMouseLeave: () => toggleVisible(false),
155
- }),
156
- })}
157
- {typeof window !== 'undefined' &&
158
- createPortal(
159
- <Transition
160
- in={_visible}
161
- mountOnEnter
162
- nodeRef={popoverRef}
163
- onEnter={onShow}
164
- onExit={onHide}
165
- timeout={{
166
- enter: 0,
167
- exit: popoverRef.current ? getTransitionDurationFromElement(popoverRef.current) + 50 : 200,
168
- }}
169
- unmountOnExit
170
- >
171
- {(state) => (
172
- <div
173
- className={classNames(
174
- 'popover',
175
- 'bs-popover-auto',
176
- {
177
- fade: animation,
178
- show: state === 'entered',
179
- },
180
- className,
181
- )}
182
- ref={popoverRef}
183
- role="tooltip"
184
- {...rest}
185
- >
186
- <div className="popover-arrow"></div>
187
- <div className="popover-header">{title}</div>
188
- <div className="popover-body">{content}</div>
189
- </div>
190
- )}
191
- </Transition>,
192
- document.body,
193
- )}
194
- </>
195
- )
196
- }
147
+ return (
148
+ <>
149
+ {React.cloneElement(children as React.ReactElement<any>, {
150
+ ref: togglerRef,
151
+ ...((trigger === 'click' || trigger.includes('click')) && {
152
+ onClick: () => toggleVisible(!_visible),
153
+ }),
154
+ ...((trigger === 'focus' || trigger.includes('focus')) && {
155
+ onFocus: () => toggleVisible(true),
156
+ onBlur: () => toggleVisible(false),
157
+ }),
158
+ ...((trigger === 'hover' || trigger.includes('hover')) && {
159
+ onMouseEnter: () => toggleVisible(true),
160
+ onMouseLeave: () => toggleVisible(false),
161
+ }),
162
+ })}
163
+ {typeof window !== 'undefined' &&
164
+ createPortal(
165
+ <Transition
166
+ in={_visible}
167
+ mountOnEnter
168
+ nodeRef={popoverRef}
169
+ onEnter={onShow}
170
+ onExit={onHide}
171
+ timeout={{
172
+ enter: 0,
173
+ exit: popoverRef.current
174
+ ? getTransitionDurationFromElement(popoverRef.current) + 50
175
+ : 200,
176
+ }}
177
+ unmountOnExit
178
+ >
179
+ {(state) => (
180
+ <div
181
+ className={classNames(
182
+ 'popover',
183
+ 'bs-popover-auto',
184
+ {
185
+ fade: animation,
186
+ show: state === 'entered',
187
+ },
188
+ className,
189
+ )}
190
+ ref={forkedRef}
191
+ role="tooltip"
192
+ {...rest}
193
+ >
194
+ <div className="popover-arrow"></div>
195
+ <div className="popover-header">{title}</div>
196
+ <div className="popover-body">{content}</div>
197
+ </div>
198
+ )}
199
+ </Transition>,
200
+ document.body,
201
+ )}
202
+ </>
203
+ )
204
+ },
205
+ )
197
206
 
198
207
  CPopover.propTypes = {
199
208
  animation: PropTypes.bool,
@@ -183,7 +183,7 @@ export const CTimePicker = forwardRef<HTMLDivElement | HTMLLIElement, CTimePicke
183
183
  })
184
184
 
185
185
  useEffect(() => {
186
- setDate(convertTimeToDate(time))
186
+ setDate(time ? convertTimeToDate(time) : null)
187
187
  }, [time])
188
188
 
189
189
  useEffect(() => {
@@ -390,38 +390,6 @@ export const CTimePicker = forwardRef<HTMLDivElement | HTMLLIElement, CTimePicke
390
390
  )
391
391
  }
392
392
 
393
- const TimePickerRoll = () => (
394
- <>
395
- <CTimePickerRollCol
396
- elements={localizedTimePartials && localizedTimePartials.listOfHours}
397
- onClick={(index: number) => handleTimeChange('hours', index.toString())}
398
- selected={getSelectedHour(date, locale, ampm)}
399
- />
400
- <CTimePickerRollCol
401
- elements={localizedTimePartials && localizedTimePartials.listOfMinutes}
402
- onClick={(index: number) => handleTimeChange('minutes', index.toString())}
403
- selected={getSelectedMinutes(date)}
404
- />
405
- {seconds && (
406
- <CTimePickerRollCol
407
- elements={localizedTimePartials && localizedTimePartials.listOfSeconds}
408
- onClick={(index: number) => handleTimeChange('seconds', index.toString())}
409
- selected={getSelectedSeconds(date)}
410
- />
411
- )}
412
- {localizedTimePartials && localizedTimePartials.hour12 && (
413
- <CTimePickerRollCol
414
- elements={[
415
- { value: 'am', label: 'AM' },
416
- { value: 'pm', label: 'PM' },
417
- ]}
418
- onClick={(value: string) => handleTimeChange('toggle', value)}
419
- selected={_ampm}
420
- />
421
- )}
422
- </>
423
- )
424
-
425
393
  return (
426
394
  <CFormControlWrapper
427
395
  describedby={rest['aria-describedby']}
@@ -499,7 +467,39 @@ export const CTimePicker = forwardRef<HTMLDivElement | HTMLLIElement, CTimePicke
499
467
  ['time-picker-roll']: variant === 'roll',
500
468
  })}
501
469
  >
502
- {variant === 'select' ? <TimePickerSelect /> : TimePickerRoll()}
470
+ {variant === 'select' ? (
471
+ <TimePickerSelect />
472
+ ) : (
473
+ <>
474
+ <CTimePickerRollCol
475
+ elements={localizedTimePartials && localizedTimePartials.listOfHours}
476
+ onClick={(index: number) => handleTimeChange('hours', index.toString())}
477
+ selected={getSelectedHour(date, locale, ampm)}
478
+ />
479
+ <CTimePickerRollCol
480
+ elements={localizedTimePartials && localizedTimePartials.listOfMinutes}
481
+ onClick={(index: number) => handleTimeChange('minutes', index.toString())}
482
+ selected={getSelectedMinutes(date)}
483
+ />
484
+ {seconds && (
485
+ <CTimePickerRollCol
486
+ elements={localizedTimePartials && localizedTimePartials.listOfSeconds}
487
+ onClick={(index: number) => handleTimeChange('seconds', index.toString())}
488
+ selected={getSelectedSeconds(date)}
489
+ />
490
+ )}
491
+ {localizedTimePartials && localizedTimePartials.hour12 && (
492
+ <CTimePickerRollCol
493
+ elements={[
494
+ { value: 'am', label: 'AM' },
495
+ { value: 'pm', label: 'PM' },
496
+ ]}
497
+ onClick={(value: string) => handleTimeChange('toggle', value)}
498
+ selected={_ampm}
499
+ />
500
+ )}
501
+ </>
502
+ )}
503
503
  </div>
504
504
  </CPicker>
505
505
  </CFormControlWrapper>
@@ -2,7 +2,7 @@ import React, { forwardRef, useEffect, useRef } from 'react'
2
2
  import PropTypes from 'prop-types'
3
3
  import classNames from 'classnames'
4
4
 
5
- import { useForkedRef } from '../../hooks'
5
+ import { useForkedRef, useIsVisible } from '../../hooks'
6
6
 
7
7
  export interface Element {
8
8
  value: number | string
@@ -20,18 +20,21 @@ export const CTimePickerRollCol = forwardRef<HTMLDivElement, CTimePickerRollColP
20
20
  const init = useRef(true)
21
21
  const colRef = useRef<HTMLDivElement>(null)
22
22
  const forkedRef = useForkedRef(ref, colRef)
23
+ const isVisible = useIsVisible(colRef)
23
24
 
24
25
  useEffect(() => {
25
26
  const nodeEl = colRef.current?.querySelector('.selected')
26
- if (nodeEl && nodeEl instanceof HTMLElement) {
27
+ if (isVisible && nodeEl && nodeEl instanceof HTMLElement) {
27
28
  colRef.current?.scrollTo({
28
29
  top: nodeEl.offsetTop,
29
30
  behavior: init.current ? 'auto' : 'smooth',
30
31
  })
31
32
  }
32
33
 
33
- init.current = false
34
- }, [selected])
34
+ if (isVisible) {
35
+ init.current = false
36
+ }
37
+ }, [isVisible, selected])
35
38
 
36
39
  const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>, value: number | string) => {
37
40
  if (event.code === 'Space' || event.key === 'Enter') {