@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.
- package/README.md +1 -1
- package/dist/components/calendar/CCalendar.d.ts +106 -0
- package/dist/components/calendar/CCalendar.utils.d.ts +21 -0
- package/dist/components/datepicker/CDatePicker.d.ts +13 -0
- package/dist/components/daterangepicker/CDateRangePicker.d.ts +65 -0
- package/dist/components/dropdown/CDropdown.d.ts +10 -1
- package/dist/components/dropdown/CDropdownToggle.d.ts +4 -0
- package/dist/components/dropdown/CDropdownToggleCustom.d.ts +15 -0
- package/dist/components/picker/CPicker.d.ts +78 -0
- package/dist/components/smart-table/CSmartTableInterface.d.ts +0 -9
- package/dist/components/timepicker/CTimePicker.d.ts +45 -0
- package/dist/components/timepicker/CTimePicker.utils.d.ts +17 -0
- package/dist/components/timepicker/CTimePickerRollCol.d.ts +11 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.es.js +2568 -718
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +2849 -995
- package/dist/index.js.map +1 -1
- package/dist/utils/calendar.d.ts +21 -0
- package/dist/utils/time.d.ts +17 -0
- package/package.json +11 -8
- package/src/components/accordion/CAccordionButton.tsx +2 -1
- package/src/components/accordion/__tests__/__snapshots__/CAccordionButton.spec.tsx.snap +2 -0
- package/src/components/accordion/__tests__/__snapshots__/CAccordionHeader.spec.tsx.snap +2 -0
- package/src/components/calendar/CCalendar.tsx +528 -0
- package/src/components/calendar/__tests__/CCalendar.spec.tsx +9 -0
- package/src/components/calendar/__tests__/__snapshots__/CCalendar.spec.tsx.snap +573 -0
- package/src/components/datepicker/CDatePicker.tsx +50 -0
- package/src/components/datepicker/__tests__/CDatePicker.spec.tsx +9 -0
- package/src/components/datepicker/__tests__/__snapshots__/CDatePicker.spec.tsx.snap +617 -0
- package/src/components/daterangepicker/CDateRangePicker.tsx +427 -0
- package/src/components/daterangepicker/__tests__/CDateRangePicker.spec.tsx +9 -0
- package/src/components/daterangepicker/__tests__/__snapshots__/CDateRangePicker.spec.tsx.snap +1201 -0
- package/src/components/dropdown/CDropdown.tsx +19 -24
- package/src/components/dropdown/CDropdownMenu.tsx +57 -3
- package/src/components/dropdown/CDropdownToggle.tsx +28 -5
- package/src/components/picker/CPicker.tsx +207 -0
- package/src/components/picker/__tests__/CPicker.spec.tsx +9 -0
- package/src/components/picker/__tests__/__snapshots__/CPicker.spec.tsx.snap +23 -0
- package/src/components/smart-table/CSmartTable.tsx +44 -96
- package/src/components/smart-table/CSmartTableCleaner.tsx +3 -16
- package/src/components/smart-table/CSmartTableInterface.tsx +0 -9
- package/src/components/timepicker/CTimePicker.tsx +320 -0
- package/src/components/timepicker/CTimePickerRollCol.tsx +63 -0
- package/src/components/timepicker/__tests__/CTimePicker.spec.tsx +9 -0
- package/src/index.ts +8 -0
- package/src/utils/calendar.ts +193 -0
- package/src/utils/time.ts +58 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import React, { ChangeEvent, forwardRef, useEffect, useMemo,
|
|
1
|
+
import React, { ChangeEvent, forwardRef, useEffect, useMemo, useState } from 'react'
|
|
2
2
|
import PropTypes from 'prop-types'
|
|
3
3
|
|
|
4
|
+
import { cilArrowBottom, cilArrowTop, cilSwapVertical } from '@coreui/icons-pro'
|
|
5
|
+
|
|
4
6
|
import {
|
|
5
7
|
ColumnFilterValue,
|
|
6
8
|
CSmartTableProps,
|
|
@@ -55,36 +57,9 @@ export const CSmartTable = forwardRef<HTMLDivElement, CSmartTableProps>(
|
|
|
55
57
|
scopedColumns,
|
|
56
58
|
selectable,
|
|
57
59
|
sorterValue,
|
|
58
|
-
sortingIcon =
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
icon={[
|
|
62
|
-
'512 512',
|
|
63
|
-
'<polygon fill="var(--ci-primary-color, currentColor)" points="384 433.373 384 160 352 160 352 434.51 282.177 364.687 259.55 387.313 367.432 495.196 475.313 387.313 452.687 364.687 384 433.373" class="ci-primary"></polygon><polygon fill="var(--ci-primary-color, currentColor)" points="159.432 17.372 51.55 125.255 74.177 147.882 144 78.059 144 352 176 352 176 79.195 244.687 147.882 267.313 125.255 159.432 17.372" class="ci-primary"></polygon>',
|
|
64
|
-
]}
|
|
65
|
-
key="csv"
|
|
66
|
-
/>
|
|
67
|
-
),
|
|
68
|
-
sortingIconAscending = (
|
|
69
|
-
<CIcon
|
|
70
|
-
width={18}
|
|
71
|
-
icon={[
|
|
72
|
-
'512 512',
|
|
73
|
-
'<polygon fill="var(--ci-primary-color, currentColor)" points="390.624 150.625 256 16 121.376 150.625 144.004 173.252 240.001 77.254 240.001 495.236 272.001 495.236 272.001 77.257 367.996 173.252 390.624 150.625" class="ci-primary"></polygon>',
|
|
74
|
-
]}
|
|
75
|
-
key="cat"
|
|
76
|
-
/>
|
|
77
|
-
),
|
|
78
|
-
sortingIconDescending = (
|
|
79
|
-
<CIcon
|
|
80
|
-
width={18}
|
|
81
|
-
icon={[
|
|
82
|
-
'512 512',
|
|
83
|
-
'<polygon fill="var(--ci-primary-color, currentColor)" points="367.997 338.75 271.999 434.747 271.999 17.503 239.999 17.503 239.999 434.745 144.003 338.75 121.376 361.377 256 496 390.624 361.377 367.997 338.75" class="ci-primary"></polygon>',
|
|
84
|
-
]}
|
|
85
|
-
key="cab"
|
|
86
|
-
/>
|
|
87
|
-
),
|
|
60
|
+
sortingIcon = <CIcon width={18} icon={cilSwapVertical} key="csv" />,
|
|
61
|
+
sortingIconAscending = <CIcon width={18} icon={cilArrowTop} key="cat" />,
|
|
62
|
+
sortingIconDescending = <CIcon width={18} icon={cilArrowBottom} key="cab" />,
|
|
88
63
|
tableBodyProps,
|
|
89
64
|
tableFootProps,
|
|
90
65
|
tableFilter,
|
|
@@ -98,45 +73,40 @@ export const CSmartTable = forwardRef<HTMLDivElement, CSmartTableProps>(
|
|
|
98
73
|
ref,
|
|
99
74
|
) => {
|
|
100
75
|
// instance data
|
|
101
|
-
const compData = useRef({ firstRun: true, columnFiltered: 0, changeItems: 0 }).current
|
|
102
|
-
|
|
103
|
-
const previousSorterValue = useRef<SorterValue>()
|
|
104
|
-
const previousItemsValue = useRef<Item[]>()
|
|
105
|
-
|
|
106
76
|
const [_activePage, setActivePage] = useState<number>(activePage)
|
|
107
77
|
const [_items, setItems] = useState<ItemInternal[]>([])
|
|
108
78
|
const [_itemsPerPage, setItemsPerPage] = useState(itemsPerPage)
|
|
109
|
-
const [columnFilterState, setColumnFilterState] = useState<ColumnFilterValue>(
|
|
110
|
-
columnFilterValue ? columnFilterValue : {},
|
|
111
|
-
)
|
|
79
|
+
const [columnFilterState, setColumnFilterState] = useState<ColumnFilterValue>({})
|
|
112
80
|
const [selectedAll, setSelectedAll] = useState<boolean | string>()
|
|
113
|
-
const [sorterState, setSorterState] = useState<SorterValue>(
|
|
81
|
+
const [sorterState, setSorterState] = useState<SorterValue>({})
|
|
114
82
|
const [tableFilterState, setTableFilterState] = useState<string>(
|
|
115
83
|
tableFilterValue ? tableFilterValue : '',
|
|
116
84
|
)
|
|
117
85
|
|
|
86
|
+
// watch
|
|
118
87
|
useMemo(() => {
|
|
119
|
-
if (
|
|
120
|
-
|
|
121
|
-
setSorterState({ ...sorterValue })
|
|
88
|
+
if (items && items.length < _itemsPerPage * _activePage - _itemsPerPage) {
|
|
89
|
+
setActivePage(1)
|
|
122
90
|
}
|
|
123
|
-
|
|
91
|
+
|
|
92
|
+
// Create the internal array of items
|
|
93
|
+
Array.isArray(items) &&
|
|
94
|
+
setItems(
|
|
95
|
+
items?.map((item: Item, index: number) => {
|
|
96
|
+
return { ...item, _id: index }
|
|
97
|
+
}),
|
|
98
|
+
)
|
|
99
|
+
}, [JSON.stringify(items)])
|
|
124
100
|
|
|
125
101
|
useMemo(() => {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
items?.map((item: Item, index: number) => {
|
|
135
|
-
return { ...item, _id: index }
|
|
136
|
-
}),
|
|
137
|
-
)
|
|
138
|
-
}
|
|
139
|
-
}, [items])
|
|
102
|
+
columnFilterValue && setColumnFilterState(columnFilterValue)
|
|
103
|
+
}, [JSON.stringify(columnFilterValue)])
|
|
104
|
+
|
|
105
|
+
useMemo(() => {
|
|
106
|
+
setSorterState({ ...sorterValue })
|
|
107
|
+
}, [JSON.stringify(sorterValue)])
|
|
108
|
+
|
|
109
|
+
useMemo(() => setItemsPerPage(itemsPerPage), [itemsPerPage])
|
|
140
110
|
|
|
141
111
|
// functions
|
|
142
112
|
|
|
@@ -225,17 +195,7 @@ export const CSmartTable = forwardRef<HTMLDivElement, CSmartTableProps>(
|
|
|
225
195
|
const itemsDataColumns = rawColumnNames.filter((name) => genCols.includes(name))
|
|
226
196
|
|
|
227
197
|
// variables
|
|
228
|
-
|
|
229
|
-
useMemo(() => {
|
|
230
|
-
compData.columnFiltered++
|
|
231
|
-
}, [
|
|
232
|
-
JSON.stringify(columnFilter),
|
|
233
|
-
JSON.stringify(columnFilterState),
|
|
234
|
-
itemsDataColumns.join(''),
|
|
235
|
-
compData.changeItems,
|
|
236
|
-
])
|
|
237
|
-
|
|
238
|
-
const columnFiltered: ItemInternal[] = useMemo(() => {
|
|
198
|
+
const filteredColumns: ItemInternal[] = useMemo(() => {
|
|
239
199
|
let items = _items
|
|
240
200
|
if (columnFilter && typeof columnFilter === 'object' && columnFilter.external) {
|
|
241
201
|
return items
|
|
@@ -249,10 +209,10 @@ export const CSmartTable = forwardRef<HTMLDivElement, CSmartTableProps>(
|
|
|
249
209
|
}
|
|
250
210
|
})
|
|
251
211
|
return items
|
|
252
|
-
}, [
|
|
212
|
+
}, [JSON.stringify(columnFilterState), JSON.stringify(_items)])
|
|
253
213
|
|
|
254
|
-
const
|
|
255
|
-
let items =
|
|
214
|
+
const filteredTable: ItemInternal[] = useMemo(() => {
|
|
215
|
+
let items = filteredColumns
|
|
256
216
|
if (
|
|
257
217
|
!tableFilterState ||
|
|
258
218
|
(tableFilter && typeof tableFilter === 'object' && tableFilter.external)
|
|
@@ -265,12 +225,7 @@ export const CSmartTable = forwardRef<HTMLDivElement, CSmartTableProps>(
|
|
|
265
225
|
return !!itemsDataColumns.find((key) => valueContainFilter(item[key]))
|
|
266
226
|
})
|
|
267
227
|
return items
|
|
268
|
-
}, [
|
|
269
|
-
compData.columnFiltered,
|
|
270
|
-
tableFilterState,
|
|
271
|
-
JSON.stringify(tableFilter),
|
|
272
|
-
JSON.stringify(columnFiltered),
|
|
273
|
-
])
|
|
228
|
+
}, [tableFilterState, JSON.stringify(tableFilterValue), JSON.stringify(filteredColumns)])
|
|
274
229
|
|
|
275
230
|
const sortedItems: ItemInternal[] = useMemo(() => {
|
|
276
231
|
const col = sorterState?.column
|
|
@@ -279,12 +234,11 @@ export const CSmartTable = forwardRef<HTMLDivElement, CSmartTableProps>(
|
|
|
279
234
|
!itemsDataColumns.includes(col) ||
|
|
280
235
|
(columnSorter && typeof columnSorter === 'object' && columnSorter.external)
|
|
281
236
|
) {
|
|
282
|
-
return
|
|
237
|
+
return filteredTable
|
|
283
238
|
}
|
|
284
|
-
|
|
285
|
-
// const flip = sorterState.asc ? 1 : -1
|
|
239
|
+
|
|
286
240
|
const flip = sorterState.state === 'asc' ? 1 : sorterState.state === 'desc' ? -1 : 0
|
|
287
|
-
const sorted =
|
|
241
|
+
const sorted = filteredTable.slice().sort((item, item2) => {
|
|
288
242
|
const value = item[col]
|
|
289
243
|
const value2 = item2[col]
|
|
290
244
|
const a = typeof value === 'number' ? value : String(value).toLowerCase()
|
|
@@ -292,7 +246,13 @@ export const CSmartTable = forwardRef<HTMLDivElement, CSmartTableProps>(
|
|
|
292
246
|
return a > b ? 1 * flip : b > a ? -1 * flip : 0
|
|
293
247
|
})
|
|
294
248
|
return sorted
|
|
295
|
-
}, [
|
|
249
|
+
}, [
|
|
250
|
+
JSON.stringify(filteredTable),
|
|
251
|
+
JSON.stringify(sorterState),
|
|
252
|
+
JSON.stringify(columnSorter),
|
|
253
|
+
JSON.stringify(filteredColumns),
|
|
254
|
+
JSON.stringify(_items),
|
|
255
|
+
])
|
|
296
256
|
|
|
297
257
|
const numberOfPages: number = _itemsPerPage ? Math.ceil(sortedItems.length / _itemsPerPage) : 1
|
|
298
258
|
|
|
@@ -332,21 +292,9 @@ export const CSmartTable = forwardRef<HTMLDivElement, CSmartTableProps>(
|
|
|
332
292
|
}, [tableFilterState])
|
|
333
293
|
|
|
334
294
|
useEffect(() => {
|
|
335
|
-
|
|
295
|
+
onFilteredItemsChange && onFilteredItemsChange(sortedItems)
|
|
336
296
|
}, [JSON.stringify(sortedItems)])
|
|
337
297
|
|
|
338
|
-
// watch
|
|
339
|
-
useMemo(() => setItemsPerPage(itemsPerPage), [itemsPerPage])
|
|
340
|
-
useMemo(
|
|
341
|
-
() =>
|
|
342
|
-
JSON.stringify(previousSorterValue.current) !== JSON.stringify(sorterValue) &&
|
|
343
|
-
setSorterState({ ...sorterValue }),
|
|
344
|
-
[sorterValue],
|
|
345
|
-
)
|
|
346
|
-
useMemo(() => setColumnFilterState({ ...columnFilterValue }), [columnFilterValue])
|
|
347
|
-
|
|
348
|
-
compData.firstRun = false
|
|
349
|
-
|
|
350
298
|
const handleRowChecked = (id: number, value: boolean) => {
|
|
351
299
|
const newArr = [..._items]
|
|
352
300
|
newArr[id]._selected = value
|
|
@@ -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,320 @@
|
|
|
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 {
|
|
14
|
+
convert12hTo24h,
|
|
15
|
+
convertTimeToDate,
|
|
16
|
+
getAmPm,
|
|
17
|
+
getListOfHours,
|
|
18
|
+
getMinutesOrSeconds,
|
|
19
|
+
getSelectedHour,
|
|
20
|
+
getSelectedMinutes,
|
|
21
|
+
getSelectedSeconds,
|
|
22
|
+
isAmPm,
|
|
23
|
+
isValidTime,
|
|
24
|
+
} from '../../utils/time'
|
|
25
|
+
|
|
26
|
+
export interface CTimePickerProps
|
|
27
|
+
extends Omit<
|
|
28
|
+
CPickerProps,
|
|
29
|
+
'autoClose' | 'component' | 'dark' | 'placeholder' | 'variant' | 'onChange'
|
|
30
|
+
> {
|
|
31
|
+
/**
|
|
32
|
+
* A string of all className you want applied to the component.
|
|
33
|
+
*/
|
|
34
|
+
className?: string
|
|
35
|
+
/**
|
|
36
|
+
* Toggle visibility or set the content of the cleaner button.
|
|
37
|
+
*/
|
|
38
|
+
cleaner?: ReactNode | boolean
|
|
39
|
+
/**
|
|
40
|
+
* Toggle visibility or set the content of the input indicator.
|
|
41
|
+
*/
|
|
42
|
+
indicator?: ReactNode | boolean
|
|
43
|
+
/**
|
|
44
|
+
* Toggle the readonly state for the component.
|
|
45
|
+
*/
|
|
46
|
+
inputReadOnly?: boolean
|
|
47
|
+
/**
|
|
48
|
+
* Sets the default locale for components. If not set, it is inherited from the browser.
|
|
49
|
+
*/
|
|
50
|
+
locale?: string
|
|
51
|
+
/**
|
|
52
|
+
* Callback fired when the time changed.
|
|
53
|
+
*/
|
|
54
|
+
onTimeChange?: (timeString: string, localeTimeString: string, date: Date) => void
|
|
55
|
+
/**
|
|
56
|
+
* Specifies a short hint that is visible in the input.
|
|
57
|
+
*/
|
|
58
|
+
placeholder?: string
|
|
59
|
+
/**
|
|
60
|
+
* Size the component small or large.
|
|
61
|
+
*/
|
|
62
|
+
size?: 'sm' | 'lg'
|
|
63
|
+
/**
|
|
64
|
+
* Initial selected time.
|
|
65
|
+
*/
|
|
66
|
+
time?: Date | string | null
|
|
67
|
+
/**
|
|
68
|
+
* Set the time picker variant to a roll or select.
|
|
69
|
+
*/
|
|
70
|
+
variant?: 'roll' | 'select'
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const CTimePicker = forwardRef<HTMLDivElement | HTMLLIElement, CTimePickerProps>(
|
|
74
|
+
(
|
|
75
|
+
{
|
|
76
|
+
className,
|
|
77
|
+
cleaner = true,
|
|
78
|
+
container = 'dropdown',
|
|
79
|
+
disabled,
|
|
80
|
+
footer = true,
|
|
81
|
+
indicator = true,
|
|
82
|
+
inputReadOnly,
|
|
83
|
+
locale = 'default',
|
|
84
|
+
onTimeChange,
|
|
85
|
+
onHide,
|
|
86
|
+
onShow,
|
|
87
|
+
placeholder = 'Select time',
|
|
88
|
+
size,
|
|
89
|
+
time,
|
|
90
|
+
variant = 'roll',
|
|
91
|
+
...rest
|
|
92
|
+
},
|
|
93
|
+
ref,
|
|
94
|
+
) => {
|
|
95
|
+
const inputRef = useRef<HTMLInputElement>(null)
|
|
96
|
+
const [date, setDate] = useState<Date | null>(convertTimeToDate(time))
|
|
97
|
+
const [initialDate, setInitialDate] = useState<Date | null>(null)
|
|
98
|
+
const [_ampm, setAmPm] = useState<'am' | 'pm'>(date ? getAmPm(new Date(date), locale) : 'am')
|
|
99
|
+
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
setDate(convertTimeToDate(time))
|
|
102
|
+
}, [time])
|
|
103
|
+
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
if (inputRef.current) {
|
|
106
|
+
inputRef.current.value = date ? date.toLocaleTimeString(locale) : ''
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
date && setAmPm(getAmPm(new Date(date), locale))
|
|
110
|
+
}, [date])
|
|
111
|
+
|
|
112
|
+
const handleClear = (event: React.MouseEvent<HTMLElement>) => {
|
|
113
|
+
event.stopPropagation()
|
|
114
|
+
setDate(null)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const handleTimeChange = (set: 'hours' | 'minutes' | 'seconds' | 'toggle', value: string) => {
|
|
118
|
+
const _date = date || new Date('1970-01-01')
|
|
119
|
+
|
|
120
|
+
if (set === 'toggle') {
|
|
121
|
+
if (value === 'am') {
|
|
122
|
+
_date.setHours(_date.getHours() - 12)
|
|
123
|
+
}
|
|
124
|
+
if (value === 'pm') {
|
|
125
|
+
_date.setHours(_date.getHours() + 12)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (set === 'hours') {
|
|
130
|
+
if (isAmPm(locale)) {
|
|
131
|
+
_date.setHours(convert12hTo24h(_ampm, parseInt(value)))
|
|
132
|
+
} else {
|
|
133
|
+
_date.setHours(parseInt(value))
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (set === 'minutes') {
|
|
138
|
+
_date.setMinutes(parseInt(value))
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (set === 'seconds') {
|
|
142
|
+
_date.setSeconds(parseInt(value))
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
setDate(new Date(_date))
|
|
146
|
+
onTimeChange && onTimeChange(_date.toTimeString(), _date.toLocaleTimeString(), _date)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const InputGroup = () => (
|
|
150
|
+
<CInputGroup className="picker-input-group" size={size}>
|
|
151
|
+
<CFormInput
|
|
152
|
+
disabled={disabled}
|
|
153
|
+
onChange={(event) =>
|
|
154
|
+
isValidTime(event.target.value) && setDate(convertTimeToDate(event.target.value))
|
|
155
|
+
}
|
|
156
|
+
placeholder={placeholder}
|
|
157
|
+
readOnly={inputReadOnly}
|
|
158
|
+
ref={inputRef}
|
|
159
|
+
/>
|
|
160
|
+
{(indicator || cleaner) && (
|
|
161
|
+
<CInputGroupText>
|
|
162
|
+
{indicator && (
|
|
163
|
+
<span className="picker-input-group-indicator">
|
|
164
|
+
{typeof indicator === 'boolean' ? (
|
|
165
|
+
<span className="picker-input-group-icon time-picker-input-icon" />
|
|
166
|
+
) : (
|
|
167
|
+
indicator
|
|
168
|
+
)}
|
|
169
|
+
</span>
|
|
170
|
+
)}
|
|
171
|
+
{cleaner && date && (
|
|
172
|
+
<span
|
|
173
|
+
className="picker-input-group-cleaner"
|
|
174
|
+
onClick={(event) => handleClear(event)}
|
|
175
|
+
role="button"
|
|
176
|
+
>
|
|
177
|
+
{typeof cleaner === 'boolean' ? (
|
|
178
|
+
<span className="picker-input-group-icon time-picker-cleaner-icon" />
|
|
179
|
+
) : (
|
|
180
|
+
cleaner
|
|
181
|
+
)}
|
|
182
|
+
</span>
|
|
183
|
+
)}
|
|
184
|
+
</CInputGroupText>
|
|
185
|
+
)}
|
|
186
|
+
</CInputGroup>
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
const TimePickerSelect = () => {
|
|
190
|
+
return (
|
|
191
|
+
<>
|
|
192
|
+
<span className="time-picker-inline-icon" />
|
|
193
|
+
<CFormSelect
|
|
194
|
+
disabled={disabled}
|
|
195
|
+
options={getListOfHours(locale).map((option) => {
|
|
196
|
+
return {
|
|
197
|
+
value: option.value.toString(),
|
|
198
|
+
label: option.label,
|
|
199
|
+
}
|
|
200
|
+
})}
|
|
201
|
+
onChange={(event: React.ChangeEvent<HTMLSelectElement>) =>
|
|
202
|
+
handleTimeChange('hours', event.target.value)
|
|
203
|
+
}
|
|
204
|
+
value={getSelectedHour(date, locale)}
|
|
205
|
+
/>
|
|
206
|
+
<>:</>
|
|
207
|
+
<CFormSelect
|
|
208
|
+
disabled={disabled}
|
|
209
|
+
options={Array.from({ length: 60 }, (_, i) => {
|
|
210
|
+
return {
|
|
211
|
+
value: i.toString(),
|
|
212
|
+
label: i.toLocaleString(locale).padStart(2, (0).toLocaleString(locale)),
|
|
213
|
+
}
|
|
214
|
+
})}
|
|
215
|
+
onChange={(event: React.ChangeEvent<HTMLSelectElement>) =>
|
|
216
|
+
handleTimeChange('minutes', event.target.value)
|
|
217
|
+
}
|
|
218
|
+
value={getSelectedMinutes(date)}
|
|
219
|
+
/>
|
|
220
|
+
<>:</>
|
|
221
|
+
<CFormSelect
|
|
222
|
+
disabled={disabled}
|
|
223
|
+
options={Array.from({ length: 60 }, (_, i) => {
|
|
224
|
+
return {
|
|
225
|
+
value: i.toString(),
|
|
226
|
+
label: i.toLocaleString(locale).padStart(2, (0).toLocaleString(locale)),
|
|
227
|
+
}
|
|
228
|
+
})}
|
|
229
|
+
onChange={(event: React.ChangeEvent<HTMLSelectElement>) =>
|
|
230
|
+
handleTimeChange('seconds', event.target.value)
|
|
231
|
+
}
|
|
232
|
+
value={getSelectedSeconds(date)}
|
|
233
|
+
/>
|
|
234
|
+
{isAmPm(locale) && (
|
|
235
|
+
<CFormSelect
|
|
236
|
+
disabled={disabled}
|
|
237
|
+
options={[
|
|
238
|
+
{ value: 'am', label: 'AM' },
|
|
239
|
+
{ value: 'pm', label: 'PM' },
|
|
240
|
+
]}
|
|
241
|
+
onChange={(event: React.ChangeEvent<HTMLSelectElement>) =>
|
|
242
|
+
handleTimeChange('toggle', event.target.value)
|
|
243
|
+
}
|
|
244
|
+
value={_ampm}
|
|
245
|
+
/>
|
|
246
|
+
)}
|
|
247
|
+
</>
|
|
248
|
+
)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const TimePickerRoll = () => (
|
|
252
|
+
<>
|
|
253
|
+
<CTimePickerRollCol
|
|
254
|
+
elements={getListOfHours(locale)}
|
|
255
|
+
onClick={(index: number) => handleTimeChange('hours', index.toString())}
|
|
256
|
+
selected={getSelectedHour(date, locale)}
|
|
257
|
+
/>
|
|
258
|
+
<CTimePickerRollCol
|
|
259
|
+
elements={getMinutesOrSeconds(locale)}
|
|
260
|
+
onClick={(index: number) => handleTimeChange('minutes', index.toString())}
|
|
261
|
+
selected={getSelectedMinutes(date)}
|
|
262
|
+
/>
|
|
263
|
+
<CTimePickerRollCol
|
|
264
|
+
elements={getMinutesOrSeconds(locale)}
|
|
265
|
+
onClick={(index: number) => handleTimeChange('seconds', index.toString())}
|
|
266
|
+
selected={getSelectedSeconds(date)}
|
|
267
|
+
/>
|
|
268
|
+
{isAmPm(locale) && (
|
|
269
|
+
<CTimePickerRollCol
|
|
270
|
+
elements={[
|
|
271
|
+
{ value: 'am', label: 'AM' },
|
|
272
|
+
{ value: 'pm', label: 'PM' },
|
|
273
|
+
]}
|
|
274
|
+
onClick={(value: string) => handleTimeChange('toggle', value)}
|
|
275
|
+
selected={_ampm}
|
|
276
|
+
/>
|
|
277
|
+
)}
|
|
278
|
+
</>
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
const _className = classNames('time-picker', className)
|
|
282
|
+
|
|
283
|
+
return (
|
|
284
|
+
<CPicker
|
|
285
|
+
className={_className}
|
|
286
|
+
container={container}
|
|
287
|
+
disabled={disabled}
|
|
288
|
+
footer={footer}
|
|
289
|
+
onCancel={() => initialDate && setDate(new Date(initialDate))}
|
|
290
|
+
onHide={() => onHide && onHide()}
|
|
291
|
+
onShow={() => {
|
|
292
|
+
date && setInitialDate(new Date(date))
|
|
293
|
+
onShow && onShow()
|
|
294
|
+
}}
|
|
295
|
+
toggler={InputGroup()}
|
|
296
|
+
{...rest}
|
|
297
|
+
ref={ref}
|
|
298
|
+
>
|
|
299
|
+
<div
|
|
300
|
+
className={classNames('time-picker-body', {
|
|
301
|
+
['time-picker-roll']: variant === 'roll',
|
|
302
|
+
})}
|
|
303
|
+
>
|
|
304
|
+
{variant === 'select' ? <TimePickerSelect /> : TimePickerRoll()}
|
|
305
|
+
</div>
|
|
306
|
+
</CPicker>
|
|
307
|
+
)
|
|
308
|
+
},
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
CTimePicker.propTypes = {
|
|
312
|
+
...CPicker.propTypes,
|
|
313
|
+
className: PropTypes.string,
|
|
314
|
+
locale: PropTypes.string,
|
|
315
|
+
onTimeChange: PropTypes.func,
|
|
316
|
+
time: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.string]),
|
|
317
|
+
variant: PropTypes.oneOf(['roll', 'select']),
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
CTimePicker.displayName = 'CTimePicker'
|
|
@@ -0,0 +1,63 @@
|
|
|
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 init = useRef(true)
|
|
21
|
+
const colRef = useRef<HTMLDivElement>(null)
|
|
22
|
+
const forkedRef = useForkedRef(ref, colRef)
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
const nodeEl = colRef.current?.querySelector('.selected')
|
|
26
|
+
if (nodeEl && nodeEl instanceof HTMLElement) {
|
|
27
|
+
colRef.current?.scrollTo({
|
|
28
|
+
top: nodeEl.offsetTop,
|
|
29
|
+
behavior: init.current ? 'auto' : 'smooth',
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
init.current = false
|
|
34
|
+
}, [selected])
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div className="time-picker-roll-col" ref={forkedRef}>
|
|
38
|
+
{elements.map((element, index) => {
|
|
39
|
+
return (
|
|
40
|
+
<div
|
|
41
|
+
className={classNames('time-picker-roll-cell', {
|
|
42
|
+
selected: element.value === selected,
|
|
43
|
+
})}
|
|
44
|
+
key={index}
|
|
45
|
+
onClick={() => onClick && onClick(element.value)}
|
|
46
|
+
role="button"
|
|
47
|
+
>
|
|
48
|
+
{element.label}
|
|
49
|
+
</div>
|
|
50
|
+
)
|
|
51
|
+
})}
|
|
52
|
+
</div>
|
|
53
|
+
)
|
|
54
|
+
},
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
CTimePickerRollCol.propTypes = {
|
|
58
|
+
elements: PropTypes.array.isRequired,
|
|
59
|
+
onClick: PropTypes.func,
|
|
60
|
+
selected: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
CTimePickerRollCol.displayName = 'CTimePickerRollCol'
|
|
@@ -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 { CTimePicker } from '../CTimePicker'
|
|
5
|
+
|
|
6
|
+
test('loads and displays CTimePicker component', async () => {
|
|
7
|
+
const { container } = render(<CTimePicker />)
|
|
8
|
+
expect(container).toMatchSnapshot()
|
|
9
|
+
})
|