@planningcenter/tapestry-react 2.7.0 → 2.8.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/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 +74 -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 +75 -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} +96 -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,124 @@ 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 navigateCalendarWithArrowKeys = useArrowKeysToNavigateCalendar({
|
|
145
|
+
date: value,
|
|
146
|
+
calendarIsOpen: isPopoverOpen,
|
|
147
|
+
openCalendar: openPopover,
|
|
148
|
+
onChange: setDate,
|
|
114
149
|
})
|
|
115
150
|
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
151
|
+
const handleInputOnChange = useCallback(
|
|
152
|
+
(event: React.ChangeEvent<HTMLInputElement>) => {
|
|
153
|
+
setDate(event.currentTarget.value)
|
|
154
|
+
},
|
|
155
|
+
[setDate]
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
const handleOnBlur = useCallback(() => {
|
|
159
|
+
clearKeyBuffer()
|
|
124
160
|
closePopover()
|
|
125
|
-
})
|
|
161
|
+
}, [clearKeyBuffer, closePopover])
|
|
162
|
+
|
|
163
|
+
const inputColors = useMemo(() => {
|
|
164
|
+
if (invalidKeyBuffer) {
|
|
165
|
+
return { color: 'error-darker', backgroundColor: 'error-lighter' }
|
|
166
|
+
} else {
|
|
167
|
+
return {}
|
|
168
|
+
}
|
|
169
|
+
}, [invalidKeyBuffer])
|
|
126
170
|
|
|
127
171
|
return (
|
|
128
|
-
<FocusGroup onBlur={
|
|
172
|
+
<FocusGroup onBlur={handleOnBlur}>
|
|
129
173
|
{({ requestBlur, setRef }) => (
|
|
130
174
|
<Popover
|
|
131
175
|
{...popoverProps}
|
|
132
176
|
ref={(component) => {
|
|
133
177
|
popover.current = component
|
|
134
178
|
}}
|
|
179
|
+
as={Card}
|
|
180
|
+
elevation={2}
|
|
135
181
|
innerRef={(node) => {
|
|
136
182
|
popover.current = node
|
|
137
183
|
setRef(`${id}-popover`)(node)
|
|
138
184
|
}}
|
|
139
|
-
as={Card}
|
|
140
|
-
tabIndex={-1}
|
|
141
|
-
elevation={2}
|
|
142
|
-
onBlur={requestBlur}
|
|
143
185
|
keepInView={keepInView}
|
|
144
186
|
lockScrollWhileOpen={lockScrollWhileOpen}
|
|
145
|
-
|
|
146
|
-
open={isPopoverOpen}
|
|
187
|
+
onBlur={requestBlur}
|
|
147
188
|
onRequestClose={closePopover}
|
|
189
|
+
open={isPopoverOpen}
|
|
190
|
+
placement={placement}
|
|
191
|
+
tabIndex={-1}
|
|
148
192
|
anchorElement={
|
|
149
193
|
<Input
|
|
150
194
|
innerRef={(node) => {
|
|
151
195
|
inputWrapper.current = node
|
|
152
196
|
setRef(`${id}-input`)(node)
|
|
153
197
|
}}
|
|
154
|
-
|
|
155
|
-
|
|
198
|
+
onBlur={requestBlur}
|
|
199
|
+
onFocus={openPopover}
|
|
200
|
+
onChange={handleInputOnChange}
|
|
201
|
+
onKeyDown={navigateCalendarWithArrowKeys}
|
|
202
|
+
value={formattedDate}
|
|
156
203
|
renderRight={
|
|
157
|
-
<Icon
|
|
204
|
+
<Icon
|
|
205
|
+
name="general.calendar"
|
|
206
|
+
color="foregroundTertiary"
|
|
207
|
+
onClick={openPopover}
|
|
208
|
+
/>
|
|
158
209
|
}
|
|
159
|
-
|
|
160
|
-
onBlur={requestBlur}
|
|
161
|
-
onKeyDown={(event) => {
|
|
162
|
-
if (event.key === ' ') {
|
|
163
|
-
event.preventDefault()
|
|
164
|
-
togglePopover()
|
|
165
|
-
}
|
|
166
|
-
}}
|
|
210
|
+
{...inputColors}
|
|
167
211
|
{...restProps}
|
|
168
212
|
/>
|
|
169
213
|
}
|
|
@@ -171,11 +215,11 @@ function DateField({
|
|
|
171
215
|
<Calendar
|
|
172
216
|
size="sm"
|
|
173
217
|
{...calendarProps}
|
|
174
|
-
|
|
218
|
+
date={value}
|
|
175
219
|
selected={value}
|
|
176
220
|
minDate={minDate}
|
|
177
221
|
maxDate={maxDate}
|
|
178
|
-
onDateSelect={
|
|
222
|
+
onDateSelect={handleDateSelectedFromCalendar}
|
|
179
223
|
/>
|
|
180
224
|
</Popover>
|
|
181
225
|
)}
|
|
@@ -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
|
+
})
|