@primer/components 0.0.0-2021931194230 → 0.0.0-202193121134

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/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @primer/components
2
2
 
3
- ## 0.0.0-2021931194230
3
+ ## 0.0.0-202193121134
4
4
 
5
5
  ### Patch Changes
6
6
 
@@ -85,6 +85,7 @@ const DatePicker = ({
85
85
 
86
86
  return /*#__PURE__*/_react.default.createElement(_useDatePicker.DatePickerProvider, {
87
87
  configuration: datePickerConfiguration,
88
+ isOpen: isOpen,
88
89
  value: value,
89
90
  closePicker: () => setIsOpen(false)
90
91
  }, /*#__PURE__*/_react.default.createElement(_DatePickerAnchor.DatePickerAnchor, {
@@ -5,7 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.DatePickerOverlay = void 0;
7
7
 
8
- var _react = _interopRequireWildcard(require("react"));
8
+ var _react = _interopRequireDefault(require("react"));
9
9
 
10
10
  var _useDatePicker = _interopRequireDefault(require("./useDatePicker"));
11
11
 
@@ -15,10 +15,6 @@ var _DatePickerPanel = require("./DatePickerPanel");
15
15
 
16
16
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
17
17
 
18
- function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
19
-
20
- function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
21
-
22
18
  function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
23
19
 
24
20
  const DatePickerOverlay = ({
@@ -26,15 +22,16 @@ const DatePickerOverlay = ({
26
22
  ...rest
27
23
  }) => {
28
24
  const {
29
- onClose: onDatePickerClose
25
+ dialogOpen,
26
+ onClose: onDatePickerClose,
27
+ setDialogOpen
30
28
  } = (0, _useDatePicker.default)();
31
- const [suspendFocusTrap, setSuspendFocusTrap] = (0, _react.useState)(false);
32
29
 
33
30
  const onOverlayClose = async gesture => {
34
- if (!suspendFocusTrap) {
35
- setSuspendFocusTrap(true);
31
+ if (!dialogOpen) {
32
+ setDialogOpen(true);
36
33
  await onDatePickerClose();
37
- setSuspendFocusTrap(false);
34
+ setDialogOpen(false);
38
35
  onClose === null || onClose === void 0 ? void 0 : onClose(gesture);
39
36
  }
40
37
  };
@@ -6,6 +6,7 @@ import { DaySelection } from './useDatePicker';
6
6
  export declare type DayProps = {
7
7
  blocked?: boolean;
8
8
  disabled?: boolean;
9
+ focused?: boolean;
9
10
  onAction?: (date: Date, event?: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>) => void;
10
11
  selected?: DaySelection;
11
12
  date: Date;
@@ -90,15 +90,12 @@ const getStateStyles = (props, prop, state) => {
90
90
  const {
91
91
  blocked,
92
92
  disabled,
93
+ focused,
93
94
  selected,
94
95
  today
95
96
  } = props;
96
97
 
97
- if (blocked) {
98
- return states.blocked[prop];
99
- } else if (disabled) {
100
- return states.disabled[prop];
101
- } else if (selected) {
98
+ if (selected) {
102
99
  switch (selected) {
103
100
  case 'start':
104
101
  return today && prop === 'color' ? states.selected.start['todayColor'] : states.selected.start[prop];
@@ -112,6 +109,12 @@ const getStateStyles = (props, prop, state) => {
112
109
  default:
113
110
  return today && prop === 'color' ? states.selected.default['todayColor'] : states.selected.default[prop];
114
111
  }
112
+ } else if (blocked) {
113
+ return states.blocked[prop];
114
+ } else if (disabled) {
115
+ return states.disabled[prop];
116
+ } else if (focused) {
117
+ return states.default.hover[prop];
115
118
  } else {
116
119
  return today && prop === 'color' ? states.default[state]['todayColor'] : states.default[state][prop];
117
120
  }
@@ -128,7 +131,7 @@ const DayComponent = (0, _styledComponents.default)(DayBaseComponent).attrs(prop
128
131
  })).withConfig({
129
132
  displayName: "Day__DayComponent",
130
133
  componentId: "sc-1japneh-1"
131
- })(["background-color:", ";border-radius:", ";transition:0.1s background-color ease;& ", "{align-self:center;color:", ";display:flex;font-family:", ";font-size:", ";justify-self:center;user-select:none;transition:0.1s color ease;}&:hover{background-color:", ";cursor:pointer;transition:0.05s background-color ease;& ", "{color:", ";transition:0.1s color ease;}}&:active{background-color:", ";box-shadow:inset ", ";transition:0.1s background-color ease,0.1s box-shadow ease,0.1s color ease;& ", "{color:", ";transition:0.1s color ease;}}"], props => props.background, props => props.borderRadius, _Text.default, props => props.textColor, (0, _constants.get)('fonts.mono'), (0, _constants.get)('fontSizes.0'), props => props.backgroundHover, _Text.default, props => props.textColorHover, props => props.backgroundPressed, (0, _constants.get)('shadows.shadow.medium'), _Text.default, props => props.textColorPressed);
134
+ })(["background-color:", ";border-radius:", ";opacity:", ";transition:0.1s background-color ease;& ", "{align-self:center;color:", ";display:flex;font-family:", ";font-size:", ";justify-self:center;user-select:none;transition:0.1s color ease;}&:hover{background-color:", ";cursor:pointer;transition:0.05s background-color ease;& ", "{color:", ";transition:0.1s color ease;}}&:active{background-color:", ";box-shadow:inset ", ";transition:0.1s background-color ease,0.1s box-shadow ease,0.1s color ease;& ", "{color:", ";transition:0.1s color ease;}}"], props => props.background, props => props.borderRadius, props => props.disabled ? 0.5 : 1, _Text.default, props => props.textColor, (0, _constants.get)('fonts.mono'), (0, _constants.get)('fontSizes.0'), props => props.backgroundHover, _Text.default, props => props.textColorHover, props => props.backgroundPressed, (0, _constants.get)('shadows.shadow.medium'), _Text.default, props => props.textColorPressed);
132
135
 
133
136
  const Day = ({
134
137
  date,
@@ -139,6 +142,7 @@ const Day = ({
139
142
  onSelection,
140
143
  disabled,
141
144
  blocked,
145
+ focused,
142
146
  selected,
143
147
  today
144
148
  } = (0, _useDatePicker.default)(date);
@@ -172,6 +176,7 @@ const Day = ({
172
176
  "aria-selected": selected !== false,
173
177
  blocked: blocked,
174
178
  disabled: disabled,
179
+ focused: focused,
175
180
  selected: selected,
176
181
  today: today,
177
182
  onClick: clickHandler,
@@ -33,6 +33,8 @@ export interface DatePickerContext {
33
33
  disabled?: boolean;
34
34
  configuration: DatePickerConfiguration;
35
35
  currentViewingDate: Date;
36
+ dialogOpen: boolean;
37
+ focusDate: Date;
36
38
  goToMonth: (date: Date) => void;
37
39
  hoverRange?: RangeSelection | null;
38
40
  selection?: Selection;
@@ -47,6 +49,7 @@ export interface DatePickerContext {
47
49
  previousMonth: () => void;
48
50
  revertValue: () => void;
49
51
  saveValue: (selection?: Selection) => void;
52
+ setDialogOpen: React.Dispatch<React.SetStateAction<boolean>>;
50
53
  }
51
54
  export declare type Selection = Date | Array<Date> | RangeSelection | null;
52
55
  export declare type StringSelection = string | Array<string> | {
@@ -56,11 +59,14 @@ export declare type StringSelection = string | Array<string> | {
56
59
  export declare type DaySelection = boolean | 'start' | 'middle' | 'end';
57
60
  declare const useDatePicker: (date?: Date | undefined) => {
58
61
  blocked: boolean | undefined;
59
- disabled: boolean;
62
+ disabled: boolean | undefined;
63
+ focused: boolean;
60
64
  selected: DaySelection;
61
65
  today: boolean;
62
66
  configuration: DatePickerConfiguration;
63
67
  currentViewingDate: Date;
68
+ dialogOpen: boolean;
69
+ focusDate: Date;
64
70
  goToMonth: (date: Date) => void;
65
71
  hoverRange?: RangeSelection | null | undefined;
66
72
  selection?: Selection | undefined;
@@ -75,11 +81,13 @@ declare const useDatePicker: (date?: Date | undefined) => {
75
81
  previousMonth: () => void;
76
82
  revertValue: () => void;
77
83
  saveValue: (selection?: Selection | undefined) => void;
84
+ setDialogOpen: React.Dispatch<React.SetStateAction<boolean>>;
78
85
  };
79
86
  export default useDatePicker;
80
87
  export interface DatePickerProviderProps {
81
88
  closePicker?: () => void;
82
89
  configuration?: DatePickerConfiguration;
90
+ isOpen?: boolean;
83
91
  value?: Selection | StringSelection;
84
92
  }
85
93
  export declare function isSingleSelection(selection: Selection): selection is Date;
@@ -13,6 +13,8 @@ var _octiconsReact = require("@primer/octicons-react");
13
13
 
14
14
  var _dateFns = require("date-fns");
15
15
 
16
+ var _esm = require("date-fns/esm");
17
+
16
18
  var _deepmerge = _interopRequireDefault(require("deepmerge"));
17
19
 
18
20
  var _react = _interopRequireWildcard(require("react"));
@@ -30,65 +32,69 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
30
32
  const DatePickerContext = /*#__PURE__*/(0, _react.createContext)(null);
31
33
 
32
34
  const useDatePicker = date => {
33
- const value = (0, _react.useContext)(DatePickerContext);
35
+ const dateCtx = (0, _react.useContext)(DatePickerContext);
34
36
  const [selected, setSelected] = (0, _react.useState)(false);
35
37
  const today = date ? (0, _dateFns.isToday)(date) : false;
36
38
 
37
- if (!value) {
39
+ if (!dateCtx) {
38
40
  throw new Error('useDatePicker must be used inside a DatePickerProvider');
39
41
  }
40
42
 
41
43
  (0, _react.useEffect)(() => {
42
44
  if (date) {
43
- if (value.hoverRange) {
44
- if (isRangeSelection(value.hoverRange)) {
45
- if ((0, _dateFns.isEqual)(date, value.hoverRange.from)) {
45
+ if (dateCtx.hoverRange) {
46
+ if (isRangeSelection(dateCtx.hoverRange)) {
47
+ if ((0, _dateFns.isEqual)(date, dateCtx.hoverRange.from)) {
46
48
  setSelected('start');
47
- } else if (value.hoverRange.to && (0, _dateFns.isEqual)(date, value.hoverRange.to)) {
49
+ } else if (dateCtx.hoverRange.to && (0, _dateFns.isEqual)(date, dateCtx.hoverRange.to)) {
48
50
  setSelected('end');
49
- } else if ((0, _dateFns.isAfter)(date, value.hoverRange.from) && value.hoverRange.to && (0, _dateFns.isBefore)(date, value.hoverRange.to)) {
51
+ } else if ((0, _dateFns.isAfter)(date, dateCtx.hoverRange.from) && dateCtx.hoverRange.to && (0, _dateFns.isBefore)(date, dateCtx.hoverRange.to)) {
50
52
  setSelected('middle');
51
53
  } else {
52
54
  setSelected(false);
53
55
  }
54
56
  }
55
- } else if (value.selection) {
56
- if (isMultiSelection(value.selection)) {
57
- setSelected(!!value.selection.find(d => (0, _dateFns.isEqual)(d, date)));
58
- } else if (isRangeSelection(value.selection)) {
59
- if ((0, _dateFns.isEqual)(date, value.selection.from)) {
57
+ } else if (dateCtx.selection) {
58
+ if (isMultiSelection(dateCtx.selection)) {
59
+ setSelected(!!dateCtx.selection.find(d => (0, _dateFns.isEqual)(d, date)));
60
+ } else if (isRangeSelection(dateCtx.selection)) {
61
+ if ((0, _dateFns.isEqual)(date, dateCtx.selection.from)) {
60
62
  setSelected('start');
61
- } else if (value.selection.to && (0, _dateFns.isEqual)(date, value.selection.to)) {
63
+ } else if (dateCtx.selection.to && (0, _dateFns.isEqual)(date, dateCtx.selection.to)) {
62
64
  setSelected('end');
63
- } else if ((0, _dateFns.isAfter)(date, value.selection.from) && value.selection.to && (0, _dateFns.isBefore)(date, value.selection.to)) {
65
+ } else if ((0, _dateFns.isAfter)(date, dateCtx.selection.from) && dateCtx.selection.to && (0, _dateFns.isBefore)(date, dateCtx.selection.to)) {
64
66
  setSelected('middle');
65
67
  } else {
66
68
  setSelected(false);
67
69
  }
68
70
  } else {
69
- setSelected((0, _dateFns.isEqual)(date, value.selection));
71
+ setSelected((0, _dateFns.isEqual)(date, dateCtx.selection));
70
72
  }
71
73
  }
72
74
  }
73
- }, [date, value.hoverRange, value.selection, today]);
75
+ }, [date, dateCtx.hoverRange, dateCtx.selection, today]);
74
76
  let blocked,
75
- disabled = false;
77
+ disabled,
78
+ focused = false;
76
79
 
77
80
  if (date) {
78
- // Determine if date is blocked out
79
- if (value.configuration.blockedDates) {
80
- blocked = !!value.configuration.blockedDates.find(d => (0, _dateFns.isEqual)(d, date));
81
+ // Determine if date is focused
82
+ if ((0, _dateFns.isEqual)(dateCtx.focusDate, date)) focused = true; // Determine if date is blocked out
83
+
84
+ if (dateCtx.configuration.blockedDates) {
85
+ blocked = !!dateCtx.configuration.blockedDates.find(d => (0, _dateFns.isEqual)(d, date));
81
86
  } // Determine if date is disabled
82
87
 
83
88
 
84
- if (value.configuration.minDate || value.configuration.maxDate || value.configuration.disableWeekends) {
85
- disabled = (value.configuration.minDate ? (0, _dateFns.isBefore)(date, value.configuration.minDate) : false) || (value.configuration.maxDate ? (0, _dateFns.isAfter)(date, value.configuration.maxDate) : false) || (value.configuration.disableWeekends ? (0, _dateFns.isWeekend)(date) : false);
89
+ if (dateCtx.configuration.minDate || dateCtx.configuration.maxDate || dateCtx.configuration.disableWeekends) {
90
+ disabled = (dateCtx.configuration.minDate ? (0, _dateFns.isBefore)(date, dateCtx.configuration.minDate) : false) || (dateCtx.configuration.maxDate ? (0, _dateFns.isAfter)(date, dateCtx.configuration.maxDate) : false) || (dateCtx.configuration.disableWeekends ? (0, _dateFns.isWeekend)(date) : false);
86
91
  }
87
92
  }
88
93
 
89
- return { ...value,
94
+ return { ...dateCtx,
90
95
  blocked,
91
96
  disabled,
97
+ focused,
92
98
  selected,
93
99
  today
94
100
  };
@@ -167,6 +173,20 @@ function parseSelection(selection, variant) {
167
173
  }
168
174
  }
169
175
 
176
+ const getInitialFocusDate = selection => {
177
+ if (!selection) return new Date();
178
+
179
+ if (selection instanceof Date) {
180
+ return selection;
181
+ } else if (Array.isArray(selection)) {
182
+ return selection[0];
183
+ } else if (isRangeSelection(selection)) {
184
+ return selection.from;
185
+ } else {
186
+ return new Date();
187
+ }
188
+ };
189
+
170
190
  const defaultConfiguration = {
171
191
  anchorVariant: 'button',
172
192
  confirmation: false,
@@ -184,6 +204,7 @@ const DatePickerProvider = ({
184
204
  configuration: externalConfig = {},
185
205
  children,
186
206
  closePicker,
207
+ isOpen = false,
187
208
  value
188
209
  }) => {
189
210
  const [configuration, setConfiguration] = (0, _react.useState)((0, _deepmerge.default)(defaultConfiguration, externalConfig));
@@ -193,6 +214,8 @@ const DatePickerProvider = ({
193
214
  const [hoverRange, setHoverRange] = (0, _react.useState)(null);
194
215
  const [currentViewingDate, setCurrentViewingDate] = (0, _react.useState)(new Date());
195
216
  const confirm = (0, _.useConfirm)();
217
+ const [dialogOpen, setDialogOpen] = (0, _react.useState)(false);
218
+ const [focusDate, setFocusDate] = (0, _react.useState)(getInitialFocusDate(selection));
196
219
  (0, _react.useEffect)(() => {
197
220
  setConfiguration((0, _deepmerge.default)(defaultConfiguration, externalConfig));
198
221
  setSelection(parseSelection(selection, configuration.variant)); // Don't want this to run every time selection gets updated
@@ -330,6 +353,67 @@ const DatePickerProvider = ({
330
353
  }
331
354
  }
332
355
  }, [configuration]);
356
+ const handleKeyDown = (0, _react.useCallback)(async e => {
357
+ const key = e.key;
358
+
359
+ switch (key) {
360
+ case 'ArrowRight':
361
+ {
362
+ // Increase selection by 1 day
363
+ setFocusDate((0, _dateFns.addDays)(focusDate, 1));
364
+ break;
365
+ }
366
+
367
+ case 'ArrowLeft':
368
+ {
369
+ // Decrease selection by 1 day
370
+ setFocusDate((0, _dateFns.subDays)(focusDate, 1));
371
+ break;
372
+ }
373
+
374
+ case 'ArrowUp':
375
+ {
376
+ // Decrease selection by 1 week
377
+ setFocusDate((0, _dateFns.subWeeks)(focusDate, 1));
378
+ break;
379
+ }
380
+
381
+ case 'ArrowDown':
382
+ {
383
+ // Decrease selection by 1 week
384
+ setFocusDate((0, _dateFns.addWeeks)(focusDate, 1));
385
+ break;
386
+ }
387
+
388
+ case 'Enter':
389
+ {
390
+ // Start Selection
391
+ break;
392
+ }
393
+
394
+ case 'Esc':
395
+ {
396
+ // Cancel Selection if started, reset if not? or close
397
+ break;
398
+ }
399
+
400
+ default:
401
+ {
402
+ break;
403
+ }
404
+ }
405
+ }, [focusDate]);
406
+ (0, _react.useEffect)(() => {
407
+ if (isOpen) {
408
+ window.addEventListener('keydown', handleKeyDown);
409
+ } else {
410
+ window.removeEventListener('keydown', handleKeyDown);
411
+ }
412
+
413
+ return () => {
414
+ window.removeEventListener('keydown', handleKeyDown);
415
+ };
416
+ }, [handleKeyDown, isOpen]);
333
417
  const selectionHandler = (0, _react.useCallback)(date => {
334
418
  setIsDirty(true);
335
419
 
@@ -389,6 +473,7 @@ const DatePickerProvider = ({
389
473
  minDate,
390
474
  maxDate,
391
475
  maxRangeSize,
476
+ disableWeekends,
392
477
  variant
393
478
  } = configuration;
394
479
 
@@ -401,6 +486,10 @@ const DatePickerProvider = ({
401
486
  hoverDate = (0, _dateFns.isBefore)(hoverDate, selection.from) ? (0, _dateFns.subDays)(selection.from, configuration.maxRangeSize - 1) : (0, _dateFns.addDays)(selection.from, configuration.maxRangeSize - 1);
402
487
  }
403
488
 
489
+ if (disableWeekends && (0, _dateFns.isWeekend)(hoverDate)) {
490
+ hoverDate = (0, _esm.previousFriday)(hoverDate);
491
+ }
492
+
404
493
  setHoverRange((0, _dateFns.isBefore)(hoverDate, selection.from) ? {
405
494
  from: hoverDate,
406
495
  to: selection.from
@@ -415,9 +504,11 @@ const DatePickerProvider = ({
415
504
  configuration,
416
505
  currentViewingDate,
417
506
  disabled: false,
507
+ focusDate,
418
508
  formattedDate: getFormattedDate,
419
509
  goToMonth,
420
510
  hoverRange,
511
+ dialogOpen,
421
512
  nextMonth,
422
513
  onClose: handleClose,
423
514
  onDateInput: inputHandler,
@@ -427,9 +518,10 @@ const DatePickerProvider = ({
427
518
  revertValue,
428
519
  saveValue,
429
520
  selectionActive: false,
430
- selection
521
+ selection,
522
+ setDialogOpen
431
523
  };
432
- }, [configuration, currentViewingDate, focusHnadler, getFormattedDate, goToMonth, handleClose, hoverRange, inputHandler, nextMonth, previousMonth, revertValue, saveValue, selection, selectionHandler]);
524
+ }, [configuration, currentViewingDate, dialogOpen, focusDate, focusHnadler, getFormattedDate, goToMonth, handleClose, hoverRange, inputHandler, nextMonth, previousMonth, revertValue, saveValue, selection, selectionHandler]);
433
525
  return /*#__PURE__*/_react.default.createElement(DatePickerContext.Provider, {
434
526
  value: datePickerCtx
435
527
  }, children);
@@ -70,6 +70,7 @@ export const DatePicker = ({
70
70
 
71
71
  return /*#__PURE__*/React.createElement(DatePickerProvider, {
72
72
  configuration: datePickerConfiguration,
73
+ isOpen: isOpen,
73
74
  value: value,
74
75
  closePicker: () => setIsOpen(false)
75
76
  }, /*#__PURE__*/React.createElement(DatePickerAnchor, {
@@ -1,6 +1,6 @@
1
1
  function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
2
2
 
3
- import React, { useState } from 'react';
3
+ import React from 'react';
4
4
  import useDatePicker from './useDatePicker';
5
5
  import { AnchoredOverlay } from '../AnchoredOverlay';
6
6
  import { DatePickerPanel } from './DatePickerPanel';
@@ -9,15 +9,16 @@ export const DatePickerOverlay = ({
9
9
  ...rest
10
10
  }) => {
11
11
  const {
12
- onClose: onDatePickerClose
12
+ dialogOpen,
13
+ onClose: onDatePickerClose,
14
+ setDialogOpen
13
15
  } = useDatePicker();
14
- const [suspendFocusTrap, setSuspendFocusTrap] = useState(false);
15
16
 
16
17
  const onOverlayClose = async gesture => {
17
- if (!suspendFocusTrap) {
18
- setSuspendFocusTrap(true);
18
+ if (!dialogOpen) {
19
+ setDialogOpen(true);
19
20
  await onDatePickerClose();
20
- setSuspendFocusTrap(false);
21
+ setDialogOpen(false);
21
22
  onClose === null || onClose === void 0 ? void 0 : onClose(gesture);
22
23
  }
23
24
  };
@@ -6,6 +6,7 @@ import { DaySelection } from './useDatePicker';
6
6
  export declare type DayProps = {
7
7
  blocked?: boolean;
8
8
  disabled?: boolean;
9
+ focused?: boolean;
9
10
  onAction?: (date: Date, event?: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>) => void;
10
11
  selected?: DaySelection;
11
12
  date: Date;
@@ -71,15 +71,12 @@ const getStateStyles = (props, prop, state) => {
71
71
  const {
72
72
  blocked,
73
73
  disabled,
74
+ focused,
74
75
  selected,
75
76
  today
76
77
  } = props;
77
78
 
78
- if (blocked) {
79
- return states.blocked[prop];
80
- } else if (disabled) {
81
- return states.disabled[prop];
82
- } else if (selected) {
79
+ if (selected) {
83
80
  switch (selected) {
84
81
  case 'start':
85
82
  return today && prop === 'color' ? states.selected.start['todayColor'] : states.selected.start[prop];
@@ -93,6 +90,12 @@ const getStateStyles = (props, prop, state) => {
93
90
  default:
94
91
  return today && prop === 'color' ? states.selected.default['todayColor'] : states.selected.default[prop];
95
92
  }
93
+ } else if (blocked) {
94
+ return states.blocked[prop];
95
+ } else if (disabled) {
96
+ return states.disabled[prop];
97
+ } else if (focused) {
98
+ return states.default.hover[prop];
96
99
  } else {
97
100
  return today && prop === 'color' ? states.default[state]['todayColor'] : states.default[state][prop];
98
101
  }
@@ -109,7 +112,7 @@ const DayComponent = styled(DayBaseComponent).attrs(props => ({
109
112
  })).withConfig({
110
113
  displayName: "Day__DayComponent",
111
114
  componentId: "sc-1japneh-1"
112
- })(["background-color:", ";border-radius:", ";transition:0.1s background-color ease;& ", "{align-self:center;color:", ";display:flex;font-family:", ";font-size:", ";justify-self:center;user-select:none;transition:0.1s color ease;}&:hover{background-color:", ";cursor:pointer;transition:0.05s background-color ease;& ", "{color:", ";transition:0.1s color ease;}}&:active{background-color:", ";box-shadow:inset ", ";transition:0.1s background-color ease,0.1s box-shadow ease,0.1s color ease;& ", "{color:", ";transition:0.1s color ease;}}"], props => props.background, props => props.borderRadius, Text, props => props.textColor, get('fonts.mono'), get('fontSizes.0'), props => props.backgroundHover, Text, props => props.textColorHover, props => props.backgroundPressed, get('shadows.shadow.medium'), Text, props => props.textColorPressed);
115
+ })(["background-color:", ";border-radius:", ";opacity:", ";transition:0.1s background-color ease;& ", "{align-self:center;color:", ";display:flex;font-family:", ";font-size:", ";justify-self:center;user-select:none;transition:0.1s color ease;}&:hover{background-color:", ";cursor:pointer;transition:0.05s background-color ease;& ", "{color:", ";transition:0.1s color ease;}}&:active{background-color:", ";box-shadow:inset ", ";transition:0.1s background-color ease,0.1s box-shadow ease,0.1s color ease;& ", "{color:", ";transition:0.1s color ease;}}"], props => props.background, props => props.borderRadius, props => props.disabled ? 0.5 : 1, Text, props => props.textColor, get('fonts.mono'), get('fontSizes.0'), props => props.backgroundHover, Text, props => props.textColorHover, props => props.backgroundPressed, get('shadows.shadow.medium'), Text, props => props.textColorPressed);
113
116
  export const Day = ({
114
117
  date,
115
118
  onAction
@@ -119,6 +122,7 @@ export const Day = ({
119
122
  onSelection,
120
123
  disabled,
121
124
  blocked,
125
+ focused,
122
126
  selected,
123
127
  today
124
128
  } = useDatePicker(date);
@@ -152,6 +156,7 @@ export const Day = ({
152
156
  "aria-selected": selected !== false,
153
157
  blocked: blocked,
154
158
  disabled: disabled,
159
+ focused: focused,
155
160
  selected: selected,
156
161
  today: today,
157
162
  onClick: clickHandler,
@@ -33,6 +33,8 @@ export interface DatePickerContext {
33
33
  disabled?: boolean;
34
34
  configuration: DatePickerConfiguration;
35
35
  currentViewingDate: Date;
36
+ dialogOpen: boolean;
37
+ focusDate: Date;
36
38
  goToMonth: (date: Date) => void;
37
39
  hoverRange?: RangeSelection | null;
38
40
  selection?: Selection;
@@ -47,6 +49,7 @@ export interface DatePickerContext {
47
49
  previousMonth: () => void;
48
50
  revertValue: () => void;
49
51
  saveValue: (selection?: Selection) => void;
52
+ setDialogOpen: React.Dispatch<React.SetStateAction<boolean>>;
50
53
  }
51
54
  export declare type Selection = Date | Array<Date> | RangeSelection | null;
52
55
  export declare type StringSelection = string | Array<string> | {
@@ -56,11 +59,14 @@ export declare type StringSelection = string | Array<string> | {
56
59
  export declare type DaySelection = boolean | 'start' | 'middle' | 'end';
57
60
  declare const useDatePicker: (date?: Date | undefined) => {
58
61
  blocked: boolean | undefined;
59
- disabled: boolean;
62
+ disabled: boolean | undefined;
63
+ focused: boolean;
60
64
  selected: DaySelection;
61
65
  today: boolean;
62
66
  configuration: DatePickerConfiguration;
63
67
  currentViewingDate: Date;
68
+ dialogOpen: boolean;
69
+ focusDate: Date;
64
70
  goToMonth: (date: Date) => void;
65
71
  hoverRange?: RangeSelection | null | undefined;
66
72
  selection?: Selection | undefined;
@@ -75,11 +81,13 @@ declare const useDatePicker: (date?: Date | undefined) => {
75
81
  previousMonth: () => void;
76
82
  revertValue: () => void;
77
83
  saveValue: (selection?: Selection | undefined) => void;
84
+ setDialogOpen: React.Dispatch<React.SetStateAction<boolean>>;
78
85
  };
79
86
  export default useDatePicker;
80
87
  export interface DatePickerProviderProps {
81
88
  closePicker?: () => void;
82
89
  configuration?: DatePickerConfiguration;
90
+ isOpen?: boolean;
83
91
  value?: Selection | StringSelection;
84
92
  }
85
93
  export declare function isSingleSelection(selection: Selection): selection is Date;
@@ -1,5 +1,6 @@
1
1
  import { CheckIcon, TrashIcon } from '@primer/octicons-react';
2
- import { isEqual, isAfter, isBefore, addMonths, subMonths, isToday, isWeekend, differenceInDays, addDays, subDays } from 'date-fns';
2
+ import { isEqual, isAfter, isBefore, addMonths, subMonths, isToday, isWeekend, differenceInDays, addDays, subDays, addWeeks, subWeeks } from 'date-fns';
3
+ import { previousFriday } from 'date-fns/esm';
3
4
  import deepmerge from 'deepmerge';
4
5
  import React, { createContext, useCallback, useContext, useMemo, useEffect, useState } from 'react';
5
6
  import { Text, useConfirm } from '..';
@@ -7,65 +8,69 @@ import { formatDate } from './dateParser';
7
8
  const DatePickerContext = /*#__PURE__*/createContext(null);
8
9
 
9
10
  const useDatePicker = date => {
10
- const value = useContext(DatePickerContext);
11
+ const dateCtx = useContext(DatePickerContext);
11
12
  const [selected, setSelected] = useState(false);
12
13
  const today = date ? isToday(date) : false;
13
14
 
14
- if (!value) {
15
+ if (!dateCtx) {
15
16
  throw new Error('useDatePicker must be used inside a DatePickerProvider');
16
17
  }
17
18
 
18
19
  useEffect(() => {
19
20
  if (date) {
20
- if (value.hoverRange) {
21
- if (isRangeSelection(value.hoverRange)) {
22
- if (isEqual(date, value.hoverRange.from)) {
21
+ if (dateCtx.hoverRange) {
22
+ if (isRangeSelection(dateCtx.hoverRange)) {
23
+ if (isEqual(date, dateCtx.hoverRange.from)) {
23
24
  setSelected('start');
24
- } else if (value.hoverRange.to && isEqual(date, value.hoverRange.to)) {
25
+ } else if (dateCtx.hoverRange.to && isEqual(date, dateCtx.hoverRange.to)) {
25
26
  setSelected('end');
26
- } else if (isAfter(date, value.hoverRange.from) && value.hoverRange.to && isBefore(date, value.hoverRange.to)) {
27
+ } else if (isAfter(date, dateCtx.hoverRange.from) && dateCtx.hoverRange.to && isBefore(date, dateCtx.hoverRange.to)) {
27
28
  setSelected('middle');
28
29
  } else {
29
30
  setSelected(false);
30
31
  }
31
32
  }
32
- } else if (value.selection) {
33
- if (isMultiSelection(value.selection)) {
34
- setSelected(!!value.selection.find(d => isEqual(d, date)));
35
- } else if (isRangeSelection(value.selection)) {
36
- if (isEqual(date, value.selection.from)) {
33
+ } else if (dateCtx.selection) {
34
+ if (isMultiSelection(dateCtx.selection)) {
35
+ setSelected(!!dateCtx.selection.find(d => isEqual(d, date)));
36
+ } else if (isRangeSelection(dateCtx.selection)) {
37
+ if (isEqual(date, dateCtx.selection.from)) {
37
38
  setSelected('start');
38
- } else if (value.selection.to && isEqual(date, value.selection.to)) {
39
+ } else if (dateCtx.selection.to && isEqual(date, dateCtx.selection.to)) {
39
40
  setSelected('end');
40
- } else if (isAfter(date, value.selection.from) && value.selection.to && isBefore(date, value.selection.to)) {
41
+ } else if (isAfter(date, dateCtx.selection.from) && dateCtx.selection.to && isBefore(date, dateCtx.selection.to)) {
41
42
  setSelected('middle');
42
43
  } else {
43
44
  setSelected(false);
44
45
  }
45
46
  } else {
46
- setSelected(isEqual(date, value.selection));
47
+ setSelected(isEqual(date, dateCtx.selection));
47
48
  }
48
49
  }
49
50
  }
50
- }, [date, value.hoverRange, value.selection, today]);
51
+ }, [date, dateCtx.hoverRange, dateCtx.selection, today]);
51
52
  let blocked,
52
- disabled = false;
53
+ disabled,
54
+ focused = false;
53
55
 
54
56
  if (date) {
55
- // Determine if date is blocked out
56
- if (value.configuration.blockedDates) {
57
- blocked = !!value.configuration.blockedDates.find(d => isEqual(d, date));
57
+ // Determine if date is focused
58
+ if (isEqual(dateCtx.focusDate, date)) focused = true; // Determine if date is blocked out
59
+
60
+ if (dateCtx.configuration.blockedDates) {
61
+ blocked = !!dateCtx.configuration.blockedDates.find(d => isEqual(d, date));
58
62
  } // Determine if date is disabled
59
63
 
60
64
 
61
- if (value.configuration.minDate || value.configuration.maxDate || value.configuration.disableWeekends) {
62
- disabled = (value.configuration.minDate ? isBefore(date, value.configuration.minDate) : false) || (value.configuration.maxDate ? isAfter(date, value.configuration.maxDate) : false) || (value.configuration.disableWeekends ? isWeekend(date) : false);
65
+ if (dateCtx.configuration.minDate || dateCtx.configuration.maxDate || dateCtx.configuration.disableWeekends) {
66
+ disabled = (dateCtx.configuration.minDate ? isBefore(date, dateCtx.configuration.minDate) : false) || (dateCtx.configuration.maxDate ? isAfter(date, dateCtx.configuration.maxDate) : false) || (dateCtx.configuration.disableWeekends ? isWeekend(date) : false);
63
67
  }
64
68
  }
65
69
 
66
- return { ...value,
70
+ return { ...dateCtx,
67
71
  blocked,
68
72
  disabled,
73
+ focused,
69
74
  selected,
70
75
  today
71
76
  };
@@ -139,6 +144,20 @@ function parseSelection(selection, variant) {
139
144
  }
140
145
  }
141
146
 
147
+ const getInitialFocusDate = selection => {
148
+ if (!selection) return new Date();
149
+
150
+ if (selection instanceof Date) {
151
+ return selection;
152
+ } else if (Array.isArray(selection)) {
153
+ return selection[0];
154
+ } else if (isRangeSelection(selection)) {
155
+ return selection.from;
156
+ } else {
157
+ return new Date();
158
+ }
159
+ };
160
+
142
161
  const defaultConfiguration = {
143
162
  anchorVariant: 'button',
144
163
  confirmation: false,
@@ -155,6 +174,7 @@ export const DatePickerProvider = ({
155
174
  configuration: externalConfig = {},
156
175
  children,
157
176
  closePicker,
177
+ isOpen = false,
158
178
  value
159
179
  }) => {
160
180
  const [configuration, setConfiguration] = useState(deepmerge(defaultConfiguration, externalConfig));
@@ -164,6 +184,8 @@ export const DatePickerProvider = ({
164
184
  const [hoverRange, setHoverRange] = useState(null);
165
185
  const [currentViewingDate, setCurrentViewingDate] = useState(new Date());
166
186
  const confirm = useConfirm();
187
+ const [dialogOpen, setDialogOpen] = useState(false);
188
+ const [focusDate, setFocusDate] = useState(getInitialFocusDate(selection));
167
189
  useEffect(() => {
168
190
  setConfiguration(deepmerge(defaultConfiguration, externalConfig));
169
191
  setSelection(parseSelection(selection, configuration.variant)); // Don't want this to run every time selection gets updated
@@ -301,6 +323,67 @@ export const DatePickerProvider = ({
301
323
  }
302
324
  }
303
325
  }, [configuration]);
326
+ const handleKeyDown = useCallback(async e => {
327
+ const key = e.key;
328
+
329
+ switch (key) {
330
+ case 'ArrowRight':
331
+ {
332
+ // Increase selection by 1 day
333
+ setFocusDate(addDays(focusDate, 1));
334
+ break;
335
+ }
336
+
337
+ case 'ArrowLeft':
338
+ {
339
+ // Decrease selection by 1 day
340
+ setFocusDate(subDays(focusDate, 1));
341
+ break;
342
+ }
343
+
344
+ case 'ArrowUp':
345
+ {
346
+ // Decrease selection by 1 week
347
+ setFocusDate(subWeeks(focusDate, 1));
348
+ break;
349
+ }
350
+
351
+ case 'ArrowDown':
352
+ {
353
+ // Decrease selection by 1 week
354
+ setFocusDate(addWeeks(focusDate, 1));
355
+ break;
356
+ }
357
+
358
+ case 'Enter':
359
+ {
360
+ // Start Selection
361
+ break;
362
+ }
363
+
364
+ case 'Esc':
365
+ {
366
+ // Cancel Selection if started, reset if not? or close
367
+ break;
368
+ }
369
+
370
+ default:
371
+ {
372
+ break;
373
+ }
374
+ }
375
+ }, [focusDate]);
376
+ useEffect(() => {
377
+ if (isOpen) {
378
+ window.addEventListener('keydown', handleKeyDown);
379
+ } else {
380
+ window.removeEventListener('keydown', handleKeyDown);
381
+ }
382
+
383
+ return () => {
384
+ window.removeEventListener('keydown', handleKeyDown);
385
+ };
386
+ }, [handleKeyDown, isOpen]);
304
387
  const selectionHandler = useCallback(date => {
305
388
  setIsDirty(true);
306
389
 
@@ -360,6 +443,7 @@ export const DatePickerProvider = ({
360
443
  minDate,
361
444
  maxDate,
362
445
  maxRangeSize,
446
+ disableWeekends,
363
447
  variant
364
448
  } = configuration;
365
449
 
@@ -372,6 +456,10 @@ export const DatePickerProvider = ({
372
456
  hoverDate = isBefore(hoverDate, selection.from) ? subDays(selection.from, configuration.maxRangeSize - 1) : addDays(selection.from, configuration.maxRangeSize - 1);
373
457
  }
374
458
 
459
+ if (disableWeekends && isWeekend(hoverDate)) {
460
+ hoverDate = previousFriday(hoverDate);
461
+ }
462
+
375
463
  setHoverRange(isBefore(hoverDate, selection.from) ? {
376
464
  from: hoverDate,
377
465
  to: selection.from
@@ -386,9 +474,11 @@ export const DatePickerProvider = ({
386
474
  configuration,
387
475
  currentViewingDate,
388
476
  disabled: false,
477
+ focusDate,
389
478
  formattedDate: getFormattedDate,
390
479
  goToMonth,
391
480
  hoverRange,
481
+ dialogOpen,
392
482
  nextMonth,
393
483
  onClose: handleClose,
394
484
  onDateInput: inputHandler,
@@ -398,9 +488,10 @@ export const DatePickerProvider = ({
398
488
  revertValue,
399
489
  saveValue,
400
490
  selectionActive: false,
401
- selection
491
+ selection,
492
+ setDialogOpen
402
493
  };
403
- }, [configuration, currentViewingDate, focusHnadler, getFormattedDate, goToMonth, handleClose, hoverRange, inputHandler, nextMonth, previousMonth, revertValue, saveValue, selection, selectionHandler]);
494
+ }, [configuration, currentViewingDate, dialogOpen, focusDate, focusHnadler, getFormattedDate, goToMonth, handleClose, hoverRange, inputHandler, nextMonth, previousMonth, revertValue, saveValue, selection, selectionHandler]);
404
495
  return /*#__PURE__*/React.createElement(DatePickerContext.Provider, {
405
496
  value: datePickerCtx
406
497
  }, children);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primer/components",
3
- "version": "0.0.0-2021931194230",
3
+ "version": "0.0.0-202193121134",
4
4
  "description": "Primer react components",
5
5
  "main": "lib/index.js",
6
6
  "module": "lib-esm/index.js",