@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.
Files changed (39) hide show
  1. package/dist/cjs/Button/Button.js +10 -13
  2. package/dist/cjs/Button/Button.test.js +53 -21
  3. package/dist/cjs/Calendar/Calendar.js +30 -25
  4. package/dist/cjs/Combobox/ComboboxInput.js +41 -37
  5. package/dist/cjs/DateField/DateField.js +74 -47
  6. package/dist/cjs/DateField/parse.js +106 -0
  7. package/dist/cjs/DateField/parse.test.js +46 -0
  8. package/dist/cjs/DateField/useArrowKeysToNavigateCalendar.js +44 -0
  9. package/dist/cjs/DateField/useEditableDate.js +72 -0
  10. package/dist/cjs/Select/Select.test.js +74 -0
  11. package/dist/esm/Button/Button.js +10 -13
  12. package/dist/esm/Button/Button.test.js +58 -26
  13. package/dist/esm/Calendar/Calendar.js +30 -25
  14. package/dist/esm/Combobox/ComboboxInput.js +40 -37
  15. package/dist/esm/DateField/DateField.js +75 -48
  16. package/dist/esm/DateField/parse.js +93 -0
  17. package/dist/esm/DateField/parse.test.js +42 -0
  18. package/dist/esm/DateField/useArrowKeysToNavigateCalendar.js +36 -0
  19. package/dist/esm/DateField/useEditableDate.js +62 -0
  20. package/dist/esm/Select/Select.test.js +59 -0
  21. package/dist/types/Button/Button.d.ts +1 -1
  22. package/dist/types/DateField/DateField.d.ts +48 -0
  23. package/dist/types/DateField/parse.d.ts +17 -0
  24. package/dist/types/DateField/parse.test.d.ts +1 -0
  25. package/dist/types/DateField/useArrowKeysToNavigateCalendar.d.ts +8 -0
  26. package/dist/types/DateField/useEditableDate.d.ts +25 -0
  27. package/dist/types/Select/Select.test.d.ts +1 -0
  28. package/package.json +3 -3
  29. package/src/Button/Button.test.tsx +32 -8
  30. package/src/Button/Button.tsx +8 -9
  31. package/src/Calendar/Calendar.js +22 -17
  32. package/src/Combobox/ComboboxInput.js +76 -62
  33. package/src/DateField/DateField.mdx +15 -0
  34. package/src/DateField/{DateField.js → DateField.tsx} +96 -52
  35. package/src/DateField/parse.test.ts +76 -0
  36. package/src/DateField/parse.ts +92 -0
  37. package/src/DateField/useArrowKeysToNavigateCalendar.ts +54 -0
  38. package/src/DateField/useEditableDate.ts +81 -0
  39. 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
- var _ref3 = /*#__PURE__*/React.createElement(Icon, {
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 openPopover = useCallback(function () {
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
- if (onChange) {
75
- onChange(date);
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: closePopover
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
- placement: placement,
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
- onKeyDown: function onKeyDown(event) {
113
- if (event.key === ' ') {
114
- event.preventDefault();
115
- togglePopover();
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
- initialDate: value,
149
+ date: value,
123
150
  selected: value,
124
151
  minDate: minDate,
125
152
  maxDate: maxDate,
126
- onDateSelect: handleDateSelect
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
- * Disables button by removing click handlers and making the button transparent.
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.7.0",
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": "^16.8.0 || ^17.0.0",
42
- "react-dom": "^16.8.0 || ^17.0.0"
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(`should render "disabled" attribute, if <button> is disabled`, () => {
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} />)