@gravity-ui/date-components 1.0.0 → 1.1.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 (55) hide show
  1. package/dist/cjs/components/Calendar/Calendar.css +9 -1
  2. package/dist/cjs/components/Calendar/CalendarBase.js +9 -9
  3. package/dist/cjs/components/Calendar/hooks/types.d.ts +5 -2
  4. package/dist/cjs/components/Calendar/hooks/useCalendarCellProps.js +21 -16
  5. package/dist/cjs/components/Calendar/hooks/useCalendarGridProps.js +2 -7
  6. package/dist/cjs/components/Calendar/hooks/useCalendarProps.js +6 -3
  7. package/dist/cjs/components/Calendar/hooks/useCalendarState.js +69 -26
  8. package/dist/cjs/components/Calendar/hooks/useRangeCalendarState.js +6 -6
  9. package/dist/cjs/components/Calendar/utils.d.ts +3 -1
  10. package/dist/cjs/components/Calendar/utils.js +7 -1
  11. package/dist/cjs/components/DateField/hooks/useDateFieldProps.js +2 -2
  12. package/dist/cjs/components/DateField/hooks/useDateFieldState.d.ts +2 -0
  13. package/dist/cjs/components/DateField/hooks/useDateFieldState.js +33 -6
  14. package/dist/cjs/components/DateField/types.d.ts +1 -1
  15. package/dist/cjs/components/DateField/utils.d.ts +0 -9
  16. package/dist/cjs/components/DateField/utils.js +1 -25
  17. package/dist/cjs/components/DatePicker/DatePicker.d.ts +4 -1
  18. package/dist/cjs/components/DatePicker/DatePicker.js +2 -2
  19. package/dist/cjs/components/DatePicker/DesktopCalendar.d.ts +3 -1
  20. package/dist/cjs/components/DatePicker/DesktopCalendar.js +17 -8
  21. package/dist/cjs/components/DatePicker/MobileCalendar.js +4 -4
  22. package/dist/cjs/components/DatePicker/hooks/useDatePickerState.js +3 -8
  23. package/dist/cjs/components/RelativeDatePicker/RelativeDatePicker.d.ts +3 -0
  24. package/dist/cjs/components/RelativeDatePicker/RelativeDatePicker.js +1 -1
  25. package/dist/cjs/components/RelativeDatePicker/hooks/useRelativeDatePickerProps.js +7 -1
  26. package/dist/cjs/components/utils/dates.d.ts +9 -0
  27. package/dist/cjs/components/utils/dates.js +28 -0
  28. package/dist/esm/components/Calendar/Calendar.css +9 -1
  29. package/dist/esm/components/Calendar/CalendarBase.js +10 -10
  30. package/dist/esm/components/Calendar/hooks/types.d.ts +5 -2
  31. package/dist/esm/components/Calendar/hooks/useCalendarCellProps.js +21 -16
  32. package/dist/esm/components/Calendar/hooks/useCalendarGridProps.js +2 -7
  33. package/dist/esm/components/Calendar/hooks/useCalendarProps.js +6 -3
  34. package/dist/esm/components/Calendar/hooks/useCalendarState.js +70 -27
  35. package/dist/esm/components/Calendar/hooks/useRangeCalendarState.js +6 -6
  36. package/dist/esm/components/Calendar/utils.d.ts +3 -1
  37. package/dist/esm/components/Calendar/utils.js +6 -0
  38. package/dist/esm/components/DateField/hooks/useDateFieldProps.js +2 -2
  39. package/dist/esm/components/DateField/hooks/useDateFieldState.d.ts +2 -0
  40. package/dist/esm/components/DateField/hooks/useDateFieldState.js +28 -1
  41. package/dist/esm/components/DateField/types.d.ts +1 -1
  42. package/dist/esm/components/DateField/utils.d.ts +0 -9
  43. package/dist/esm/components/DateField/utils.js +0 -21
  44. package/dist/esm/components/DatePicker/DatePicker.d.ts +4 -1
  45. package/dist/esm/components/DatePicker/DatePicker.js +2 -2
  46. package/dist/esm/components/DatePicker/DesktopCalendar.d.ts +3 -1
  47. package/dist/esm/components/DatePicker/DesktopCalendar.js +17 -8
  48. package/dist/esm/components/DatePicker/MobileCalendar.js +1 -1
  49. package/dist/esm/components/DatePicker/hooks/useDatePickerState.js +2 -7
  50. package/dist/esm/components/RelativeDatePicker/RelativeDatePicker.d.ts +3 -0
  51. package/dist/esm/components/RelativeDatePicker/RelativeDatePicker.js +1 -1
  52. package/dist/esm/components/RelativeDatePicker/hooks/useRelativeDatePickerProps.js +7 -1
  53. package/dist/esm/components/utils/dates.d.ts +9 -0
  54. package/dist/esm/components/utils/dates.js +22 -0
  55. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.mergeDateTime = exports.isInvalid = exports.createPlaceholderValue = exports.splitFormatIntoSections = exports.expandFormat = exports.CtrlCmd = exports.isMac = void 0;
3
+ exports.splitFormatIntoSections = exports.expandFormat = exports.CtrlCmd = exports.isMac = void 0;
4
4
  const date_utils_1 = require("@gravity-ui/date-utils");
5
5
  const i18n_1 = require("./i18n");
6
6
  exports.isMac = window.navigator.userAgent.indexOf('Macintosh') >= 0;
@@ -250,27 +250,3 @@ function getSectionOptions(section, token) {
250
250
  }
251
251
  return undefined;
252
252
  }
253
- function createPlaceholderValue({ placeholderValue, timeZone }) {
254
- return (placeholderValue !== null && placeholderValue !== void 0 ? placeholderValue : (0, date_utils_1.dateTime)({ timeZone }).set('hour', 0).set('minute', 0).set('second', 0));
255
- }
256
- exports.createPlaceholderValue = createPlaceholderValue;
257
- function isInvalid(value, minValue, maxValue) {
258
- if (!value) {
259
- return false;
260
- }
261
- if (minValue && value.isBefore(minValue)) {
262
- return true;
263
- }
264
- if (maxValue && maxValue.isBefore(value)) {
265
- return true;
266
- }
267
- return false;
268
- }
269
- exports.isInvalid = isInvalid;
270
- function mergeDateTime(date, time) {
271
- return date
272
- .set('hours', time.hour())
273
- .set('minutes', time.minute())
274
- .set('seconds', time.second());
275
- }
276
- exports.mergeDateTime = mergeDateTime;
@@ -1,5 +1,8 @@
1
+ import React from 'react';
2
+ import type { CalendarProps } from '../Calendar';
1
3
  import type { AccessibilityProps, DateFieldBase, DomProps, FocusableProps, KeyboardEvents, StyleProps, TextInputProps } from '../types';
2
4
  import './DatePicker.css';
3
5
  export interface DatePickerProps extends DateFieldBase, TextInputProps, FocusableProps, KeyboardEvents, DomProps, StyleProps, AccessibilityProps {
6
+ children?: (props: CalendarProps) => React.ReactNode;
4
7
  }
5
- export declare function DatePicker({ value, defaultValue, onUpdate, className, onFocus, onBlur, ...props }: DatePickerProps): import("react/jsx-runtime").JSX.Element;
8
+ export declare function DatePicker({ value, defaultValue, onUpdate, className, onFocus, onBlur, children, ...props }: DatePickerProps): import("react/jsx-runtime").JSX.Element;
@@ -25,7 +25,7 @@ const useDatePickerState_1 = require("./hooks/useDatePickerState");
25
25
  const utils_1 = require("./utils");
26
26
  require("./DatePicker.css");
27
27
  function DatePicker(_a) {
28
- var { value, defaultValue, onUpdate, className, onFocus, onBlur } = _a, props = __rest(_a, ["value", "defaultValue", "onUpdate", "className", "onFocus", "onBlur"]);
28
+ var { value, defaultValue, onUpdate, className, onFocus, onBlur, children } = _a, props = __rest(_a, ["value", "defaultValue", "onUpdate", "className", "onFocus", "onBlur", "children"]);
29
29
  const anchorRef = react_1.default.useRef(null);
30
30
  const state = (0, useDatePickerState_1.useDatePickerState)(Object.assign(Object.assign({}, props), { value,
31
31
  defaultValue,
@@ -61,6 +61,6 @@ function DatePicker(_a) {
61
61
  e.stopPropagation();
62
62
  state.setOpen(true);
63
63
  }
64
- }, children: [isMobile ? ((0, jsx_runtime_1.jsx)(MobileCalendar_1.MobileCalendar, { props: props, state: state })) : ((0, jsx_runtime_1.jsx)(DesktopCalendar_1.DesktopCalendar, { anchorRef: anchorRef, props: props, state: state })), (0, jsx_runtime_1.jsx)(uikit_1.TextInput, Object.assign({}, inputProps, { value: fieldState.isEmpty && !isActive && props.placeholder ? '' : inputProps.value, className: (0, utils_1.b)('field', { mobile: isMobile }), hasClear: !isMobile && inputProps.hasClear, rightContent: isMobile ? ((0, jsx_runtime_1.jsx)(MobileCalendar_1.MobileCalendarIcon, { props: props, state: state })) : ((0, jsx_runtime_1.jsx)(DesktopCalendar_1.DesktopCalendarButton, { props: props, state: state })) }))] })));
64
+ }, children: [isMobile ? ((0, jsx_runtime_1.jsx)(MobileCalendar_1.MobileCalendar, { props: props, state: state })) : ((0, jsx_runtime_1.jsx)(DesktopCalendar_1.DesktopCalendar, { anchorRef: anchorRef, props: props, state: state, renderCalendar: children })), (0, jsx_runtime_1.jsx)(uikit_1.TextInput, Object.assign({}, inputProps, { value: fieldState.isEmpty && !isActive && props.placeholder ? '' : inputProps.value, className: (0, utils_1.b)('field', { mobile: isMobile }), hasClear: !isMobile && inputProps.hasClear, rightContent: isMobile ? ((0, jsx_runtime_1.jsx)(MobileCalendar_1.MobileCalendarIcon, { props: props, state: state })) : ((0, jsx_runtime_1.jsx)(DesktopCalendar_1.DesktopCalendarButton, { props: props, state: state })) }))] })));
65
65
  }
66
66
  exports.DatePicker = DatePicker;
@@ -1,12 +1,14 @@
1
1
  import React from 'react';
2
+ import { type CalendarProps } from '../Calendar';
2
3
  import type { DatePickerProps } from './DatePicker';
3
4
  import type { DatePickerState } from './hooks/useDatePickerState';
4
5
  interface DesktopCalendarProps {
5
6
  anchorRef: React.RefObject<HTMLElement>;
6
7
  props: DatePickerProps;
7
8
  state: DatePickerState;
9
+ renderCalendar?: (props: CalendarProps) => React.ReactNode;
8
10
  }
9
- export declare function DesktopCalendar({ anchorRef, props, state }: DesktopCalendarProps): import("react/jsx-runtime").JSX.Element | null;
11
+ export declare function DesktopCalendar({ anchorRef, props, state, renderCalendar }: DesktopCalendarProps): import("react/jsx-runtime").JSX.Element | null;
10
12
  interface DesktopCalendarButtonProps {
11
13
  props: DatePickerProps;
12
14
  state: DatePickerState;
@@ -13,7 +13,7 @@ const DateField_1 = require("../DateField");
13
13
  const getButtonSizeForInput_1 = require("../utils/getButtonSizeForInput");
14
14
  const i18n_1 = require("./i18n");
15
15
  const utils_1 = require("./utils");
16
- function DesktopCalendar({ anchorRef, props, state }) {
16
+ function DesktopCalendar({ anchorRef, props, state, renderCalendar }) {
17
17
  var _a;
18
18
  const { focusWithinProps } = (0, uikit_1.useFocusWithin)({
19
19
  isDisabled: !state.isOpen,
@@ -24,15 +24,24 @@ function DesktopCalendar({ anchorRef, props, state }) {
24
24
  if (!state.hasDate) {
25
25
  return null;
26
26
  }
27
+ const calendarProps = {
28
+ autoFocus: true,
29
+ size: props.size === 's' ? 'm' : props.size,
30
+ disabled: props.disabled,
31
+ readOnly: props.readOnly,
32
+ onUpdate: (d) => {
33
+ state.setDateValue(d);
34
+ },
35
+ defaultFocusedValue: (_a = state.dateValue) !== null && _a !== void 0 ? _a : undefined,
36
+ value: state.dateValue,
37
+ minValue: props.minValue,
38
+ maxValue: props.maxValue,
39
+ isDateUnavailable: props.isDateUnavailable,
40
+ timeZone: props.timeZone,
41
+ };
27
42
  return ((0, jsx_runtime_1.jsx)(uikit_1.Popup, { open: state.isOpen, anchorRef: anchorRef, onClose: () => {
28
43
  state.setOpen(false);
29
- }, restoreFocus: true, children: (0, jsx_runtime_1.jsxs)("div", Object.assign({}, focusWithinProps, { className: (0, utils_1.b)('popup-content'), children: [(0, jsx_runtime_1.jsx)(Calendar_1.Calendar
30
- // eslint-disable-next-line jsx-a11y/no-autofocus
31
- , {
32
- // eslint-disable-next-line jsx-a11y/no-autofocus
33
- autoFocus: true, size: props.size === 's' ? 'm' : props.size, disabled: props.disabled, readOnly: props.readOnly, onUpdate: (d) => {
34
- state.setDateValue(d);
35
- }, defaultFocusedValue: (_a = state.dateValue) !== null && _a !== void 0 ? _a : undefined, value: state.dateValue, minValue: props.minValue, maxValue: props.maxValue, isDateUnavailable: props.isDateUnavailable, timeZone: props.timeZone }), state.hasTime && ((0, jsx_runtime_1.jsx)("div", { className: (0, utils_1.b)('time-field-wrapper'), children: (0, jsx_runtime_1.jsx)(DateField_1.DateField, { className: (0, utils_1.b)('time-field'), size: props.size, readOnly: props.readOnly, value: state.timeValue, onUpdate: state.setTimeValue, placeholderValue: props.placeholderValue, minValue: props.minValue, maxValue: props.maxValue, timeZone: props.timeZone, format: state.timeFormat }) }))] })) }));
44
+ }, restoreFocus: true, children: (0, jsx_runtime_1.jsxs)("div", Object.assign({}, focusWithinProps, { className: (0, utils_1.b)('popup-content'), tabIndex: -1, children: [typeof renderCalendar === 'function' ? (renderCalendar(calendarProps)) : ((0, jsx_runtime_1.jsx)(Calendar_1.Calendar, Object.assign({}, calendarProps))), state.hasTime && ((0, jsx_runtime_1.jsx)("div", { className: (0, utils_1.b)('time-field-wrapper'), children: (0, jsx_runtime_1.jsx)(DateField_1.DateField, { className: (0, utils_1.b)('time-field'), size: props.size, readOnly: props.readOnly, value: state.timeValue, onUpdate: state.setTimeValue, placeholderValue: props.placeholderValue, minValue: props.minValue, maxValue: props.maxValue, timeZone: props.timeZone, format: state.timeFormat }) }))] })) }));
36
45
  }
37
46
  exports.DesktopCalendar = DesktopCalendar;
38
47
  function DesktopCalendarButton({ props, state }) {
@@ -6,7 +6,7 @@ const date_utils_1 = require("@gravity-ui/date-utils");
6
6
  const icons_1 = require("@gravity-ui/icons");
7
7
  const uikit_1 = require("@gravity-ui/uikit");
8
8
  const cn_1 = require("../../utils/cn");
9
- const utils_1 = require("../DateField/utils");
9
+ const dates_1 = require("../utils/dates");
10
10
  const getButtonSizeForInput_1 = require("../utils/getButtonSizeForInput");
11
11
  require("./MobileCalendar.css");
12
12
  const b = (0, cn_1.block)('mobile-calendar');
@@ -31,15 +31,15 @@ function MobileCalendar({ props, state }) {
31
31
  format: getDateFormat(type),
32
32
  timeZone: props.timeZone,
33
33
  })
34
- : (0, utils_1.createPlaceholderValue)({
34
+ : (0, dates_1.createPlaceholderValue)({
35
35
  placeholderValue: props.placeholderValue,
36
36
  timeZone: props.timeZone,
37
37
  });
38
38
  if (state.hasTime) {
39
- newDate = (0, utils_1.mergeDateTime)(newDate, localDate);
39
+ newDate = (0, dates_1.mergeDateTime)(newDate, localDate);
40
40
  }
41
41
  else if (state.dateValue) {
42
- newDate = (0, utils_1.mergeDateTime)(newDate, state.dateValue);
42
+ newDate = (0, dates_1.mergeDateTime)(newDate, state.dateValue);
43
43
  }
44
44
  else {
45
45
  newDate = newDate.set('hours', 0).set('minutes', 0).set('seconds', 0);
@@ -7,6 +7,7 @@ exports.useDatePickerState = void 0;
7
7
  const react_1 = __importDefault(require("react"));
8
8
  const utils_1 = require("../../DateField/utils");
9
9
  const useControlledState_1 = require("../../hooks/useControlledState");
10
+ const dates_1 = require("../../utils/dates");
10
11
  function useDatePickerState(props) {
11
12
  const { disabled, readOnly } = props;
12
13
  const [isOpen, setOpen] = react_1.default.useState(false);
@@ -44,7 +45,7 @@ function useDatePickerState(props) {
44
45
  if (disabled || readOnly) {
45
46
  return;
46
47
  }
47
- setValue(mergeDateTime(date, time));
48
+ setValue((0, dates_1.mergeDateTime)(date, time));
48
49
  setSelectedDate(null);
49
50
  setSelectedTime(null);
50
51
  };
@@ -103,12 +104,6 @@ function useDatePickerState(props) {
103
104
  };
104
105
  }
105
106
  exports.useDatePickerState = useDatePickerState;
106
- function mergeDateTime(date, time) {
107
- return date
108
- .set('hours', time.hour())
109
- .set('minutes', time.minute())
110
- .set('seconds', time.second());
111
- }
112
107
  function getPlaceholderTime(placeholderValue, timeZone) {
113
- return (0, utils_1.createPlaceholderValue)({ placeholderValue, timeZone });
108
+ return (0, dates_1.createPlaceholderValue)({ placeholderValue, timeZone });
114
109
  }
@@ -1,6 +1,9 @@
1
+ import React from 'react';
2
+ import type { CalendarProps } from '../Calendar';
1
3
  import type { AccessibilityProps, DomProps, FocusableProps, KeyboardEvents, StyleProps, TextInputProps } from '../types';
2
4
  import type { RelativeDatePickerStateOptions } from './hooks/useRelativeDatePickerState';
3
5
  import './RelativeDatePicker.css';
4
6
  export interface RelativeDatePickerProps extends RelativeDatePickerStateOptions, TextInputProps, FocusableProps, KeyboardEvents, DomProps, StyleProps, AccessibilityProps {
7
+ children?: (props: CalendarProps) => React.ReactNode;
5
8
  }
6
9
  export declare function RelativeDatePicker(props: RelativeDatePickerProps): import("react/jsx-runtime").JSX.Element;
@@ -27,6 +27,6 @@ function RelativeDatePicker(props) {
27
27
  readOnly: props.readOnly,
28
28
  placeholderValue: props.placeholderValue,
29
29
  timeZone: props.timeZone,
30
- } })), (0, jsx_runtime_1.jsx)(uikit_1.TextInput, Object.assign({}, fieldProps, { leftContent: (0, jsx_runtime_1.jsx)(uikit_1.Button, Object.assign({}, modeSwitcherProps, { children: (0, jsx_runtime_1.jsx)(uikit_1.Icon, { data: icons_1.Function }) })), rightContent: (0, jsx_runtime_1.jsxs)(react_1.default.Fragment, { children: [!isMobile && ((0, jsx_runtime_1.jsx)(uikit_1.Button, Object.assign({}, calendarButtonProps, { children: (0, jsx_runtime_1.jsx)(uikit_1.Icon, { data: icons_1.Calendar }) }))), isMobile && state.mode === 'absolute' && ((0, jsx_runtime_1.jsx)(MobileCalendar_1.MobileCalendarIcon, { state: state.datePickerState, props: { size: props.size } }))] }) })), !isMobile && ((0, jsx_runtime_1.jsx)(uikit_1.Popup, Object.assign({}, popupProps, { anchorRef: anchorRef, children: (0, jsx_runtime_1.jsxs)("div", { className: b('popup-content'), children: [(0, jsx_runtime_1.jsx)(Calendar_1.Calendar, Object.assign({}, calendarProps)), state.datePickerState.hasTime && ((0, jsx_runtime_1.jsx)("div", { className: b('time-field-wrapper'), children: (0, jsx_runtime_1.jsx)(DateField_1.DateField, Object.assign({}, timeInputProps)) }))] }) })))] })));
30
+ } })), (0, jsx_runtime_1.jsx)(uikit_1.TextInput, Object.assign({}, fieldProps, { leftContent: (0, jsx_runtime_1.jsx)(uikit_1.Button, Object.assign({}, modeSwitcherProps, { children: (0, jsx_runtime_1.jsx)(uikit_1.Icon, { data: icons_1.Function }) })), rightContent: (0, jsx_runtime_1.jsxs)(react_1.default.Fragment, { children: [!isMobile && ((0, jsx_runtime_1.jsx)(uikit_1.Button, Object.assign({}, calendarButtonProps, { children: (0, jsx_runtime_1.jsx)(uikit_1.Icon, { data: icons_1.Calendar }) }))), isMobile && state.mode === 'absolute' && ((0, jsx_runtime_1.jsx)(MobileCalendar_1.MobileCalendarIcon, { state: state.datePickerState, props: { size: props.size } }))] }) })), !isMobile && ((0, jsx_runtime_1.jsx)(uikit_1.Popup, Object.assign({}, popupProps, { anchorRef: anchorRef, children: (0, jsx_runtime_1.jsxs)("div", { className: b('popup-content'), children: [typeof props.children === 'function' ? (props.children(calendarProps)) : ((0, jsx_runtime_1.jsx)(Calendar_1.Calendar, Object.assign({}, calendarProps))), state.datePickerState.hasTime && ((0, jsx_runtime_1.jsx)("div", { className: b('time-field-wrapper'), children: (0, jsx_runtime_1.jsx)(DateField_1.DateField, Object.assign({}, timeInputProps)) }))] }) })))] })));
31
31
  }
32
32
  exports.RelativeDatePicker = RelativeDatePicker;
@@ -87,7 +87,13 @@ function useRelativeDatePickerProps(state, _a) {
87
87
  const handleRef = (0, uikit_1.useForkRef)(inputRef, mode === 'relative' ? relativeDateProps.controlRef : inputProps.controlRef);
88
88
  const calendarRef = react_1.default.useRef(null);
89
89
  return {
90
- groupProps: Object.assign({ role: 'group' }, focusWithinProps),
90
+ groupProps: Object.assign(Object.assign({ role: 'group' }, focusWithinProps), { onKeyDown: (e) => {
91
+ if (e.altKey && (e.key === 'ArrowDown' || e.key === 'ArrowUp')) {
92
+ e.preventDefault();
93
+ e.stopPropagation();
94
+ setOpen(true);
95
+ }
96
+ } }),
91
97
  fieldProps: (0, mergeProps_1.mergeProps)(commonInputProps, mode === 'relative' ? relativeDateProps : inputProps, { controlRef: handleRef, error }),
92
98
  modeSwitcherProps: {
93
99
  size: (0, getButtonSizeForInput_1.getButtonSizeForInput)(props.size),
@@ -0,0 +1,9 @@
1
+ import type { DateTime } from '@gravity-ui/date-utils';
2
+ interface PlaceholderValueOptions {
3
+ placeholderValue?: DateTime;
4
+ timeZone?: string;
5
+ }
6
+ export declare function createPlaceholderValue({ placeholderValue, timeZone }: PlaceholderValueOptions): DateTime;
7
+ export declare function isInvalid(value: DateTime | null | undefined, minValue: DateTime | undefined, maxValue: DateTime | undefined): boolean;
8
+ export declare function mergeDateTime(date: DateTime, time: DateTime): DateTime;
9
+ export {};
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mergeDateTime = exports.isInvalid = exports.createPlaceholderValue = void 0;
4
+ const date_utils_1 = require("@gravity-ui/date-utils");
5
+ function createPlaceholderValue({ placeholderValue, timeZone }) {
6
+ return (placeholderValue !== null && placeholderValue !== void 0 ? placeholderValue : (0, date_utils_1.dateTime)({ timeZone }).set('hour', 0).set('minute', 0).set('second', 0));
7
+ }
8
+ exports.createPlaceholderValue = createPlaceholderValue;
9
+ function isInvalid(value, minValue, maxValue) {
10
+ if (!value) {
11
+ return false;
12
+ }
13
+ if (minValue && value.isBefore(minValue)) {
14
+ return true;
15
+ }
16
+ if (maxValue && maxValue.isBefore(value)) {
17
+ return true;
18
+ }
19
+ return false;
20
+ }
21
+ exports.isInvalid = isInvalid;
22
+ function mergeDateTime(date, time) {
23
+ return date
24
+ .set('hours', time.hour())
25
+ .set('minutes', time.minute())
26
+ .set('seconds', time.second());
27
+ }
28
+ exports.mergeDateTime = mergeDateTime;
@@ -82,6 +82,11 @@
82
82
  .g-date-calendar__content_animation_zoom-out.g-date-calendar__previous-state {
83
83
  animation: calendar-zoom-out-hiding 0.25s ease forwards;
84
84
  }
85
+ @media screen and (prefers-reduced-motion: reduce), (update: slow) {
86
+ .g-date-calendar__content[class] {
87
+ animation-duration: 0.001ms;
88
+ }
89
+ }
85
90
  @keyframes calendar-forward {
86
91
  from {
87
92
  transform: translateX(0);
@@ -142,10 +147,13 @@
142
147
  display: grid;
143
148
  gap: var(--_--calendar-days-gap);
144
149
  }
145
- .g-date-calendar__grid-rowgroup_mode_months, .g-date-calendar__grid-rowgroup_mode_years {
150
+ .g-date-calendar__grid-rowgroup_mode_months, .g-date-calendar__grid-rowgroup_mode_quarters, .g-date-calendar__grid-rowgroup_mode_years {
146
151
  grid-row: 1/-1;
147
152
  padding: 12px 0 0;
148
153
  }
154
+ .g-date-calendar__grid-rowgroup-header {
155
+ align-self: center;
156
+ }
149
157
  .g-date-calendar__grid-row {
150
158
  display: grid;
151
159
  grid-auto-columns: 1fr;
@@ -5,7 +5,7 @@ import { block } from '../../utils/cn';
5
5
  import { useCalendarCellProps } from './hooks/useCalendarCellProps';
6
6
  import { useCalendarGridProps } from './hooks/useCalendarGridProps';
7
7
  import { useCalendarProps } from './hooks/useCalendarProps';
8
- import { getDaysInPeriod, getWeekDays } from './utils';
8
+ import { calendarLayouts, getDaysInPeriod, getWeekDays } from './utils';
9
9
  import './Calendar.css';
10
10
  const b = block('calendar');
11
11
  export const CalendarBase = React.forwardRef(function Calendar(props, ref) {
@@ -16,7 +16,8 @@ export const CalendarBase = React.forwardRef(function Calendar(props, ref) {
16
16
  state.setFocused(true);
17
17
  },
18
18
  }));
19
- return (_jsxs("div", Object.assign({}, calendarProps, { className: b({ size: props.size }), children: [_jsxs("div", { className: b('header'), children: [_jsx(Button, Object.assign({}, modeButtonProps, { view: "flat", size: props.size, children: state.mode === 'years' ? (_jsx("span", { className: b('mode-label', b(`years-label`)), children: modeButtonProps.children }, "label")) : ([
19
+ return (_jsxs("div", Object.assign({}, calendarProps, { className: b({ size: props.size }), children: [_jsxs("div", { className: b('header'), children: [_jsx(Button, Object.assign({}, modeButtonProps, { view: "flat", size: props.size, children: state.availableModes.indexOf(state.mode) + 1 ===
20
+ state.availableModes.length ? (_jsx("span", { className: b('mode-label', b(`years-label`)), children: modeButtonProps.children }, "label")) : ([
20
21
  _jsx("span", { className: b('mode-label'), children: modeButtonProps.children }, "label"),
21
22
  _jsx(Button.Icon, { children: _jsx(ArrowToggle, { direction: "bottom" }) }, "icon"),
22
23
  ]) })), _jsxs("div", { className: b('controls'), children: [_jsx(Button, Object.assign({}, previousButtonProps, { view: "flat", size: props.size, children: _jsx(Button.Icon, { children: _jsx(ArrowToggle, { direction: "left" }) }) })), _jsx(Button, Object.assign({}, nextButtonProps, { view: "flat", size: props.size, children: _jsx(Button.Icon, { children: _jsx(ArrowToggle, { direction: "right" }) }) }))] })] }), _jsx(CalendarGrid, { state: state })] })));
@@ -27,8 +28,7 @@ function CalendarGrid({ state }) {
27
28
  const startDateChanged = !state.startDate.isSame(prevState.startDate, 'days');
28
29
  let animation;
29
30
  if (modeChanged) {
30
- if ((state.mode === 'days' && prevState.mode === 'months') ||
31
- (state.mode === 'months' && prevState.mode === 'years')) {
31
+ if (calendarLayouts.indexOf(prevState.mode) > calendarLayouts.indexOf(state.mode)) {
32
32
  animation = 'zoom-out';
33
33
  }
34
34
  else {
@@ -59,13 +59,13 @@ function Weekdays({ state }) {
59
59
  }
60
60
  function CalendarGridCells({ state }) {
61
61
  const rowsInPeriod = state.mode === 'days' ? 6 : 4;
62
- const columnsInRow = state.mode === 'days' ? 7 : 3;
62
+ const columnsInRow = state.mode === 'days' ? 7 : 3 + (state.mode === 'quarters' ? 1 : 0);
63
63
  const days = getDaysInPeriod(state.startDate, state.endDate, state.mode);
64
- return (_jsx("div", { className: b('grid-rowgroup', { mode: state.mode }), role: "rowgroup", children: [...new Array(rowsInPeriod).keys()].map((rowIndex) => (_jsx("div", { className: b('grid-row'), role: "row", children: days
65
- .slice(rowIndex * columnsInRow, (rowIndex + 1) * columnsInRow)
66
- .map((date) => {
67
- return _jsx(CalendarCell, { date: date, state: state }, date.unix());
68
- }) }, rowIndex))) }));
64
+ return (_jsx("div", { className: b('grid-rowgroup', { mode: state.mode }), role: "rowgroup", children: [...new Array(rowsInPeriod).keys()].map((rowIndex) => (_jsxs("div", { className: b('grid-row'), role: "row", children: [state.mode === 'quarters' ? (_jsx("span", { role: "rowheader", className: b('grid-rowgroup-header'), children: days[rowIndex * columnsInRow].format('YYYY') })) : null, days
65
+ .slice(rowIndex * columnsInRow, (rowIndex + 1) * columnsInRow)
66
+ .map((date) => {
67
+ return _jsx(CalendarCell, { date: date, state: state }, date.unix());
68
+ })] }, rowIndex))) }));
69
69
  }
70
70
  function CalendarCell({ date, state }) {
71
71
  const { cellProps, buttonProps, formattedDate, isDisabled, isSelected, isRangeSelection, isSelectionStart, isSelectionEnd, isOutsideCurrentRange, isUnavailable, isCurrent, isWeekend, } = useCalendarCellProps(date, state);
@@ -1,6 +1,6 @@
1
1
  import type { DateTime } from '@gravity-ui/date-utils';
2
2
  import type { InputBase, RangeValue } from '../../types';
3
- export type CalendarLayout = 'days' | 'months' | 'years';
3
+ export type CalendarLayout = 'days' | 'months' | 'quarters' | 'years';
4
4
  export interface CalendarStateOptionsBase extends InputBase {
5
5
  /** The minimum allowed date that a user may select. */
6
6
  minValue?: DateTime;
@@ -27,6 +27,8 @@ export interface CalendarStateOptionsBase extends InputBase {
27
27
  defaultMode?: CalendarLayout;
28
28
  /** Handler that is called when the mode changes */
29
29
  onUpdateMode?: (mode: CalendarLayout) => void;
30
+ /** Controls which modes to use */
31
+ modes?: Partial<Record<CalendarLayout, boolean>>;
30
32
  }
31
33
  interface CalendarStateBase {
32
34
  /** Whether the calendar is disabled. */
@@ -45,7 +47,7 @@ interface CalendarStateBase {
45
47
  /** Selects the currently focused date. */
46
48
  selectFocusedDate(): void;
47
49
  /** Selects the given date. */
48
- selectDate(date: DateTime): void;
50
+ selectDate(date: DateTime, force?: boolean): void;
49
51
  /** Moves focus to the next calendar date. */
50
52
  focusNextCell(): void;
51
53
  /** Moves focus to the previous calendar date. */
@@ -98,6 +100,7 @@ interface CalendarStateBase {
98
100
  readonly mode: CalendarLayout;
99
101
  /** Sets calendar layout */
100
102
  setMode(mode: CalendarLayout): void;
103
+ readonly availableModes: CalendarLayout[];
101
104
  readonly startDate: DateTime;
102
105
  readonly endDate: DateTime;
103
106
  }
@@ -21,16 +21,7 @@ export function useCalendarCellProps(date, state) {
21
21
  const isSelectable = !isDisabled && !isUnavailable;
22
22
  const isCurrent = dateTime().isSame(date, state.mode);
23
23
  const isWeekend = state.isWeekend(date);
24
- let label = '';
25
- if (state.mode === 'days') {
26
- label = `${date.format('dddd')}, ${date.format('LL')}`;
27
- }
28
- else if (state.mode === 'months') {
29
- label = `${date.format('MMMM YYYY')}`;
30
- }
31
- else if (state.mode === 'years') {
32
- label = `${date.format('YYYY')}`;
33
- }
24
+ const label = getDateLabel(date, state.mode);
34
25
  const cellProps = {
35
26
  role: 'gridcell',
36
27
  'aria-selected': isSelected ? 'true' : undefined,
@@ -45,12 +36,7 @@ export function useCalendarCellProps(date, state) {
45
36
  onClick: isSelectable
46
37
  ? () => {
47
38
  state.setFocusedDate(date);
48
- if (state.mode === 'days') {
49
- state.selectDate(date);
50
- }
51
- else {
52
- state.zoomIn();
53
- }
39
+ state.selectDate(date);
54
40
  }
55
41
  : undefined,
56
42
  onPointerEnter() {
@@ -71,6 +57,9 @@ export function useCalendarCellProps(date, state) {
71
57
  if (state.mode === 'months') {
72
58
  formattedDate = date.format('MMM');
73
59
  }
60
+ else if (state.mode === 'quarters') {
61
+ formattedDate = date.format('[Q]Q');
62
+ }
74
63
  else if (state.mode === 'years') {
75
64
  formattedDate = date.format('YYYY');
76
65
  }
@@ -89,3 +78,19 @@ export function useCalendarCellProps(date, state) {
89
78
  isWeekend,
90
79
  };
91
80
  }
81
+ function getDateLabel(date, mode) {
82
+ let label = '';
83
+ if (mode === 'days') {
84
+ label = `${date.format('dddd')}, ${date.format('LL')}`;
85
+ }
86
+ else if (mode === 'months') {
87
+ label = `${date.format('MMMM YYYY')}`;
88
+ }
89
+ else if (mode === 'quarters') {
90
+ label = `${date.format('[Q]Q YYYY')}`;
91
+ }
92
+ else if (mode === 'years') {
93
+ label = `${date.format('YYYY')}`;
94
+ }
95
+ return label;
96
+ }
@@ -6,7 +6,7 @@ export function useCalendarGridProps(state) {
6
6
  state.setFocused(isFocused);
7
7
  },
8
8
  });
9
- const gridProps = Object.assign(Object.assign({ role: 'grid', 'aria-label': state.mode === 'years'
9
+ const gridProps = Object.assign(Object.assign({ role: 'grid', 'aria-label': state.mode === 'years' || state.mode === 'quarters'
10
10
  ? `${state.startDate.year()} — ${state.endDate.year()}`
11
11
  : state.focusedDate.format(state.mode === 'days' ? 'MMMM YYYY' : 'YYYY'), 'aria-disabled': state.disabled ? 'true' : undefined, 'aria-readonly': state.readOnly ? 'true' : undefined }, focusWithinProps), { onKeyDown: (e) => {
12
12
  if (e.key === 'ArrowRight') {
@@ -40,12 +40,7 @@ export function useCalendarGridProps(state) {
40
40
  state.zoomIn();
41
41
  }
42
42
  else if (e.key === 'Enter' || e.key === ' ') {
43
- if (state.mode === 'days') {
44
- state.selectFocusedDate();
45
- }
46
- else {
47
- state.setMode(state.mode === 'months' ? 'days' : 'months');
48
- }
43
+ state.selectDate(state.focusedDate);
49
44
  }
50
45
  } });
51
46
  return {
@@ -4,7 +4,7 @@ import { i18n } from '../i18n';
4
4
  const buttonDisabledClassName = 'yc-button_disabled g-button_disabled';
5
5
  // eslint-disable-next-line complexity
6
6
  export function useCalendarProps(props, state) {
7
- const title = state.mode === 'years'
7
+ const title = state.mode === 'years' || state.mode === 'quarters'
8
8
  ? `${state.startDate.year()} — ${state.endDate.year()}`
9
9
  : state.focusedDate.format(state.mode === 'days' ? 'MMMM YYYY' : 'YYYY');
10
10
  const { focusWithinProps } = useFocusWithin({
@@ -12,7 +12,10 @@ export function useCalendarProps(props, state) {
12
12
  onBlurWithin: props.onBlur,
13
13
  });
14
14
  const calendarProps = Object.assign({ role: 'group', id: props.id, 'aria-label': [props['aria-label'], title].filter(Boolean).join(', '), 'aria-labelledby': props['aria-labelledby'] || undefined, 'aria-describedby': props['aria-describedby'] || undefined, 'aria-details': props['aria-details'] || undefined, 'aria-disabled': state.disabled || undefined }, focusWithinProps);
15
- const modeDisabled = state.disabled || state.mode === 'years';
15
+ const modeIndex = state.availableModes.indexOf(state.mode);
16
+ const isModeLast = modeIndex + 1 === state.availableModes.length;
17
+ const isNextModeLast = modeIndex + 2 === state.availableModes.length;
18
+ const modeDisabled = state.disabled || isModeLast;
16
19
  const modeButtonProps = {
17
20
  disabled: state.disabled,
18
21
  // FIXME: do not use button class name
@@ -21,7 +24,7 @@ export function useCalendarProps(props, state) {
21
24
  ? undefined
22
25
  : () => {
23
26
  state.zoomOut();
24
- if (state.mode === 'months') {
27
+ if (isNextModeLast) {
25
28
  state.setFocused(true);
26
29
  }
27
30
  },