@coreui/react-pro 4.1.2 → 4.2.0-beta.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.
Files changed (39) hide show
  1. package/README.md +1 -1
  2. package/dist/components/calendar/CCalendar.d.ts +89 -0
  3. package/dist/components/calendar/CCalendar.utils.d.ts +20 -0
  4. package/dist/components/datepicker/CDatePicker.d.ts +13 -0
  5. package/dist/components/daterangepicker/CDateRangePicker.d.ts +50 -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/modal/CModal.d.ts +1 -0
  10. package/dist/components/picker/CPicker.d.ts +37 -0
  11. package/dist/components/smart-table/CSmartTableInterface.d.ts +0 -9
  12. package/dist/components/timepicker/CTimePicker.d.ts +49 -0
  13. package/dist/components/timepicker/CTimePicker.utils.d.ts +4 -0
  14. package/dist/components/timepicker/CTimePickerRollCol.d.ts +11 -0
  15. package/dist/index.d.ts +5 -1
  16. package/dist/index.es.js +2976 -1148
  17. package/dist/index.es.js.map +1 -1
  18. package/dist/index.js +4691 -2859
  19. package/dist/index.js.map +1 -1
  20. package/package.json +10 -8
  21. package/src/components/accordion/CAccordionButton.tsx +2 -1
  22. package/src/components/calendar/CCalendar.tsx +482 -0
  23. package/src/components/calendar/CCalendar.utils.ts +183 -0
  24. package/src/components/datepicker/CDatePicker.tsx +49 -0
  25. package/src/components/daterangepicker/CDateRangePicker.tsx +366 -0
  26. package/src/components/dropdown/CDropdown.tsx +19 -24
  27. package/src/components/dropdown/CDropdownMenu.tsx +55 -3
  28. package/src/components/dropdown/CDropdownToggle.tsx +28 -5
  29. package/src/components/modal/CModal.tsx +42 -19
  30. package/src/components/modal/__tests__/CModal.spec.tsx +1 -1
  31. package/src/components/modal/__tests__/__snapshots__/CModal.spec.tsx.snap +2 -10
  32. package/src/components/picker/CPicker.tsx +142 -0
  33. package/src/components/smart-table/CSmartTable.tsx +44 -96
  34. package/src/components/smart-table/CSmartTableCleaner.tsx +3 -16
  35. package/src/components/smart-table/CSmartTableInterface.tsx +0 -9
  36. package/src/components/timepicker/CTimePicker.tsx +341 -0
  37. package/src/components/timepicker/CTimePicker.utils.ts +27 -0
  38. package/src/components/timepicker/CTimePickerRollCol.tsx +57 -0
  39. package/src/index.ts +8 -0
@@ -1,6 +1,8 @@
1
1
  import PropTypes from 'prop-types'
2
2
  import React, { forwardRef, HTMLAttributes, ReactNode } from 'react'
3
3
 
4
+ import { cilFilterX } from '@coreui/icons-pro'
5
+
4
6
  import CIcon from '@coreui/icons-react'
5
7
 
6
8
  interface CSmartTableCleanerProps extends HTMLAttributes<HTMLButtonElement> {
@@ -9,22 +11,7 @@ interface CSmartTableCleanerProps extends HTMLAttributes<HTMLButtonElement> {
9
11
  }
10
12
 
11
13
  export const CSmartTableCleaner = forwardRef<HTMLButtonElement, CSmartTableCleanerProps>(
12
- (
13
- {
14
- content = (
15
- <CIcon
16
- width={18}
17
- icon={[
18
- '512 512',
19
- '<polygon fill="var(--ci-primary-color, currentColor)" points="40 16 40 53.828 109.024 136 150.815 136 76.896 48 459.51 48 304 242.388 304 401.373 241.373 464 240 464 240 368 208 368 208 496 254.627 496 336 414.627 336 253.612 496 53.612 496 16 40 16" class="ci-primary"></polygon><polygon fill="var(--ci-primary-color, currentColor)" points="166.403 248.225 226.864 187.763 204.237 165.135 143.775 225.597 83.313 165.135 60.687 187.763 121.148 248.225 60.687 308.687 83.313 331.314 143.775 270.852 204.237 331.314 226.864 308.687 166.403 248.225" class="ci-primary"></polygon>',
20
- ]}
21
- />
22
- ),
23
- isFiltered,
24
- ...rest
25
- },
26
- ref,
27
- ) => {
14
+ ({ content = <CIcon width={18} icon={cilFilterX} />, isFiltered, ...rest }, ref) => {
28
15
  return (
29
16
  <button
30
17
  type="button"
@@ -59,8 +59,6 @@ export interface SorterValue {
59
59
  export interface TableFilter {
60
60
  lazy?: boolean
61
61
  external?: boolean
62
- label?: string
63
- placeholder?: string
64
62
  }
65
63
 
66
64
  export interface NoItemsView {
@@ -75,14 +73,9 @@ export interface CSmartTableProps extends HTMLAttributes<HTMLDivElement> {
75
73
  * @default 1
76
74
  */
77
75
  activePage?: number
78
- /**
79
- * A string of all className you want applied to the base component.
80
- */
81
- className?: string
82
76
  /**
83
77
  * When set, displays table cleaner above table, next to the table filter (or in place of table filter if `tableFilter` prop is not set)
84
78
  * Cleaner resets `tableFilterValue`, `columnFilterValue`, `sorterValue`. If clean is possible it is clickable (`tabIndex="0"` `role="button"`, `color="danger"`), otherwise it is not clickable and transparent. Cleaner can be customized through the `cleaner` slot.
85
- *
86
79
  */
87
80
  cleaner?: boolean
88
81
  /**
@@ -276,8 +269,6 @@ export interface CSmartTableProps extends HTMLAttributes<HTMLDivElement> {
276
269
  * When set, displays table filter above table, allowing filtering by specific column.
277
270
  *
278
271
  * Column filter can be customized, by passing prop as object with additional options as keys. Available options:
279
- * - placeholder (String) - Sets custom table filter placeholder.
280
- * - label (String) - Sets custom table filter label.
281
272
  * - external (Boolean) - Disables automatic filtering inside component.
282
273
  * - lazy (Boolean) - Set to true to trigger filter updates only on change event.
283
274
  */
@@ -0,0 +1,341 @@
1
+ /* eslint-disable react/prop-types */
2
+ import React, { forwardRef, ReactNode, useEffect, useRef, useState } from 'react'
3
+ import PropTypes from 'prop-types'
4
+ import classNames from 'classnames'
5
+
6
+ import { CFormInput } from '../form/CFormInput'
7
+ import { CFormSelect } from '../form/CFormSelect'
8
+ import { CInputGroup } from '../form/CInputGroup'
9
+ import { CInputGroupText } from '../form/CInputGroupText'
10
+
11
+ import { CTimePickerRollCol } from './CTimePickerRollCol'
12
+ import { CPicker, CPickerProps } from './../picker/CPicker'
13
+ import { convert12hTo24h, convert24hTo12h, getAmPm, isAmPm } from './CTimePicker.utils'
14
+
15
+ export interface CTimePickerProps
16
+ extends Omit<
17
+ CPickerProps,
18
+ 'autoClose' | 'component' | 'dark' | 'placeholder' | 'variant' | 'onChange'
19
+ > {
20
+ /**
21
+ * A string of all className you want applied to the component.
22
+ */
23
+ className?: string
24
+ /**
25
+ * Enables selection cleaner element.
26
+ */
27
+ cleaner?: boolean
28
+ /**
29
+ * Cleaner icon.
30
+ */
31
+ cleanerIcon?: ReactNode | boolean
32
+ /**
33
+ * Default input icon.
34
+ */
35
+ defaultIcon?: ReactNode | boolean
36
+ /**
37
+ * Toggle the readonly state for the component.
38
+ */
39
+ inputReadOnly?: boolean
40
+ /**
41
+ * Sets the default locale for components. If not set, it is inherited from the navigator.language.
42
+ */
43
+ locale?: string
44
+ /**
45
+ * Callback fired when the user hovers over the calendar cell.
46
+ */
47
+ onChange?: (timeString: string, localeTimeString: string, date: Date) => void
48
+ /**
49
+ * Specifies a short hint that is visible in the input.
50
+ */
51
+ placeholder?: string
52
+ /**
53
+ * Size the component small or large.
54
+ */
55
+ size?: 'sm' | 'lg'
56
+ /**
57
+ * Default value of the component
58
+ */
59
+ value?: Date | string | null
60
+ /**
61
+ * Set the time picker variant to a roll or select.
62
+ */
63
+ variant?: 'roll' | 'select'
64
+ }
65
+
66
+ export const CTimePicker = forwardRef<HTMLDivElement | HTMLLIElement, CTimePickerProps>(
67
+ (
68
+ {
69
+ className,
70
+ cleaner = true,
71
+ cleanerIcon,
72
+ container = 'dropdown',
73
+ defaultIcon,
74
+ disabled,
75
+ footer = true,
76
+ inputReadOnly,
77
+ locale = typeof navigator === 'undefined' ? 'en-US' : navigator.language,
78
+ onChange,
79
+ placeholder = 'Select time',
80
+ size,
81
+ value,
82
+ variant = 'roll',
83
+ ...rest
84
+ },
85
+ ref,
86
+ ) => {
87
+ const inputRef = useRef<HTMLInputElement>(null)
88
+ const [date, setDate] = useState<Date | null>(
89
+ value ? (value instanceof Date ? new Date(value) : new Date(`1970-01-01 ${value}`)) : null,
90
+ )
91
+ const [_ampm, setAmPm] = useState<'am' | 'pm'>(date ? getAmPm(new Date(date), locale) : 'am')
92
+
93
+ useEffect(() => {
94
+ setDate(
95
+ value ? (value instanceof Date ? new Date(value) : new Date(`1970-01-01 ${value}`)) : null,
96
+ )
97
+ }, [value])
98
+
99
+ useEffect(() => {
100
+ if (inputRef.current) {
101
+ inputRef.current.value = date ? date.toLocaleTimeString(locale) : ''
102
+ }
103
+ }, [date])
104
+
105
+ const getHours = (locale: string) =>
106
+ Array.from(new Array(isAmPm(locale) ? 12 : 24), (_, i) => {
107
+ return {
108
+ value: isAmPm(locale) ? i + 1 : i,
109
+ label: (isAmPm(locale) ? i + 1 : i).toLocaleString(locale),
110
+ }
111
+ })
112
+
113
+ const getMinutesOrSeconds = (locale: string) =>
114
+ Array.from(new Array(60), (_, i) => {
115
+ return {
116
+ value: i,
117
+ label: i.toLocaleString(locale).padStart(2, (0).toLocaleString(locale)),
118
+ }
119
+ })
120
+
121
+ const selectedHour = date
122
+ ? isAmPm(locale)
123
+ ? convert24hTo12h(date.getHours())
124
+ : date.getHours()
125
+ : null
126
+
127
+ const selectedMinutes = date ? date.getMinutes() : null
128
+
129
+ const selectedSeconds = date ? date.getSeconds() : null
130
+
131
+ const handleClear = (event: React.MouseEvent<HTMLElement>) => {
132
+ event.stopPropagation()
133
+ setDate(null)
134
+ }
135
+
136
+ const handleTimeChange = (set: 'hours' | 'minutes' | 'seconds' | 'toggle', value: string) => {
137
+ const _date = date || new Date('1970-01-01')
138
+
139
+ if (set === 'toggle') {
140
+ if (value === 'am') {
141
+ setAmPm('am')
142
+ _date.setHours(_date.getHours() - 12)
143
+ }
144
+ if (value === 'pm') {
145
+ setAmPm('pm')
146
+ _date.setHours(_date.getHours() + 12)
147
+ }
148
+ }
149
+
150
+ if (set === 'hours') {
151
+ if (isAmPm(locale)) {
152
+ _date.setHours(convert12hTo24h(_ampm, parseInt(value)))
153
+ } else {
154
+ _date.setHours(parseInt(value))
155
+ }
156
+ }
157
+
158
+ if (set === 'minutes') {
159
+ _date.setMinutes(parseInt(value))
160
+ }
161
+
162
+ if (set === 'seconds') {
163
+ _date.setSeconds(parseInt(value))
164
+ }
165
+
166
+ setDate(new Date(_date))
167
+ onChange && onChange(_date.toTimeString(), _date.toLocaleTimeString(), _date)
168
+ }
169
+
170
+ const InputGroup = () => (
171
+ <CInputGroup className="picker-input-group" size={size}>
172
+ <CFormInput
173
+ disabled={disabled}
174
+ placeholder={placeholder}
175
+ readOnly={inputReadOnly}
176
+ ref={inputRef}
177
+ />
178
+ {(defaultIcon !== false || cleaner) && (
179
+ <CInputGroupText>
180
+ {defaultIcon !== false && (
181
+ <span className="picker-input-group-indicator">
182
+ {defaultIcon ? (
183
+ defaultIcon
184
+ ) : (
185
+ <span className="picker-input-group-icon time-picker-input-icon" />
186
+ )}
187
+ </span>
188
+ )}
189
+ {cleaner && date && (
190
+ <span
191
+ className="picker-input-group-cleaner"
192
+ role="button"
193
+ onClick={(event) => handleClear(event)}
194
+ >
195
+ {cleanerIcon ? (
196
+ cleanerIcon
197
+ ) : (
198
+ <span className="picker-input-group-icon time-picker-cleaner-icon" />
199
+ )}
200
+ </span>
201
+ )}
202
+ </CInputGroupText>
203
+ )}
204
+ </CInputGroup>
205
+ )
206
+
207
+ const TimePickerSelect = () => {
208
+ return (
209
+ <>
210
+ <span className="time-picker-inline-icon" />
211
+ <CFormSelect
212
+ disabled={disabled}
213
+ options={getHours(locale).map((option) => {
214
+ return {
215
+ value: option.value.toString(),
216
+ label: option.label,
217
+ }
218
+ })}
219
+ onChange={(event: React.ChangeEvent<HTMLSelectElement>) =>
220
+ handleTimeChange('hours', event.target.value)
221
+ }
222
+ value={selectedHour ? selectedHour : '0'}
223
+ />
224
+ <span className="px-1">:</span>
225
+ <CFormSelect
226
+ disabled={disabled}
227
+ options={Array.from(new Array(60), (_, i) => {
228
+ return {
229
+ value: i.toString(),
230
+ label: i.toLocaleString(locale).padStart(2, (0).toLocaleString(locale)),
231
+ }
232
+ })}
233
+ onChange={(event: React.ChangeEvent<HTMLSelectElement>) =>
234
+ handleTimeChange('minutes', event.target.value)
235
+ }
236
+ value={selectedMinutes ? selectedMinutes : '0'}
237
+ />
238
+ <span className="px-1">:</span>
239
+ <CFormSelect
240
+ disabled={disabled}
241
+ options={Array.from(new Array(60), (_, i) => {
242
+ return {
243
+ value: i.toString(),
244
+ label: i.toLocaleString(locale).padStart(2, (0).toLocaleString(locale)),
245
+ }
246
+ })}
247
+ onChange={(event: React.ChangeEvent<HTMLSelectElement>) =>
248
+ handleTimeChange('seconds', event.target.value)
249
+ }
250
+ value={selectedSeconds ? selectedSeconds : '0'}
251
+ />
252
+ {isAmPm(locale) && (
253
+ <CFormSelect
254
+ disabled={disabled}
255
+ options={[
256
+ { value: 'am', label: 'AM' },
257
+ { value: 'pm', label: 'PM' },
258
+ ]}
259
+ onChange={(event: React.ChangeEvent<HTMLSelectElement>) =>
260
+ handleTimeChange('toggle', event.target.value)
261
+ }
262
+ value={_ampm}
263
+ />
264
+ )}
265
+ </>
266
+ )
267
+ }
268
+
269
+ const TimePickerRoll = () => (
270
+ <>
271
+ <CTimePickerRollCol
272
+ elements={getHours(locale)}
273
+ onClick={(index: number) => handleTimeChange('hours', index.toString())}
274
+ selected={selectedHour}
275
+ />
276
+ <CTimePickerRollCol
277
+ elements={getMinutesOrSeconds(locale)}
278
+ selected={selectedMinutes}
279
+ onClick={(index: number) => handleTimeChange('minutes', index.toString())}
280
+ />
281
+ <CTimePickerRollCol
282
+ elements={getMinutesOrSeconds(locale)}
283
+ selected={selectedSeconds}
284
+ onClick={(index: number) => handleTimeChange('seconds', index.toString())}
285
+ />
286
+ {isAmPm(locale) && (
287
+ <CTimePickerRollCol
288
+ elements={[
289
+ { value: 'am', label: 'AM' },
290
+ { value: 'pm', label: 'PM' },
291
+ ]}
292
+ selected={_ampm}
293
+ onClick={(value: string) => handleTimeChange('toggle', value)}
294
+ />
295
+ )}
296
+ </>
297
+ )
298
+
299
+ const _className = classNames('time-picker', className)
300
+
301
+ return (
302
+ <CPicker
303
+ className={_className}
304
+ container={container}
305
+ disabled={disabled}
306
+ footer={footer}
307
+ onCancelClick={() => {
308
+ setDate(
309
+ value
310
+ ? value instanceof Date
311
+ ? new Date(value)
312
+ : new Date(`1970-01-01 ${value}`)
313
+ : new Date(),
314
+ )
315
+ }}
316
+ toggler={InputGroup()}
317
+ {...rest}
318
+ ref={ref}
319
+ >
320
+ <div
321
+ className={classNames('time-picker-body', {
322
+ ['time-picker-roll']: variant === 'roll',
323
+ })}
324
+ >
325
+ {variant === 'select' ? <TimePickerSelect /> : TimePickerRoll()}
326
+ </div>
327
+ </CPicker>
328
+ )
329
+ },
330
+ )
331
+
332
+ CTimePicker.propTypes = {
333
+ ...CPicker.propTypes,
334
+ className: PropTypes.string,
335
+ locale: PropTypes.string,
336
+ onChange: PropTypes.func,
337
+ value: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.string]),
338
+ variant: PropTypes.oneOf(['roll', 'select']),
339
+ }
340
+
341
+ CTimePicker.displayName = 'CTimePicker'
@@ -0,0 +1,27 @@
1
+ export const convert12hTo24h = (abbr: 'am' | 'pm', hour: number) => {
2
+ if (abbr === 'am' && hour === 12) {
3
+ return 0
4
+ }
5
+ if (abbr === 'am') {
6
+ return hour
7
+ }
8
+ if (abbr === 'pm' && hour === 12) {
9
+ return 12
10
+ }
11
+ return hour + 12
12
+ }
13
+
14
+ export const convert24hTo12h = (hour: number) => hour % 12 || 12
15
+
16
+ export const getAmPm = (date: Date, locale: string) => {
17
+ if (date.toLocaleTimeString(locale).includes('AM')) {
18
+ return 'am'
19
+ }
20
+ if (date.toLocaleTimeString(locale).includes('PM')) {
21
+ return 'pm'
22
+ }
23
+ return date.getHours() >= 12 ? 'pm' : 'am'
24
+ }
25
+
26
+ export const isAmPm = (locale: string) =>
27
+ ['am', 'AM', 'pm', 'PM'].some((el) => new Date().toLocaleString(locale).includes(el))
@@ -0,0 +1,57 @@
1
+ import React, { forwardRef, useEffect, useRef } from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import classNames from 'classnames'
4
+
5
+ import { useForkedRef } from '../../utils/hooks'
6
+
7
+ export interface Element {
8
+ value: number | string
9
+ label: number | string
10
+ }
11
+
12
+ export interface CTimePickerRollColProps {
13
+ elements: Element[]
14
+ onClick?: (value: number | string) => void
15
+ selected?: number | string | null
16
+ }
17
+
18
+ export const CTimePickerRollCol = forwardRef<HTMLDivElement, CTimePickerRollColProps>(
19
+ ({ elements, onClick, selected }, ref) => {
20
+ const colRef = useRef<HTMLDivElement>(null)
21
+ const forkedRef = useForkedRef(ref, colRef)
22
+
23
+ useEffect(() => {
24
+ const nodeEl = colRef.current?.querySelector('.selected')
25
+ if (nodeEl && nodeEl instanceof HTMLElement) {
26
+ colRef.current?.scrollTo({ top: nodeEl.offsetTop, behavior: 'smooth' })
27
+ }
28
+ }, [selected])
29
+
30
+ return (
31
+ <div className="time-picker-roll-col" ref={forkedRef}>
32
+ {elements.map((element, index) => {
33
+ return (
34
+ <div
35
+ className={classNames('time-picker-roll-cell', {
36
+ selected: element.value === selected,
37
+ })}
38
+ key={index}
39
+ onClick={() => onClick && onClick(element.value)}
40
+ role="button"
41
+ >
42
+ {element.label}
43
+ </div>
44
+ )
45
+ })}
46
+ </div>
47
+ )
48
+ },
49
+ )
50
+
51
+ CTimePickerRollCol.propTypes = {
52
+ elements: PropTypes.array.isRequired,
53
+ onClick: PropTypes.func,
54
+ selected: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
55
+ }
56
+
57
+ CTimePickerRollCol.displayName = 'CTimePickerRollCol'
package/src/index.ts CHANGED
@@ -15,6 +15,7 @@ import { CBreadcrumbItem } from './components/breadcrumb/CBreadcrumbItem'
15
15
  import { CButton } from './components/button/CButton'
16
16
  import { CButtonGroup } from './components/button-group/CButtonGroup'
17
17
  import { CButtonToolbar } from './components/button-group/CButtonToolbar'
18
+ import { CCalendar } from './components/calendar/CCalendar'
18
19
  import { CCallout } from './components/callout/CCallout'
19
20
  import { CCard } from './components/card/CCard'
20
21
  import { CCardBody } from './components/card/CCardBody'
@@ -32,6 +33,8 @@ import { CCarouselCaption } from './components/carousel/CCarouselCaption'
32
33
  import { CCarouselItem } from './components/carousel/CCarouselItem'
33
34
  import { CCollapse } from './components/collapse/CCollapse'
34
35
  import { CCloseButton } from './components/close-button/CCloseButton'
36
+ import { CDatePicker } from './components/datepicker/CDatePicker'
37
+ import { CDateRangePicker } from './components/daterangepicker/CDateRangePicker'
35
38
  import { CDropdown } from './components/dropdown/CDropdown'
36
39
  import { CDropdownDivider } from './components/dropdown/CDropdownDivider'
37
40
  import { CDropdownHeader } from './components/dropdown/CDropdownHeader'
@@ -115,6 +118,7 @@ import { CTableHeaderCell } from './components/table/CTableHeaderCell'
115
118
  import { CTableRow } from './components/table/CTableRow'
116
119
  import { CTabContent } from './components/tabs/CTabContent'
117
120
  import { CTabPane } from './components/tabs/CTabPane'
121
+ import { CTimePicker } from './components/timepicker/CTimePicker'
118
122
  import { CToast } from './components/toast/CToast'
119
123
  import { CToastBody } from './components/toast/CToastBody'
120
124
  import { CToastClose } from './components/toast/CToastClose'
@@ -140,6 +144,7 @@ export {
140
144
  CButton,
141
145
  CButtonGroup,
142
146
  CButtonToolbar,
147
+ CCalendar,
143
148
  CCallout,
144
149
  CCard,
145
150
  CCardBody,
@@ -156,6 +161,8 @@ export {
156
161
  CCarouselItem,
157
162
  CCloseButton,
158
163
  CCollapse,
164
+ CDatePicker,
165
+ CDateRangePicker,
159
166
  CDropdown,
160
167
  CDropdownDivider,
161
168
  CDropdownHeader,
@@ -240,6 +247,7 @@ export {
240
247
  CTableRow,
241
248
  CTabContent,
242
249
  CTabPane,
250
+ CTimePicker,
243
251
  CToast,
244
252
  CToastBody,
245
253
  CToastClose,