@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
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import _extends from "@babel/runtime/helpers/esm/extends";
|
|
2
2
|
import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
|
|
3
|
-
import React, { useState, useCallback, useRef } from 'react';
|
|
4
|
-
import { format } from 'date-fns';
|
|
3
|
+
import React, { useState, useCallback, useMemo, useRef } from 'react';
|
|
5
4
|
import Card from '../Card';
|
|
6
5
|
import Calendar from '../Calendar';
|
|
7
6
|
import FocusGroup from '../FocusGroup';
|
|
@@ -10,11 +9,8 @@ import Input from '../Input/Input';
|
|
|
10
9
|
import Popover from '../Popover';
|
|
11
10
|
import { generateId } from '../utils';
|
|
12
11
|
import { useThemeProps } from '../system';
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
name: "general.calendar",
|
|
16
|
-
color: "foregroundTertiary"
|
|
17
|
-
});
|
|
12
|
+
import { useArrowKeysToNavigateCalendar } from './useArrowKeysToNavigateCalendar';
|
|
13
|
+
import { useEditableDate } from './useEditableDate';
|
|
18
14
|
|
|
19
15
|
function DateField(_ref) {
|
|
20
16
|
var _ref$formatValue = _ref.formatValue,
|
|
@@ -40,8 +36,6 @@ function DateField(_ref) {
|
|
|
40
36
|
trackColor = _useThemeProps.trackColor,
|
|
41
37
|
restProps = _objectWithoutPropertiesLoose(_useThemeProps, ["calendarProps", "color", "popoverProps", "sizes", "thickness", "trackColor"]);
|
|
42
38
|
|
|
43
|
-
var canClosePopover = true;
|
|
44
|
-
|
|
45
39
|
var _useState = useState(defaultOpen),
|
|
46
40
|
isPopoverOpen = _useState[0],
|
|
47
41
|
setIsPopoverOpen = _useState[1];
|
|
@@ -49,36 +43,74 @@ function DateField(_ref) {
|
|
|
49
43
|
var id = generateId('datefield');
|
|
50
44
|
var popover = useRef(null);
|
|
51
45
|
var inputWrapper = useRef(null);
|
|
52
|
-
var
|
|
53
|
-
setIsPopoverOpen(true);
|
|
54
|
-
});
|
|
55
|
-
var closePopover = useCallback(function () {
|
|
56
|
-
if (canClosePopover) {
|
|
57
|
-
setIsPopoverOpen(false);
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
var togglePopover = useCallback(function () {
|
|
61
|
-
if (isPopoverOpen) {
|
|
62
|
-
closePopover();
|
|
63
|
-
} else {
|
|
64
|
-
openPopover();
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
var handleDateSelect = useCallback(function (date) {
|
|
46
|
+
var focusInput = useCallback(function () {
|
|
68
47
|
var input = inputWrapper.current.querySelector('input');
|
|
69
48
|
|
|
70
49
|
if (input.focus) {
|
|
71
50
|
input.focus();
|
|
72
51
|
}
|
|
52
|
+
}, []);
|
|
53
|
+
var openPopover = useCallback(function () {
|
|
54
|
+
setIsPopoverOpen(true);
|
|
55
|
+
}, []);
|
|
56
|
+
var closePopover = useCallback(function () {
|
|
57
|
+
setIsPopoverOpen(false);
|
|
58
|
+
}, []);
|
|
59
|
+
var dateValidator = useCallback(function (date) {
|
|
60
|
+
if (!date) return false;
|
|
61
|
+
if (minDate && date < minDate) return false;
|
|
62
|
+
if (maxDate && date > maxDate) return false;
|
|
63
|
+
return true;
|
|
64
|
+
}, [minDate, maxDate]);
|
|
73
65
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
66
|
+
var _useEditableDate = useEditableDate({
|
|
67
|
+
date: value,
|
|
68
|
+
dateFormat: formatValue,
|
|
69
|
+
dateValidator: dateValidator,
|
|
70
|
+
onChange: onChange
|
|
71
|
+
}),
|
|
72
|
+
formattedDate = _useEditableDate.formattedDate,
|
|
73
|
+
setDate = _useEditableDate.setDate,
|
|
74
|
+
clearKeyBuffer = _useEditableDate.clearKeyBuffer,
|
|
75
|
+
invalidKeyBuffer = _useEditableDate.invalidKeyBuffer;
|
|
77
76
|
|
|
77
|
+
var handleDateSelectedFromCalendar = useCallback(function (date) {
|
|
78
|
+
focusInput();
|
|
79
|
+
setDate(date);
|
|
78
80
|
closePopover();
|
|
81
|
+
}, [focusInput, setDate, closePopover]);
|
|
82
|
+
var navigateCalendarWithArrowKeys = useArrowKeysToNavigateCalendar({
|
|
83
|
+
date: value,
|
|
84
|
+
calendarIsOpen: isPopoverOpen,
|
|
85
|
+
openCalendar: openPopover,
|
|
86
|
+
onChange: setDate
|
|
79
87
|
});
|
|
88
|
+
var handleInputOnChange = useCallback(function (event) {
|
|
89
|
+
setDate(event.currentTarget.value);
|
|
90
|
+
}, [setDate]);
|
|
91
|
+
var handleOnBlur = useCallback(function () {
|
|
92
|
+
clearKeyBuffer();
|
|
93
|
+
closePopover();
|
|
94
|
+
}, [clearKeyBuffer, closePopover]);
|
|
95
|
+
var inputColors = useMemo(function () {
|
|
96
|
+
if (invalidKeyBuffer) {
|
|
97
|
+
return {
|
|
98
|
+
color: 'error-darker',
|
|
99
|
+
backgroundColor: 'error-lighter'
|
|
100
|
+
};
|
|
101
|
+
} else {
|
|
102
|
+
return {};
|
|
103
|
+
}
|
|
104
|
+
}, [invalidKeyBuffer]);
|
|
105
|
+
|
|
106
|
+
var _ref3 = /*#__PURE__*/React.createElement(Icon, {
|
|
107
|
+
name: "general.calendar",
|
|
108
|
+
color: "foregroundTertiary",
|
|
109
|
+
onClick: openPopover
|
|
110
|
+
});
|
|
111
|
+
|
|
80
112
|
return /*#__PURE__*/React.createElement(FocusGroup, {
|
|
81
|
-
onBlur:
|
|
113
|
+
onBlur: handleOnBlur
|
|
82
114
|
}, function (_ref2) {
|
|
83
115
|
var requestBlur = _ref2.requestBlur,
|
|
84
116
|
setRef = _ref2.setRef;
|
|
@@ -86,44 +118,39 @@ function DateField(_ref) {
|
|
|
86
118
|
ref: function ref(component) {
|
|
87
119
|
popover.current = component;
|
|
88
120
|
},
|
|
121
|
+
as: Card,
|
|
122
|
+
elevation: 2,
|
|
89
123
|
innerRef: function innerRef(node) {
|
|
90
124
|
popover.current = node;
|
|
91
125
|
setRef(id + "-popover")(node);
|
|
92
126
|
},
|
|
93
|
-
as: Card,
|
|
94
|
-
tabIndex: -1,
|
|
95
|
-
elevation: 2,
|
|
96
|
-
onBlur: requestBlur,
|
|
97
127
|
keepInView: keepInView,
|
|
98
128
|
lockScrollWhileOpen: lockScrollWhileOpen,
|
|
99
|
-
|
|
100
|
-
open: isPopoverOpen,
|
|
129
|
+
onBlur: requestBlur,
|
|
101
130
|
onRequestClose: closePopover,
|
|
131
|
+
open: isPopoverOpen,
|
|
132
|
+
placement: placement,
|
|
133
|
+
tabIndex: -1,
|
|
102
134
|
anchorElement: /*#__PURE__*/React.createElement(Input, _extends({
|
|
103
135
|
innerRef: function innerRef(node) {
|
|
104
136
|
inputWrapper.current = node;
|
|
105
137
|
setRef(id + "-input")(node);
|
|
106
138
|
},
|
|
107
|
-
readOnly: true,
|
|
108
|
-
value: value ? format(value, formatValue) : '',
|
|
109
|
-
renderRight: _ref3,
|
|
110
|
-
onClick: togglePopover,
|
|
111
139
|
onBlur: requestBlur,
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}, restProps))
|
|
140
|
+
onFocus: openPopover,
|
|
141
|
+
onChange: handleInputOnChange,
|
|
142
|
+
onKeyDown: navigateCalendarWithArrowKeys,
|
|
143
|
+
value: formattedDate,
|
|
144
|
+
renderRight: _ref3
|
|
145
|
+
}, inputColors, restProps))
|
|
119
146
|
}), /*#__PURE__*/React.createElement(Calendar, _extends({
|
|
120
147
|
size: "sm"
|
|
121
148
|
}, calendarProps, {
|
|
122
|
-
|
|
149
|
+
date: value,
|
|
123
150
|
selected: value,
|
|
124
151
|
minDate: minDate,
|
|
125
152
|
maxDate: maxDate,
|
|
126
|
-
onDateSelect:
|
|
153
|
+
onDateSelect: handleDateSelectedFromCalendar
|
|
127
154
|
})));
|
|
128
155
|
});
|
|
129
156
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
var monthDayYearFormat = /^(0?[1-9]|1[0-2]|(?:(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*))(?:-|\/|\s)(0?[1-9]|[1-2][0-9]|3[0-1])(?:-|\/|,\s)(\d{4})$/i;
|
|
2
|
+
var dayMonthYearFormat = /^(0?[1-9]|[1-2][0-9]|3[0-1])(?:-|\/|\s)(0?[1-9]|1[0-2]|(?:(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*))(?:-|\/|,\s)(\d{4})$/i;
|
|
3
|
+
var yearMonthDayFormat = /^(\d{4})[-/](0?[1-9]|1[0-2])[-/](0?[1-9]|[1-2][0-9]|3[0-1])$/;
|
|
4
|
+
export var parseDate = function parseDate(_ref) {
|
|
5
|
+
var date = _ref.date,
|
|
6
|
+
format = _ref.format;
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
var _parseDateIntoObject = parseDateIntoObject(date, format),
|
|
10
|
+
year = _parseDateIntoObject.year,
|
|
11
|
+
month = _parseDateIntoObject.month,
|
|
12
|
+
day = _parseDateIntoObject.day;
|
|
13
|
+
|
|
14
|
+
return new Date(year, month - 1, day);
|
|
15
|
+
} catch (e) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
export var isValidDate = function isValidDate(date) {
|
|
20
|
+
return monthDayYearFormat.test(date) || yearMonthDayFormat.test(date);
|
|
21
|
+
};
|
|
22
|
+
export var parseMonth = function parseMonth(monthString) {
|
|
23
|
+
var months = {
|
|
24
|
+
jan: 1,
|
|
25
|
+
feb: 2,
|
|
26
|
+
mar: 3,
|
|
27
|
+
apr: 4,
|
|
28
|
+
may: 5,
|
|
29
|
+
jun: 6,
|
|
30
|
+
jul: 7,
|
|
31
|
+
aug: 8,
|
|
32
|
+
sep: 9,
|
|
33
|
+
oct: 10,
|
|
34
|
+
nov: 11,
|
|
35
|
+
dec: 12
|
|
36
|
+
};
|
|
37
|
+
var normalizedString = monthString.toLowerCase().replace(/^0/, '').slice(0, 3);
|
|
38
|
+
|
|
39
|
+
if (/^\d+$/.test(normalizedString)) {
|
|
40
|
+
var month = parseInt(normalizedString, 10);
|
|
41
|
+
|
|
42
|
+
if (month >= 1 && month <= 12) {
|
|
43
|
+
return month;
|
|
44
|
+
}
|
|
45
|
+
} else if (normalizedString in months) {
|
|
46
|
+
return months[normalizedString];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
throw new Error("Invalid month string: " + monthString);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
var parseDateIntoObject = function parseDateIntoObject(date, format) {
|
|
53
|
+
if (format != null && format.match(/d.*M/) && dayMonthYearFormat.test(date)) {
|
|
54
|
+
var _dayMonthYearFormat$e = dayMonthYearFormat.exec(date),
|
|
55
|
+
day = _dayMonthYearFormat$e[1],
|
|
56
|
+
month = _dayMonthYearFormat$e[2],
|
|
57
|
+
year = _dayMonthYearFormat$e[3];
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
year: parseInt(year, 10),
|
|
61
|
+
month: parseMonth(month),
|
|
62
|
+
day: parseInt(day, 10)
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (monthDayYearFormat.test(date)) {
|
|
67
|
+
var _monthDayYearFormat$e = monthDayYearFormat.exec(date),
|
|
68
|
+
_month = _monthDayYearFormat$e[1],
|
|
69
|
+
_day = _monthDayYearFormat$e[2],
|
|
70
|
+
_year = _monthDayYearFormat$e[3];
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
year: parseInt(_year, 10),
|
|
74
|
+
month: parseMonth(_month),
|
|
75
|
+
day: parseInt(_day, 10)
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (yearMonthDayFormat.test(date)) {
|
|
80
|
+
var _yearMonthDayFormat$e = yearMonthDayFormat.exec(date),
|
|
81
|
+
_year2 = _yearMonthDayFormat$e[1],
|
|
82
|
+
_month2 = _yearMonthDayFormat$e[2],
|
|
83
|
+
_day2 = _yearMonthDayFormat$e[3];
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
year: parseInt(_year2, 10),
|
|
87
|
+
month: parseMonth(_month2),
|
|
88
|
+
day: parseInt(_day2, 10)
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
throw new Error("Invalid date: " + date);
|
|
93
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { format } from 'date-fns';
|
|
2
|
+
import { parseDate, isValidDate, parseMonth } from './parse';
|
|
3
|
+
describe('isValidDate', function () {
|
|
4
|
+
var validDates = ['jun 6, 2022', 'June-06-2022', '6-6-2022', '6/6/2022', '06-06-2022', '2022-06-26', // year, month, day
|
|
5
|
+
'2022/6/1' // year, month, day
|
|
6
|
+
];
|
|
7
|
+
validDates.forEach(function (date) {
|
|
8
|
+
it("returns true for \"" + date + "\"", function () {
|
|
9
|
+
expect(isValidDate(date)).toBe(true);
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
var invalidDates = ['June-06-22', '13-6-2022', 'June 6 2022', '2022/6/1/1', '2022-15-06'];
|
|
13
|
+
invalidDates.forEach(function (date) {
|
|
14
|
+
it("returns false for \"" + date + "\"", function () {
|
|
15
|
+
expect(isValidDate(date)).toBe(false);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
describe('parseDate', function () {
|
|
20
|
+
var dates = [['January 31, 2022', 'January 31, 2022'], ['jun 9, 2022', 'June 09, 2022'], ['June-09-2022', 'June 09, 2022'], ['6-9-2022', 'June 09, 2022'], ['6/9/2022', 'June 09, 2022'], ['06-09-2022', 'June 09, 2022'], ['2022-06-26', 'June 26, 2022'], ['2022/6/1', 'June 01, 2022', 'MMMM dd, yyyy'], ['15/6/2022', 'June 15, 2022', 'dd/MM/YYYY'], ['1/6/2022', 'June 01, 2022', 'dd MMMM, yyyy'], ['15-aug-2023', 'August 15, 2023', 'dd MMMM, yyyy'], ['15 August, 2023', 'August 15, 2023', 'dd MMMM, yyyy']];
|
|
21
|
+
dates.forEach(function (_ref) {
|
|
22
|
+
var date = _ref[0],
|
|
23
|
+
expected = _ref[1],
|
|
24
|
+
dateFormat = _ref[2];
|
|
25
|
+
it("returns " + expected + " for \"" + date + "\"", function () {
|
|
26
|
+
expect(format(parseDate({
|
|
27
|
+
date: date,
|
|
28
|
+
format: dateFormat
|
|
29
|
+
}), 'MMMM dd, yyyy')).toEqual(expected);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
describe('parseMonth', function () {
|
|
34
|
+
var months = [['jan', 1], ['feb', 2], ['December', 12], ['2', 2], ['12', 12], ['05', 5]];
|
|
35
|
+
months.forEach(function (_ref2) {
|
|
36
|
+
var month = _ref2[0],
|
|
37
|
+
expected = _ref2[1];
|
|
38
|
+
it("returns " + expected + " for \"" + month + "\"", function () {
|
|
39
|
+
expect(parseMonth(month)).toBe(expected);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
2
|
+
export var useArrowKeysToNavigateCalendar = function useArrowKeysToNavigateCalendar(_ref) {
|
|
3
|
+
var date = _ref.date,
|
|
4
|
+
calendarIsOpen = _ref.calendarIsOpen,
|
|
5
|
+
openCalendar = _ref.openCalendar,
|
|
6
|
+
onChange = _ref.onChange;
|
|
7
|
+
var incrementDate = useCallback(function (by) {
|
|
8
|
+
if (!date) return;
|
|
9
|
+
var newDate = new Date(date);
|
|
10
|
+
newDate.setDate(newDate.getDate() + by);
|
|
11
|
+
onChange(newDate);
|
|
12
|
+
}, [date, onChange]);
|
|
13
|
+
var handleInputKeyDown = useCallback(function (event) {
|
|
14
|
+
if (calendarIsOpen) {
|
|
15
|
+
if (event.key === 'ArrowUp') {
|
|
16
|
+
event.preventDefault();
|
|
17
|
+
incrementDate(-7);
|
|
18
|
+
} else if (event.key === 'ArrowDown') {
|
|
19
|
+
event.preventDefault();
|
|
20
|
+
incrementDate(7);
|
|
21
|
+
} else if (event.key === 'ArrowLeft') {
|
|
22
|
+
event.preventDefault();
|
|
23
|
+
incrementDate(-1);
|
|
24
|
+
} else if (event.key === 'ArrowRight') {
|
|
25
|
+
event.preventDefault();
|
|
26
|
+
incrementDate(1);
|
|
27
|
+
}
|
|
28
|
+
} else {
|
|
29
|
+
if (event.key === 'ArrowDown') {
|
|
30
|
+
event.preventDefault();
|
|
31
|
+
openCalendar();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}, [calendarIsOpen, openCalendar, incrementDate]);
|
|
35
|
+
return handleInputKeyDown;
|
|
36
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState } from "react";
|
|
2
|
+
import { format } from 'date-fns';
|
|
3
|
+
import { parseDate } from "./parse";
|
|
4
|
+
export var useEditableDate = function useEditableDate(_ref) {
|
|
5
|
+
var date = _ref.date,
|
|
6
|
+
dateFormat = _ref.dateFormat,
|
|
7
|
+
dateValidator = _ref.dateValidator,
|
|
8
|
+
onChange = _ref.onChange;
|
|
9
|
+
|
|
10
|
+
var _useState = useState(),
|
|
11
|
+
keyBuffer = _useState[0],
|
|
12
|
+
setKeyBuffer = _useState[1];
|
|
13
|
+
|
|
14
|
+
var _useState2 = useState(false),
|
|
15
|
+
invalidKeyBuffer = _useState2[0],
|
|
16
|
+
setInvalidKeyBuffer = _useState2[1];
|
|
17
|
+
|
|
18
|
+
var formattedDate = useMemo(function () {
|
|
19
|
+
if (keyBuffer !== undefined) {
|
|
20
|
+
return keyBuffer;
|
|
21
|
+
} else {
|
|
22
|
+
return date ? format(date, dateFormat) : '';
|
|
23
|
+
}
|
|
24
|
+
}, [date, dateFormat, keyBuffer]);
|
|
25
|
+
var setKeyBufferAndValidate = useCallback(function (value) {
|
|
26
|
+
setKeyBuffer(value);
|
|
27
|
+
|
|
28
|
+
if (value && !dateValidator(parseDate({
|
|
29
|
+
date: value,
|
|
30
|
+
format: dateFormat
|
|
31
|
+
}))) {
|
|
32
|
+
setInvalidKeyBuffer(true);
|
|
33
|
+
} else {
|
|
34
|
+
setInvalidKeyBuffer(false);
|
|
35
|
+
}
|
|
36
|
+
}, [dateFormat, dateValidator]);
|
|
37
|
+
var setDate = useCallback(function (date) {
|
|
38
|
+
var newDate;
|
|
39
|
+
|
|
40
|
+
if (typeof date === 'string') {
|
|
41
|
+
setKeyBufferAndValidate(date);
|
|
42
|
+
newDate = parseDate({
|
|
43
|
+
date: date,
|
|
44
|
+
format: dateFormat
|
|
45
|
+
});
|
|
46
|
+
} else {
|
|
47
|
+
setKeyBufferAndValidate(undefined);
|
|
48
|
+
newDate = date;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (dateValidator(newDate) && onChange) onChange(newDate);
|
|
52
|
+
}, [onChange, setKeyBufferAndValidate, dateValidator, dateFormat]);
|
|
53
|
+
var clearKeyBuffer = useCallback(function () {
|
|
54
|
+
setKeyBufferAndValidate(undefined);
|
|
55
|
+
}, []);
|
|
56
|
+
return {
|
|
57
|
+
formattedDate: formattedDate,
|
|
58
|
+
setDate: setDate,
|
|
59
|
+
clearKeyBuffer: clearKeyBuffer,
|
|
60
|
+
invalidKeyBuffer: invalidKeyBuffer
|
|
61
|
+
};
|
|
62
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import _regeneratorRuntime from "@babel/runtime/regenerator";
|
|
2
|
+
import _asyncToGenerator from "@babel/runtime/helpers/esm/asyncToGenerator";
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { render, screen } from '@testing-library/react';
|
|
5
|
+
import userEvent from '@testing-library/user-event';
|
|
6
|
+
import Select from '.';
|
|
7
|
+
import { ThemeProvider } from '../ThemeProvider/ThemeProvider';
|
|
8
|
+
var people = [{
|
|
9
|
+
first: 'Charlie',
|
|
10
|
+
last: 'Brown',
|
|
11
|
+
twitter: 'dancounsell',
|
|
12
|
+
active: true
|
|
13
|
+
}, {
|
|
14
|
+
first: 'Charlotte',
|
|
15
|
+
last: 'White',
|
|
16
|
+
twitter: 'mtnmissy',
|
|
17
|
+
active: true
|
|
18
|
+
}, {
|
|
19
|
+
first: 'John',
|
|
20
|
+
last: 'James',
|
|
21
|
+
twitter: 'miller',
|
|
22
|
+
active: false
|
|
23
|
+
}, {
|
|
24
|
+
first: 'Travis',
|
|
25
|
+
last: 'Arnold',
|
|
26
|
+
twitter: 'souporserious',
|
|
27
|
+
active: true
|
|
28
|
+
}];
|
|
29
|
+
var selectMock = jest.fn();
|
|
30
|
+
test('can click to select item from list', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
|
|
31
|
+
return _regeneratorRuntime.wrap(function _callee$(_context) {
|
|
32
|
+
while (1) {
|
|
33
|
+
switch (_context.prev = _context.next) {
|
|
34
|
+
case 0:
|
|
35
|
+
jest.useFakeTimers();
|
|
36
|
+
render( /*#__PURE__*/React.createElement(ThemeProvider, null, /*#__PURE__*/React.createElement(Select, {
|
|
37
|
+
onChange: selectMock
|
|
38
|
+
}, people.map(function (p) {
|
|
39
|
+
return /*#__PURE__*/React.createElement(Select.Option, {
|
|
40
|
+
value: p.twitter,
|
|
41
|
+
key: p.twitter
|
|
42
|
+
}, p.first, " ", p.last);
|
|
43
|
+
}))));
|
|
44
|
+
userEvent.click(screen.getByRole('button'));
|
|
45
|
+
jest.runAllTimers();
|
|
46
|
+
userEvent.click(screen.getByText('Travis Arnold'));
|
|
47
|
+
expect(selectMock).toHaveBeenCalledWith({
|
|
48
|
+
selectedValue: 'souporserious',
|
|
49
|
+
value: 'souporserious'
|
|
50
|
+
});
|
|
51
|
+
jest.useRealTimers();
|
|
52
|
+
|
|
53
|
+
case 7:
|
|
54
|
+
case "end":
|
|
55
|
+
return _context.stop();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}, _callee);
|
|
59
|
+
})));
|
|
@@ -4,7 +4,7 @@ import { StackViewProps } from '../StackView';
|
|
|
4
4
|
declare type ButtonProps = {
|
|
5
5
|
children?: any;
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* "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.
|
|
8
8
|
*/
|
|
9
9
|
disabled?: boolean;
|
|
10
10
|
/**
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export declare type DateFieldProps = {
|
|
2
|
+
/**
|
|
3
|
+
* Format the displayed date using date-fns [format](https://date-fns.org/v2.0.0-alpha.9/docs/format) function.
|
|
4
|
+
*/
|
|
5
|
+
formatValue: string;
|
|
6
|
+
/**
|
|
7
|
+
* Controls the initial Popover state: open or closed (default).
|
|
8
|
+
*/
|
|
9
|
+
defaultOpen: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* The minimum date that can be chosen.
|
|
12
|
+
*/
|
|
13
|
+
minDate: Date;
|
|
14
|
+
/**
|
|
15
|
+
* The maximum date that can be chosen.
|
|
16
|
+
*/
|
|
17
|
+
maxDate: Date;
|
|
18
|
+
/**
|
|
19
|
+
* Called when a date has been selected.
|
|
20
|
+
*/
|
|
21
|
+
onChange: (date: Date) => null;
|
|
22
|
+
/**
|
|
23
|
+
* Determines where the popover is placed.
|
|
24
|
+
*/
|
|
25
|
+
placement: string;
|
|
26
|
+
/**
|
|
27
|
+
* The date that will be selected.
|
|
28
|
+
*/
|
|
29
|
+
value: Date;
|
|
30
|
+
/**
|
|
31
|
+
* Locks external scrollbars when open.
|
|
32
|
+
*/
|
|
33
|
+
lockScrollWhileOpen?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Attempts to keep popover in view clipping edges if too large.
|
|
36
|
+
*/
|
|
37
|
+
keepInView?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Accepts any valid [Calendar](/calendar) props.
|
|
40
|
+
*/
|
|
41
|
+
calendarProps?: object;
|
|
42
|
+
/**
|
|
43
|
+
* Accepts any valid [Popover](/popover) props.
|
|
44
|
+
*/
|
|
45
|
+
popoverProps?: object;
|
|
46
|
+
};
|
|
47
|
+
declare function DateField({ formatValue, defaultOpen, keepInView, lockScrollWhileOpen, minDate, maxDate, onChange, placement, value, ...props }: DateFieldProps): JSX.Element;
|
|
48
|
+
export default DateField;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
interface Params {
|
|
2
|
+
/**
|
|
3
|
+
* The string we want to parse into a date object
|
|
4
|
+
*/
|
|
5
|
+
date: string;
|
|
6
|
+
/**
|
|
7
|
+
* Format hint for parser
|
|
8
|
+
* Helps us know if we should parse day/month/year or month/day/year
|
|
9
|
+
*
|
|
10
|
+
* Should adhere to [date-fns spec](https://date-fns.org/v2.0.0-alpha.9/docs/format).
|
|
11
|
+
*/
|
|
12
|
+
format?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare const parseDate: ({ date, format }: Params) => Date | null;
|
|
15
|
+
export declare const isValidDate: (date: string) => boolean;
|
|
16
|
+
export declare const parseMonth: (monthString: string) => number;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
date: Date;
|
|
3
|
+
calendarIsOpen: boolean;
|
|
4
|
+
openCalendar: () => void;
|
|
5
|
+
onChange: (date: Date) => void;
|
|
6
|
+
}
|
|
7
|
+
export declare const useArrowKeysToNavigateCalendar: ({ date, calendarIsOpen, openCalendar, onChange, }: Props) => (event: React.KeyboardEvent<HTMLInputElement>) => void;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
interface Params {
|
|
2
|
+
/**
|
|
3
|
+
* The currently selected date
|
|
4
|
+
*/
|
|
5
|
+
date: Date;
|
|
6
|
+
/**
|
|
7
|
+
* Format the displayed date using date-fns [format](https://date-fns.org/v2.0.0-alpha.9/docs/format) function.
|
|
8
|
+
*/
|
|
9
|
+
dateFormat: string;
|
|
10
|
+
/**
|
|
11
|
+
* Custom function that validates date
|
|
12
|
+
*/
|
|
13
|
+
dateValidator: (date: Date) => boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Called when a valid date is entered
|
|
16
|
+
*/
|
|
17
|
+
onChange: (date: Date) => void;
|
|
18
|
+
}
|
|
19
|
+
export declare const useEditableDate: ({ date, dateFormat, dateValidator, onChange }: Params) => {
|
|
20
|
+
formattedDate: string;
|
|
21
|
+
setDate: (date: Date | string) => void;
|
|
22
|
+
clearKeyBuffer: () => void;
|
|
23
|
+
invalidKeyBuffer: boolean;
|
|
24
|
+
};
|
|
25
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@planningcenter/tapestry-react",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.0",
|
|
4
4
|
"description": "A collection of flexible React components to help you build resilient, accessible user interfaces quickly and effectively.",
|
|
5
5
|
"author": "Front End Systems Engineering <frontend@pco.bz>",
|
|
6
6
|
"main": "dist/cjs/index.js",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@emotion/cache": "10.x",
|
|
40
40
|
"@emotion/react": "^11.10.5",
|
|
41
|
-
"react": "
|
|
42
|
-
"react-dom": "
|
|
41
|
+
"react": ">=16.8.0 <19",
|
|
42
|
+
"react-dom": ">=16.8.0 <19"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@babel/cli": "7.12.17",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
-
import { render, fireEvent } from '@testing-library/react'
|
|
2
|
+
import { render, fireEvent, createEvent } from '@testing-library/react'
|
|
3
3
|
import { Button } from './Button'
|
|
4
4
|
|
|
5
5
|
it(`should render as <button> with type="button" by default`, () => {
|
|
@@ -14,20 +14,44 @@ it(`should render as <button> with type="submit"`, () => {
|
|
|
14
14
|
expect(button.getAttribute('type')).toEqual('submit')
|
|
15
15
|
})
|
|
16
16
|
|
|
17
|
-
it(`
|
|
17
|
+
it(`if "disabled" prop is provided, set "aria-disabled" attribute`, () => {
|
|
18
18
|
const { container } = render(<Button disabled />)
|
|
19
19
|
const button = container.querySelector('button')
|
|
20
|
-
expect(button.getAttribute("aria-disabled")).toEqual(null)
|
|
21
|
-
expect(button.disabled).toBe(true)
|
|
22
|
-
})
|
|
23
20
|
|
|
24
|
-
it(`should render "aria-disabled" attribute, if <button type="submit"> is disabled`, () => {
|
|
25
|
-
const { container } = render(<Button type="submit" disabled />)
|
|
26
|
-
const button = container.querySelector('button')
|
|
27
21
|
expect(button.getAttribute("aria-disabled")).toEqual("true")
|
|
28
22
|
expect(button.disabled).toBe(false)
|
|
29
23
|
})
|
|
30
24
|
|
|
25
|
+
it(`if "disabled" prop is provided, prevent "Click" event from firing`, () => {
|
|
26
|
+
const { container } = render(<Button disabled />)
|
|
27
|
+
const button = container.querySelector('button')
|
|
28
|
+
|
|
29
|
+
const clickEvent = createEvent.click(button)
|
|
30
|
+
fireEvent(button, clickEvent)
|
|
31
|
+
|
|
32
|
+
expect(clickEvent.defaultPrevented).toBe(true)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it(`if "disabled" prop is provided, prevent keyDown "Enter" event from firing`, () => {
|
|
36
|
+
const { container } = render(<Button disabled />)
|
|
37
|
+
const button = container.querySelector('button')
|
|
38
|
+
|
|
39
|
+
const keyDownEvent = createEvent.keyDown(button, { key: 'Enter' })
|
|
40
|
+
fireEvent(button, keyDownEvent)
|
|
41
|
+
|
|
42
|
+
expect(keyDownEvent.defaultPrevented).toBe(true)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it(`if "disabled" prop is provided, prevent keyDown "Space" event from firing`, () => {
|
|
46
|
+
const { container } = render(<Button disabled />)
|
|
47
|
+
const button = container.querySelector('button')
|
|
48
|
+
|
|
49
|
+
const keyDownEvent = createEvent.keyDown(button, { key: ' ' })
|
|
50
|
+
fireEvent(button, keyDownEvent)
|
|
51
|
+
|
|
52
|
+
expect(keyDownEvent.defaultPrevented).toBe(true)
|
|
53
|
+
})
|
|
54
|
+
|
|
31
55
|
it(`should render title`, () => {
|
|
32
56
|
const title = 'Hello'
|
|
33
57
|
const { getByText } = render(<Button title={title} />)
|