@planningcenter/tapestry-react 2.7.0 → 2.8.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/dist/cjs/Button/Button.js +10 -13
- package/dist/cjs/Button/Button.test.js +53 -21
- package/dist/cjs/Calendar/Calendar.js +30 -25
- package/dist/cjs/Combobox/ComboboxInput.js +41 -37
- package/dist/cjs/DateField/DateField.js +78 -47
- package/dist/cjs/DateField/parse.js +106 -0
- package/dist/cjs/DateField/parse.test.js +46 -0
- package/dist/cjs/DateField/useArrowKeysToNavigateCalendar.js +44 -0
- package/dist/cjs/DateField/useEditableDate.js +72 -0
- package/dist/cjs/Select/Select.test.js +74 -0
- package/dist/esm/Button/Button.js +10 -13
- package/dist/esm/Button/Button.test.js +58 -26
- package/dist/esm/Calendar/Calendar.js +30 -25
- package/dist/esm/Combobox/ComboboxInput.js +40 -37
- package/dist/esm/DateField/DateField.js +79 -48
- package/dist/esm/DateField/parse.js +93 -0
- package/dist/esm/DateField/parse.test.js +42 -0
- package/dist/esm/DateField/useArrowKeysToNavigateCalendar.js +36 -0
- package/dist/esm/DateField/useEditableDate.js +62 -0
- package/dist/esm/Select/Select.test.js +59 -0
- package/dist/types/Button/Button.d.ts +1 -1
- package/dist/types/DateField/DateField.d.ts +48 -0
- package/dist/types/DateField/parse.d.ts +17 -0
- package/dist/types/DateField/parse.test.d.ts +1 -0
- package/dist/types/DateField/useArrowKeysToNavigateCalendar.d.ts +8 -0
- package/dist/types/DateField/useEditableDate.d.ts +25 -0
- package/dist/types/Select/Select.test.d.ts +1 -0
- package/package.json +3 -3
- package/src/Button/Button.test.tsx +32 -8
- package/src/Button/Button.tsx +8 -9
- package/src/Calendar/Calendar.js +22 -17
- package/src/Combobox/ComboboxInput.js +76 -62
- package/src/DateField/DateField.mdx +15 -0
- package/src/DateField/{DateField.js → DateField.tsx} +104 -52
- package/src/DateField/parse.test.ts +76 -0
- package/src/DateField/parse.ts +92 -0
- package/src/DateField/useArrowKeysToNavigateCalendar.ts +54 -0
- package/src/DateField/useEditableDate.ts +81 -0
- package/src/Select/Select.test.tsx +58 -0
package/src/Button/Button.tsx
CHANGED
|
@@ -16,7 +16,7 @@ type ButtonProps = {
|
|
|
16
16
|
children?: any
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
19
|
+
* "Soft disables" button by adding an `aria-disabled` attribute and preventing `onClick` and `keyDown` events for "space" / "enter". This approach allows composing components (such as `Tooltip`) to still bubble up their events, while ensuring that clicking the button or submitting a form is prevented.
|
|
20
20
|
*/
|
|
21
21
|
disabled?: boolean
|
|
22
22
|
|
|
@@ -256,14 +256,13 @@ export function Button({
|
|
|
256
256
|
|
|
257
257
|
if (disabled) {
|
|
258
258
|
buttonProps.opacity = 0.65
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
buttonProps.onKeyDown = (event) => event.preventDefault()
|
|
259
|
+
buttonProps['aria-disabled'] = true
|
|
260
|
+
buttonProps.cursor = 'not-allowed'
|
|
261
|
+
buttonProps.onClick = (event) => event.preventDefault()
|
|
262
|
+
buttonProps.onKeyDown = (event) => {
|
|
263
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
264
|
+
event.preventDefault()
|
|
265
|
+
}
|
|
267
266
|
}
|
|
268
267
|
}
|
|
269
268
|
|
package/src/Calendar/Calendar.js
CHANGED
|
@@ -220,34 +220,36 @@ const Calendar = forwardRef(
|
|
|
220
220
|
>
|
|
221
221
|
<StackView axis="horizontal" spacing={0.5}>
|
|
222
222
|
<Box
|
|
223
|
-
as="button"
|
|
224
223
|
aria-label={isMonthsView ? 'Choose dates' : 'Choose month'}
|
|
225
|
-
|
|
226
|
-
fontSize={headerFontSize}
|
|
227
|
-
lineHeight={3}
|
|
228
|
-
radius={3}
|
|
229
|
-
cursor="pointer"
|
|
224
|
+
as="button"
|
|
230
225
|
backgroundColor={hover ? 'highlight' : 'transparent'}
|
|
226
|
+
cursor="pointer"
|
|
227
|
+
fontSize={headerFontSize}
|
|
231
228
|
hover={{ backgroundColor: 'highlightSecondary' }}
|
|
229
|
+
lineHeight={3}
|
|
232
230
|
onClick={() => {
|
|
233
231
|
setCurrentView(isMonthsView ? 'calendar' : 'months')
|
|
234
232
|
}}
|
|
233
|
+
padding={0.25}
|
|
234
|
+
radius={3}
|
|
235
|
+
tabIndex={-1}
|
|
235
236
|
>
|
|
236
237
|
{format(currentDate, 'MMMM')}
|
|
237
238
|
</Box>
|
|
238
239
|
<Box
|
|
239
|
-
as="button"
|
|
240
240
|
aria-label={isYearsView ? 'Choose dates' : 'Choose year'}
|
|
241
|
-
|
|
242
|
-
fontSize={headerFontSize}
|
|
243
|
-
lineHeight={3}
|
|
244
|
-
radius={3}
|
|
245
|
-
cursor="pointer"
|
|
241
|
+
as="button"
|
|
246
242
|
backgroundColor={hover ? 'highlight' : 'transparent'}
|
|
243
|
+
cursor="pointer"
|
|
244
|
+
fontSize={headerFontSize}
|
|
247
245
|
hover={{ backgroundColor: 'highlightSecondary' }}
|
|
246
|
+
lineHeight={3}
|
|
248
247
|
onClick={() => {
|
|
249
248
|
setCurrentView(isYearsView ? 'calendar' : 'years')
|
|
250
249
|
}}
|
|
250
|
+
padding={0.25}
|
|
251
|
+
radius={3}
|
|
252
|
+
tabIndex={-1}
|
|
251
253
|
>
|
|
252
254
|
{currentYear}
|
|
253
255
|
</Box>
|
|
@@ -260,29 +262,32 @@ const Calendar = forwardRef(
|
|
|
260
262
|
}}
|
|
261
263
|
>
|
|
262
264
|
<Button
|
|
263
|
-
title="Previous month"
|
|
264
|
-
icon={{ name: 'general.leftChevron', size: "xxs" }}
|
|
265
265
|
disabled={
|
|
266
266
|
isYearsView
|
|
267
267
|
? currentYear <= minYear
|
|
268
268
|
: isSameMonth(currentDate, minDate)
|
|
269
269
|
}
|
|
270
|
+
icon={{ name: 'general.leftChevron', size: 'xxs' }}
|
|
270
271
|
onClick={() => navigateCalendar(-1)}
|
|
272
|
+
tabIndex={-1}
|
|
273
|
+
title="Previous month"
|
|
271
274
|
/>
|
|
272
275
|
<Button
|
|
273
|
-
tooltip={{ title: 'Today' }}
|
|
274
276
|
icon={{ name: 'tapestry.radio1' }}
|
|
275
277
|
onClick={() => setDate(TODAY, true)}
|
|
278
|
+
tabIndex={-1}
|
|
279
|
+
tooltip={{ title: 'Today' }}
|
|
276
280
|
/>
|
|
277
281
|
<Button
|
|
278
|
-
title="Next month"
|
|
279
|
-
icon={{ name: 'general.rightChevron', size: "xxs"}}
|
|
280
282
|
disabled={
|
|
281
283
|
isYearsView
|
|
282
284
|
? currentYear >= maxYear
|
|
283
285
|
: isSameMonth(currentDate, maxDate)
|
|
284
286
|
}
|
|
287
|
+
icon={{ name: 'general.rightChevron', size: 'xxs' }}
|
|
285
288
|
onClick={() => navigateCalendar(1)}
|
|
289
|
+
tabIndex={-1}
|
|
290
|
+
title="Next month"
|
|
286
291
|
/>
|
|
287
292
|
</Group>
|
|
288
293
|
</StackView>
|
|
@@ -10,6 +10,7 @@ import React, {
|
|
|
10
10
|
|
|
11
11
|
import Input from '../Input'
|
|
12
12
|
import ItemListContext from '../ItemList/ItemListContext'
|
|
13
|
+
import { ItemListController } from '../ItemList'
|
|
13
14
|
import { useThemeProps } from '../system'
|
|
14
15
|
|
|
15
16
|
type Props = {
|
|
@@ -64,77 +65,90 @@ function ComboboxInput(props: Props, ref) {
|
|
|
64
65
|
}))
|
|
65
66
|
|
|
66
67
|
const itemList = useContext(ItemListContext)
|
|
67
|
-
const { highlightedItemId, id } = itemList
|
|
68
68
|
|
|
69
69
|
useEffect(() => {
|
|
70
70
|
setTimeout(() => {
|
|
71
71
|
itemList.setHighlightedIndex(0)
|
|
72
72
|
})
|
|
73
|
-
}, [itemList
|
|
73
|
+
}, [itemList])
|
|
74
74
|
|
|
75
|
-
const handleKeyDown = useCallback(
|
|
76
|
-
|
|
77
|
-
e.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
e.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
e.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
onKeyDown
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
onClose
|
|
93
|
-
|
|
94
|
-
|
|
75
|
+
const handleKeyDown = useCallback(
|
|
76
|
+
(e) => {
|
|
77
|
+
if (e.key === 'ArrowUp') {
|
|
78
|
+
e.preventDefault()
|
|
79
|
+
itemList.moveHighlightedIndex(-1, { contain: false })
|
|
80
|
+
}
|
|
81
|
+
if (e.key === 'ArrowDown') {
|
|
82
|
+
e.preventDefault()
|
|
83
|
+
itemList.moveHighlightedIndex(1, { contain: false })
|
|
84
|
+
}
|
|
85
|
+
if (e.key === 'Enter') {
|
|
86
|
+
e.preventDefault()
|
|
87
|
+
itemList.selectHighlightedItem()
|
|
88
|
+
}
|
|
89
|
+
if (onKeyDown) {
|
|
90
|
+
onKeyDown(e)
|
|
91
|
+
}
|
|
92
|
+
if (onClose && e.key === 'Tab') {
|
|
93
|
+
onClose()
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
[itemList, onClose, onKeyDown]
|
|
97
|
+
)
|
|
95
98
|
|
|
96
|
-
const handleDocumentKeyDown = useCallback(
|
|
97
|
-
|
|
98
|
-
onClear
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
const handleDocumentKeyDown = useCallback(
|
|
100
|
+
(e) => {
|
|
101
|
+
if (onClear && e.key === 'Escape') {
|
|
102
|
+
onClear()
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
[onClear]
|
|
106
|
+
)
|
|
101
107
|
|
|
102
108
|
return (
|
|
103
|
-
<
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
109
|
+
<ItemListController>
|
|
110
|
+
{(itemList) => {
|
|
111
|
+
const { highlightedItemId, id } = itemList
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<Input.InputBox
|
|
115
|
+
innerRef={innerRef}
|
|
116
|
+
size={size}
|
|
117
|
+
disabled={disabled}
|
|
118
|
+
isLoading={isLoading}
|
|
119
|
+
onClear={onClear}
|
|
120
|
+
{...restProps}
|
|
121
|
+
grow={1}
|
|
122
|
+
>
|
|
123
|
+
<Input.InputField
|
|
124
|
+
innerRef={(node) => {
|
|
125
|
+
inputNode.current = node
|
|
126
|
+
}}
|
|
127
|
+
grow={1}
|
|
128
|
+
role="combobox"
|
|
129
|
+
autoComplete="off"
|
|
130
|
+
aria-autocomplete="list"
|
|
131
|
+
aria-controls={id}
|
|
132
|
+
aria-expanded={isOpen}
|
|
133
|
+
aria-activedescendant={highlightedItemId}
|
|
134
|
+
autoFocus={autoFocus}
|
|
135
|
+
type={type}
|
|
136
|
+
name={name}
|
|
137
|
+
disabled={disabled}
|
|
138
|
+
readOnly={readOnly}
|
|
139
|
+
placeholder={placeholder}
|
|
140
|
+
defaultValue={defaultValue}
|
|
141
|
+
value={value}
|
|
142
|
+
onChange={onChange}
|
|
143
|
+
onFocus={onFocus}
|
|
144
|
+
onBlur={onBlur}
|
|
145
|
+
onKeyDown={handleKeyDown}
|
|
146
|
+
onKeyUp={onKeyUp}
|
|
147
|
+
/>
|
|
148
|
+
</Input.InputBox>
|
|
149
|
+
)
|
|
150
|
+
}}
|
|
151
|
+
</ItemListController>
|
|
138
152
|
)
|
|
139
153
|
}
|
|
140
154
|
ComboboxInput = forwardRef(ComboboxInput)
|
|
@@ -17,3 +17,18 @@ render(() => {
|
|
|
17
17
|
)
|
|
18
18
|
})
|
|
19
19
|
```
|
|
20
|
+
|
|
21
|
+
### Example with day/month/year format
|
|
22
|
+
|
|
23
|
+
```jsx live
|
|
24
|
+
render(() => {
|
|
25
|
+
const [date, setDate] = React.useState(Calendar.TODAY)
|
|
26
|
+
return (
|
|
27
|
+
<DateField
|
|
28
|
+
value={date}
|
|
29
|
+
formatValue="dd/MM/yyyy"
|
|
30
|
+
onChange={(date) => setDate(date)}
|
|
31
|
+
/>
|
|
32
|
+
)
|
|
33
|
+
})
|
|
34
|
+
```
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// @flow
|
|
2
|
-
import React, { useState, useCallback, useRef } from 'react'
|
|
3
|
-
import { format } from 'date-fns'
|
|
2
|
+
import React, { useState, useCallback, useMemo, useRef } from 'react'
|
|
4
3
|
|
|
5
4
|
import Card from '../Card'
|
|
6
5
|
import Calendar from '../Calendar'
|
|
@@ -10,62 +9,64 @@ import Input from '../Input/Input'
|
|
|
10
9
|
import Popover from '../Popover'
|
|
11
10
|
import { generateId } from '../utils'
|
|
12
11
|
import { useThemeProps } from '../system'
|
|
12
|
+
import { useArrowKeysToNavigateCalendar } from './useArrowKeysToNavigateCalendar'
|
|
13
|
+
import { useEditableDate } from './useEditableDate'
|
|
13
14
|
|
|
14
15
|
export type DateFieldProps = {
|
|
15
16
|
/**
|
|
16
17
|
* Format the displayed date using date-fns [format](https://date-fns.org/v2.0.0-alpha.9/docs/format) function.
|
|
17
18
|
*/
|
|
18
|
-
formatValue: string
|
|
19
|
+
formatValue: string
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Controls the initial Popover state: open or closed (default).
|
|
22
23
|
*/
|
|
23
|
-
defaultOpen: boolean
|
|
24
|
+
defaultOpen: boolean
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* The minimum date that can be chosen.
|
|
27
28
|
*/
|
|
28
|
-
minDate: Date
|
|
29
|
+
minDate: Date
|
|
29
30
|
|
|
30
31
|
/**
|
|
31
32
|
* The maximum date that can be chosen.
|
|
32
33
|
*/
|
|
33
|
-
maxDate: Date
|
|
34
|
+
maxDate: Date
|
|
34
35
|
|
|
35
36
|
/**
|
|
36
37
|
* Called when a date has been selected.
|
|
37
38
|
*/
|
|
38
|
-
onChange: (date: Date) => null
|
|
39
|
+
onChange: (date: Date) => null
|
|
39
40
|
|
|
40
41
|
/**
|
|
41
42
|
* Determines where the popover is placed.
|
|
42
43
|
*/
|
|
43
|
-
placement: string
|
|
44
|
+
placement: string
|
|
44
45
|
|
|
45
46
|
/**
|
|
46
47
|
* The date that will be selected.
|
|
47
48
|
*/
|
|
48
|
-
value: Date
|
|
49
|
+
value: Date
|
|
49
50
|
|
|
50
51
|
/**
|
|
51
52
|
* Locks external scrollbars when open.
|
|
52
53
|
*/
|
|
53
|
-
lockScrollWhileOpen?: boolean
|
|
54
|
+
lockScrollWhileOpen?: boolean
|
|
54
55
|
|
|
55
56
|
/**
|
|
56
57
|
* Attempts to keep popover in view clipping edges if too large.
|
|
57
58
|
*/
|
|
58
|
-
keepInView?: boolean
|
|
59
|
+
keepInView?: boolean
|
|
59
60
|
|
|
60
61
|
/**
|
|
61
62
|
* Accepts any valid [Calendar](/calendar) props.
|
|
62
63
|
*/
|
|
63
|
-
calendarProps?: object
|
|
64
|
+
calendarProps?: object
|
|
64
65
|
|
|
65
66
|
/**
|
|
66
67
|
* Accepts any valid [Popover](/popover) props.
|
|
67
68
|
*/
|
|
68
|
-
popoverProps?: object
|
|
69
|
+
popoverProps?: object
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
function DateField({
|
|
@@ -89,81 +90,131 @@ function DateField({
|
|
|
89
90
|
trackColor,
|
|
90
91
|
...restProps
|
|
91
92
|
} = useThemeProps('dateField', props)
|
|
92
|
-
let canClosePopover = true
|
|
93
93
|
const [isPopoverOpen, setIsPopoverOpen] = useState(defaultOpen)
|
|
94
94
|
const id = generateId('datefield')
|
|
95
95
|
const popover = useRef(null)
|
|
96
96
|
const inputWrapper = useRef(null)
|
|
97
97
|
|
|
98
|
+
const focusInput = useCallback(() => {
|
|
99
|
+
const input = inputWrapper.current.querySelector('input')
|
|
100
|
+
if (input.focus) {
|
|
101
|
+
input.focus()
|
|
102
|
+
}
|
|
103
|
+
}, [])
|
|
104
|
+
|
|
98
105
|
const openPopover = useCallback(() => {
|
|
99
106
|
setIsPopoverOpen(true)
|
|
100
|
-
})
|
|
107
|
+
}, [])
|
|
101
108
|
|
|
102
109
|
const closePopover = useCallback(() => {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
110
|
+
setIsPopoverOpen(false)
|
|
111
|
+
}, [])
|
|
112
|
+
|
|
113
|
+
const dateValidator = useCallback(
|
|
114
|
+
(date: Date) => {
|
|
115
|
+
if (!date) return false
|
|
116
|
+
if (minDate && date < minDate) return false
|
|
117
|
+
if (maxDate && date > maxDate) return false
|
|
118
|
+
return true
|
|
119
|
+
},
|
|
120
|
+
[minDate, maxDate]
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
const {
|
|
124
|
+
formattedDate,
|
|
125
|
+
setDate,
|
|
126
|
+
clearKeyBuffer,
|
|
127
|
+
invalidKeyBuffer,
|
|
128
|
+
} = useEditableDate({
|
|
129
|
+
date: value,
|
|
130
|
+
dateFormat: formatValue,
|
|
131
|
+
dateValidator,
|
|
132
|
+
onChange,
|
|
106
133
|
})
|
|
107
134
|
|
|
108
|
-
const
|
|
109
|
-
|
|
135
|
+
const handleDateSelectedFromCalendar = useCallback(
|
|
136
|
+
(date) => {
|
|
137
|
+
focusInput()
|
|
138
|
+
setDate(date)
|
|
110
139
|
closePopover()
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
|
|
140
|
+
},
|
|
141
|
+
[focusInput, setDate, closePopover]
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
const handleNavigationChangeFromCalendar = useCallback(
|
|
145
|
+
(date) => {
|
|
146
|
+
setDate(date)
|
|
147
|
+
},
|
|
148
|
+
[setDate]
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
const navigateCalendarWithArrowKeys = useArrowKeysToNavigateCalendar({
|
|
152
|
+
date: value,
|
|
153
|
+
calendarIsOpen: isPopoverOpen,
|
|
154
|
+
openCalendar: openPopover,
|
|
155
|
+
onChange: setDate,
|
|
114
156
|
})
|
|
115
157
|
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
158
|
+
const handleInputOnChange = useCallback(
|
|
159
|
+
(event: React.ChangeEvent<HTMLInputElement>) => {
|
|
160
|
+
setDate(event.currentTarget.value)
|
|
161
|
+
},
|
|
162
|
+
[setDate]
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
const handleOnBlur = useCallback(() => {
|
|
166
|
+
clearKeyBuffer()
|
|
124
167
|
closePopover()
|
|
125
|
-
})
|
|
168
|
+
}, [clearKeyBuffer, closePopover])
|
|
169
|
+
|
|
170
|
+
const inputColors = useMemo(() => {
|
|
171
|
+
if (invalidKeyBuffer) {
|
|
172
|
+
return { color: 'error-darker', backgroundColor: 'error-lighter' }
|
|
173
|
+
} else {
|
|
174
|
+
return {}
|
|
175
|
+
}
|
|
176
|
+
}, [invalidKeyBuffer])
|
|
126
177
|
|
|
127
178
|
return (
|
|
128
|
-
<FocusGroup onBlur={
|
|
179
|
+
<FocusGroup onBlur={handleOnBlur}>
|
|
129
180
|
{({ requestBlur, setRef }) => (
|
|
130
181
|
<Popover
|
|
131
182
|
{...popoverProps}
|
|
132
183
|
ref={(component) => {
|
|
133
184
|
popover.current = component
|
|
134
185
|
}}
|
|
186
|
+
as={Card}
|
|
187
|
+
elevation={2}
|
|
135
188
|
innerRef={(node) => {
|
|
136
189
|
popover.current = node
|
|
137
190
|
setRef(`${id}-popover`)(node)
|
|
138
191
|
}}
|
|
139
|
-
as={Card}
|
|
140
|
-
tabIndex={-1}
|
|
141
|
-
elevation={2}
|
|
142
|
-
onBlur={requestBlur}
|
|
143
192
|
keepInView={keepInView}
|
|
144
193
|
lockScrollWhileOpen={lockScrollWhileOpen}
|
|
145
|
-
|
|
146
|
-
open={isPopoverOpen}
|
|
194
|
+
onBlur={requestBlur}
|
|
147
195
|
onRequestClose={closePopover}
|
|
196
|
+
open={isPopoverOpen}
|
|
197
|
+
placement={placement}
|
|
198
|
+
tabIndex={-1}
|
|
148
199
|
anchorElement={
|
|
149
200
|
<Input
|
|
150
201
|
innerRef={(node) => {
|
|
151
202
|
inputWrapper.current = node
|
|
152
203
|
setRef(`${id}-input`)(node)
|
|
153
204
|
}}
|
|
154
|
-
|
|
155
|
-
|
|
205
|
+
onBlur={requestBlur}
|
|
206
|
+
onFocus={openPopover}
|
|
207
|
+
onChange={handleInputOnChange}
|
|
208
|
+
onKeyDown={navigateCalendarWithArrowKeys}
|
|
209
|
+
value={formattedDate}
|
|
156
210
|
renderRight={
|
|
157
|
-
<Icon
|
|
211
|
+
<Icon
|
|
212
|
+
name="general.calendar"
|
|
213
|
+
color="foregroundTertiary"
|
|
214
|
+
onClick={openPopover}
|
|
215
|
+
/>
|
|
158
216
|
}
|
|
159
|
-
|
|
160
|
-
onBlur={requestBlur}
|
|
161
|
-
onKeyDown={(event) => {
|
|
162
|
-
if (event.key === ' ') {
|
|
163
|
-
event.preventDefault()
|
|
164
|
-
togglePopover()
|
|
165
|
-
}
|
|
166
|
-
}}
|
|
217
|
+
{...inputColors}
|
|
167
218
|
{...restProps}
|
|
168
219
|
/>
|
|
169
220
|
}
|
|
@@ -171,11 +222,12 @@ function DateField({
|
|
|
171
222
|
<Calendar
|
|
172
223
|
size="sm"
|
|
173
224
|
{...calendarProps}
|
|
174
|
-
|
|
225
|
+
date={value}
|
|
175
226
|
selected={value}
|
|
176
227
|
minDate={minDate}
|
|
177
228
|
maxDate={maxDate}
|
|
178
|
-
onDateSelect={
|
|
229
|
+
onDateSelect={handleDateSelectedFromCalendar}
|
|
230
|
+
onDateChange={handleNavigationChangeFromCalendar}
|
|
179
231
|
/>
|
|
180
232
|
</Popover>
|
|
181
233
|
)}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { format } from 'date-fns'
|
|
2
|
+
import { parseDate, isValidDate, parseMonth } from './parse'
|
|
3
|
+
|
|
4
|
+
describe('isValidDate', () => {
|
|
5
|
+
const validDates = [
|
|
6
|
+
'jun 6, 2022',
|
|
7
|
+
'June-06-2022',
|
|
8
|
+
'6-6-2022',
|
|
9
|
+
'6/6/2022',
|
|
10
|
+
'06-06-2022',
|
|
11
|
+
'2022-06-26', // year, month, day
|
|
12
|
+
'2022/6/1', // year, month, day
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
validDates.forEach((date) => {
|
|
16
|
+
it(`returns true for "${date}"`, () => {
|
|
17
|
+
expect(isValidDate(date)).toBe(true)
|
|
18
|
+
})
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const invalidDates = [
|
|
22
|
+
'June-06-22',
|
|
23
|
+
'13-6-2022',
|
|
24
|
+
'June 6 2022',
|
|
25
|
+
'2022/6/1/1',
|
|
26
|
+
'2022-15-06',
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
invalidDates.forEach((date) => {
|
|
30
|
+
it(`returns false for "${date}"`, () => {
|
|
31
|
+
expect(isValidDate(date)).toBe(false)
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
describe('parseDate', () => {
|
|
37
|
+
const dates = [
|
|
38
|
+
['January 31, 2022', 'January 31, 2022'],
|
|
39
|
+
['jun 9, 2022', 'June 09, 2022'],
|
|
40
|
+
['June-09-2022', 'June 09, 2022'],
|
|
41
|
+
['6-9-2022', 'June 09, 2022'],
|
|
42
|
+
['6/9/2022', 'June 09, 2022'],
|
|
43
|
+
['06-09-2022', 'June 09, 2022'],
|
|
44
|
+
['2022-06-26', 'June 26, 2022'],
|
|
45
|
+
['2022/6/1', 'June 01, 2022', 'MMMM dd, yyyy'],
|
|
46
|
+
['15/6/2022', 'June 15, 2022', 'dd/MM/YYYY'],
|
|
47
|
+
['1/6/2022', 'June 01, 2022', 'dd MMMM, yyyy'],
|
|
48
|
+
['15-aug-2023', 'August 15, 2023', 'dd MMMM, yyyy'],
|
|
49
|
+
['15 August, 2023', 'August 15, 2023', 'dd MMMM, yyyy'],
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
dates.forEach(([date, expected, dateFormat]) => {
|
|
53
|
+
it(`returns ${expected} for "${date}"`, () => {
|
|
54
|
+
expect(
|
|
55
|
+
format(parseDate({ date, format: dateFormat }), 'MMMM dd, yyyy')
|
|
56
|
+
).toEqual(expected)
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
describe('parseMonth', () => {
|
|
62
|
+
const months: [string, number][] = [
|
|
63
|
+
['jan', 1],
|
|
64
|
+
['feb', 2],
|
|
65
|
+
['December', 12],
|
|
66
|
+
['2', 2],
|
|
67
|
+
['12', 12],
|
|
68
|
+
['05', 5],
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
months.forEach(([month, expected]) => {
|
|
72
|
+
it(`returns ${expected} for "${month}"`, () => {
|
|
73
|
+
expect(parseMonth(month)).toBe(expected)
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
})
|