@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.
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 +78 -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 +79 -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} +104 -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,77 @@ 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 handleNavigationChangeFromCalendar = useCallback(function (date) {
83
+ setDate(date);
84
+ }, [setDate]);
85
+ var navigateCalendarWithArrowKeys = useArrowKeysToNavigateCalendar({
86
+ date: value,
87
+ calendarIsOpen: isPopoverOpen,
88
+ openCalendar: openPopover,
89
+ onChange: setDate
79
90
  });
91
+ var handleInputOnChange = useCallback(function (event) {
92
+ setDate(event.currentTarget.value);
93
+ }, [setDate]);
94
+ var handleOnBlur = useCallback(function () {
95
+ clearKeyBuffer();
96
+ closePopover();
97
+ }, [clearKeyBuffer, closePopover]);
98
+ var inputColors = useMemo(function () {
99
+ if (invalidKeyBuffer) {
100
+ return {
101
+ color: 'error-darker',
102
+ backgroundColor: 'error-lighter'
103
+ };
104
+ } else {
105
+ return {};
106
+ }
107
+ }, [invalidKeyBuffer]);
108
+
109
+ var _ref3 = /*#__PURE__*/React.createElement(Icon, {
110
+ name: "general.calendar",
111
+ color: "foregroundTertiary",
112
+ onClick: openPopover
113
+ });
114
+
80
115
  return /*#__PURE__*/React.createElement(FocusGroup, {
81
- onBlur: closePopover
116
+ onBlur: handleOnBlur
82
117
  }, function (_ref2) {
83
118
  var requestBlur = _ref2.requestBlur,
84
119
  setRef = _ref2.setRef;
@@ -86,44 +121,40 @@ function DateField(_ref) {
86
121
  ref: function ref(component) {
87
122
  popover.current = component;
88
123
  },
124
+ as: Card,
125
+ elevation: 2,
89
126
  innerRef: function innerRef(node) {
90
127
  popover.current = node;
91
128
  setRef(id + "-popover")(node);
92
129
  },
93
- as: Card,
94
- tabIndex: -1,
95
- elevation: 2,
96
- onBlur: requestBlur,
97
130
  keepInView: keepInView,
98
131
  lockScrollWhileOpen: lockScrollWhileOpen,
99
- placement: placement,
100
- open: isPopoverOpen,
132
+ onBlur: requestBlur,
101
133
  onRequestClose: closePopover,
134
+ open: isPopoverOpen,
135
+ placement: placement,
136
+ tabIndex: -1,
102
137
  anchorElement: /*#__PURE__*/React.createElement(Input, _extends({
103
138
  innerRef: function innerRef(node) {
104
139
  inputWrapper.current = node;
105
140
  setRef(id + "-input")(node);
106
141
  },
107
- readOnly: true,
108
- value: value ? format(value, formatValue) : '',
109
- renderRight: _ref3,
110
- onClick: togglePopover,
111
142
  onBlur: requestBlur,
112
- onKeyDown: function onKeyDown(event) {
113
- if (event.key === ' ') {
114
- event.preventDefault();
115
- togglePopover();
116
- }
117
- }
118
- }, restProps))
143
+ onFocus: openPopover,
144
+ onChange: handleInputOnChange,
145
+ onKeyDown: navigateCalendarWithArrowKeys,
146
+ value: formattedDate,
147
+ renderRight: _ref3
148
+ }, inputColors, restProps))
119
149
  }), /*#__PURE__*/React.createElement(Calendar, _extends({
120
150
  size: "sm"
121
151
  }, calendarProps, {
122
- initialDate: value,
152
+ date: value,
123
153
  selected: value,
124
154
  minDate: minDate,
125
155
  maxDate: maxDate,
126
- onDateSelect: handleDateSelect
156
+ onDateSelect: handleDateSelectedFromCalendar,
157
+ onDateChange: handleNavigationChangeFromCalendar
127
158
  })));
128
159
  });
129
160
  }
@@ -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.1",
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} />)