@snack-uikit/calendar 0.13.6 → 0.13.7

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 (37) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +1 -0
  3. package/dist/cjs/components/Calendar/Calendar.d.ts +3 -1
  4. package/dist/cjs/components/Calendar/Calendar.js +2 -6
  5. package/dist/cjs/helperComponents/CalendarBase/CalendarBase.d.ts +3 -2
  6. package/dist/cjs/helperComponents/CalendarBase/CalendarBase.js +31 -4
  7. package/dist/cjs/helperComponents/CalendarBase/styles.module.css +5 -0
  8. package/dist/cjs/helperComponents/PeriodPresetsList/PeriodPresetsList.d.ts +16 -0
  9. package/dist/cjs/helperComponents/PeriodPresetsList/PeriodPresetsList.js +75 -0
  10. package/dist/cjs/helperComponents/PeriodPresetsList/index.d.ts +1 -0
  11. package/dist/cjs/helperComponents/PeriodPresetsList/index.js +25 -0
  12. package/dist/cjs/helperComponents/PeriodPresetsList/styles.module.css +56 -0
  13. package/dist/cjs/helperComponents/PeriodPresetsList/utils.d.ts +5 -0
  14. package/dist/cjs/helperComponents/PeriodPresetsList/utils.js +44 -0
  15. package/dist/cjs/types.d.ts +22 -0
  16. package/dist/esm/components/Calendar/Calendar.d.ts +3 -1
  17. package/dist/esm/components/Calendar/Calendar.js +2 -2
  18. package/dist/esm/helperComponents/CalendarBase/CalendarBase.d.ts +3 -2
  19. package/dist/esm/helperComponents/CalendarBase/CalendarBase.js +18 -4
  20. package/dist/esm/helperComponents/CalendarBase/styles.module.css +5 -0
  21. package/dist/esm/helperComponents/PeriodPresetsList/PeriodPresetsList.d.ts +16 -0
  22. package/dist/esm/helperComponents/PeriodPresetsList/PeriodPresetsList.js +35 -0
  23. package/dist/esm/helperComponents/PeriodPresetsList/index.d.ts +1 -0
  24. package/dist/esm/helperComponents/PeriodPresetsList/index.js +1 -0
  25. package/dist/esm/helperComponents/PeriodPresetsList/styles.module.css +56 -0
  26. package/dist/esm/helperComponents/PeriodPresetsList/utils.d.ts +5 -0
  27. package/dist/esm/helperComponents/PeriodPresetsList/utils.js +46 -0
  28. package/dist/esm/types.d.ts +22 -0
  29. package/package.json +2 -2
  30. package/src/components/Calendar/Calendar.tsx +4 -4
  31. package/src/helperComponents/CalendarBase/CalendarBase.tsx +45 -2
  32. package/src/helperComponents/CalendarBase/styles.module.scss +5 -0
  33. package/src/helperComponents/PeriodPresetsList/PeriodPresetsList.tsx +64 -0
  34. package/src/helperComponents/PeriodPresetsList/index.ts +1 -0
  35. package/src/helperComponents/PeriodPresetsList/styles.module.scss +39 -0
  36. package/src/helperComponents/PeriodPresetsList/utils.ts +54 -0
  37. package/src/types.ts +24 -0
package/CHANGELOG.md CHANGED
@@ -3,6 +3,14 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## <small>0.13.7 (2025-11-06)</small>
7
+
8
+ * feat(PDS-1292): presets for calendar range mode ([45929bd](https://github.com/cloud-ru-tech/snack-uikit/commit/45929bd))
9
+
10
+
11
+
12
+
13
+
6
14
  ## <small>0.13.6 (2025-10-28)</small>
7
15
 
8
16
  ### Only dependencies have been changed
package/README.md CHANGED
@@ -43,6 +43,7 @@ import { Calendar } from '@snack-uikit/calendar';
43
43
  | locale | `Intl.Locale` | Проставляется в соответствие с языком в настройках браузера | Локаль, в соответствие с которой выставляется язык названий и первый день недели |
44
44
  | onFocusLeave | `(direction: FocusDirection) => void` | - | Колбек потери фокуса. Вызывается со значением `next`, когда фокус покидает компонент, передвигаясь вперед, по клавише `tab`. Со значением `prev` - по клавише стрелки вверх или `shift + tab`. |
45
45
  | navigationStartRef | `RefObject<{ focus(): void; }>` | - | Ссылка на управление первым элементом навигации |
46
+ | presets | `PresetsOptions` | - | Настройки секции с пресетами быстрого выбора периода. Доступны только при mode === 'range' и отсутствии buildCellProps (временно PDS-3139) |
46
47
  | value | `Date \| Range` | - | Выбранное значение.<br> - в режиме date тип `Date` <br> - в режиме range тип `Range` (`[Date, Date]`) <br> - в режиме month тип `Date` <br> - в режиме date-time тип `Date` <br> - в режиме year тип `Date` |
47
48
  | defaultValue | `Date \| Range` | - | Значение по-умолчанию для uncontrolled.<br> - в режиме date тип `Date` <br> - в режиме range тип `Range` (`[Date, Date]`) <br> - в режиме month тип `Date` <br> - в режиме date-time тип `Date` <br> - в режиме year тип `Date` |
48
49
  | onChangeValue | `((value: Date) => void) \| ((value: Range) => void) \| ((value: Date) => void) \| ((value: Date) => void) \| ((value: Date) => void)` | - | Колбек выбора значения.<br> - в режиме date принимает тип `Date` <br> - в режиме range принимает тип `Range` <br> - в режиме month принимает тип `Date` <br> - в режиме date-time принимает тип `Date` <br> - в режиме year принимает тип `Date` |
@@ -1,7 +1,7 @@
1
1
  import { CSSProperties, RefObject } from 'react';
2
2
  import { WithSupportProps } from '@snack-uikit/utils';
3
3
  import { CALENDAR_MODE } from '../../constants';
4
- import { BuildCellPropsFunction, FocusDirection, Range, Size } from '../../types';
4
+ import { BuildCellPropsFunction, FocusDirection, PresetsOptions, Range, Size } from '../../types';
5
5
  type CommonCalendarProps = {
6
6
  /**
7
7
  * Размер
@@ -46,6 +46,8 @@ type CommonCalendarProps = {
46
46
  navigationStartRef?: RefObject<{
47
47
  focus(): void;
48
48
  }>;
49
+ /** Настройки секции с пресетами быстрого выбора периода. Доступны только при mode === 'range' и отсутствии buildCellProps (временно PDS-3139) */
50
+ presets?: PresetsOptions;
49
51
  };
50
52
  type DateCalendarProps = CommonCalendarProps & {
51
53
  /** Режим работы календаря: <br> - `date` - режим выбора даты */
@@ -19,12 +19,10 @@ const CalendarBase_1 = require("../../helperComponents/CalendarBase");
19
19
  const utils_1 = require("./utils");
20
20
  function Calendar(props) {
21
21
  const {
22
- className,
23
22
  onChangeValue,
24
- buildCellProps,
25
23
  mode
26
24
  } = props,
27
- rest = __rest(props, ["className", "onChangeValue", "buildCellProps", "mode"]);
25
+ rest = __rest(props, ["onChangeValue", "mode"]);
28
26
  const changeValueHandler = (0, react_1.useCallback)(value => {
29
27
  if (mode === constants_1.CALENDAR_MODE.Date || mode === constants_1.CALENDAR_MODE.Month || mode === constants_1.CALENDAR_MODE.Year || mode === constants_1.CALENDAR_MODE.DateTime) {
30
28
  const [date] = value;
@@ -35,10 +33,8 @@ function Calendar(props) {
35
33
  }, [onChangeValue, mode]);
36
34
  return (0, jsx_runtime_1.jsx)(CalendarBase_1.CalendarBase, Object.assign({}, rest, {
37
35
  mode: mode,
38
- className: className,
39
36
  value: (0, utils_1.getNormalizedValue)(props.value),
40
37
  defaultValue: (0, utils_1.getNormalizedValue)(props.defaultValue),
41
- onChangeValue: changeValueHandler,
42
- buildCellProps: buildCellProps
38
+ onChangeValue: changeValueHandler
43
39
  }));
44
40
  }
@@ -1,6 +1,6 @@
1
1
  import { CSSProperties, RefObject } from 'react';
2
2
  import { WithSupportProps } from '@snack-uikit/utils';
3
- import { BuildCellPropsFunction, CalendarMode, FocusDirection, Range, Size } from '../../types';
3
+ import { BuildCellPropsFunction, CalendarMode, FocusDirection, PresetsOptions, Range, Size } from '../../types';
4
4
  export type CalendarBaseProps = WithSupportProps<{
5
5
  mode: CalendarMode;
6
6
  onChangeValue(value: Range): void;
@@ -20,5 +20,6 @@ export type CalendarBaseProps = WithSupportProps<{
20
20
  navigationStartRef?: RefObject<{
21
21
  focus(): void;
22
22
  }>;
23
+ presets?: PresetsOptions;
23
24
  }>;
24
- export declare function CalendarBase({ className, mode, size, autofocus, fitToContainer, value: valueProp, defaultValue, onChangeValue, today: todayProp, showHolidays, showSeconds, style, locale: localeProp, onFocusLeave, buildCellProps, 'data-test-id': testId, navigationStartRef, ...rest }: CalendarBaseProps): import("react/jsx-runtime").JSX.Element;
25
+ export declare function CalendarBase({ className, mode, size, autofocus, fitToContainer, value: valueProp, defaultValue, onChangeValue, today: todayProp, showHolidays, showSeconds, style, locale: localeProp, onFocusLeave, buildCellProps, 'data-test-id': testId, navigationStartRef, presets, ...rest }: CalendarBaseProps): import("react/jsx-runtime").JSX.Element;
@@ -21,6 +21,7 @@ const jsx_runtime_1 = require("react/jsx-runtime");
21
21
  const classnames_1 = __importDefault(require("classnames"));
22
22
  const react_1 = require("react");
23
23
  const uncontrollable_1 = require("uncontrollable");
24
+ const divider_1 = require("@snack-uikit/divider");
24
25
  const locale_1 = require("@snack-uikit/locale");
25
26
  const utils_1 = require("@snack-uikit/utils");
26
27
  const constants_1 = require("../../constants");
@@ -31,6 +32,8 @@ const CalendarContext_1 = require("../CalendarContext");
31
32
  const CalendarNavigation_1 = require("../CalendarNavigation");
32
33
  const ColumnLabels_1 = require("../ColumnLabels");
33
34
  const Footer_1 = require("../Footer");
35
+ const PeriodPresetsList_1 = require("../PeriodPresetsList");
36
+ const utils_3 = require("../PeriodPresetsList/utils");
34
37
  const TimePickerBase_1 = require("../TimePickerBase");
35
38
  const hooks_2 = require("./hooks");
36
39
  const styles_module_scss_1 = __importDefault(require('./styles.module.css'));
@@ -69,13 +72,17 @@ function CalendarBase(_a) {
69
72
  onFocusLeave,
70
73
  buildCellProps,
71
74
  'data-test-id': testId,
72
- navigationStartRef
75
+ navigationStartRef,
76
+ presets
73
77
  } = _a,
74
- rest = __rest(_a, ["className", "mode", "size", "autofocus", "fitToContainer", "value", "defaultValue", "onChangeValue", "today", "showHolidays", "showSeconds", "style", "locale", "onFocusLeave", "buildCellProps", 'data-test-id', "navigationStartRef"]);
78
+ rest = __rest(_a, ["className", "mode", "size", "autofocus", "fitToContainer", "value", "defaultValue", "onChangeValue", "today", "showHolidays", "showSeconds", "style", "locale", "onFocusLeave", "buildCellProps", 'data-test-id', "navigationStartRef", "presets"]);
79
+ const {
80
+ t
81
+ } = (0, locale_1.useLocale)('Calendar');
75
82
  const [viewMode, setViewMode] = (0, react_1.useState)(CALENDAR_DEFAULT_MODE_MAP[mode]);
76
83
  const [viewShift, setViewShift] = (0, react_1.useState)(0);
77
84
  const [value, setValueState] = (0, uncontrollable_1.useUncontrolledProp)(valueProp, defaultValue, onChangeValue);
78
- const today = typeof todayProp === 'number' ? new Date(todayProp) : todayProp;
85
+ const today = (0, react_1.useMemo)(() => typeof todayProp === 'number' ? new Date(todayProp) : todayProp, [todayProp]);
79
86
  const [referenceDate] = (0, react_1.useState)((value === null || value === void 0 ? void 0 : value[0]) || today || new Date());
80
87
  const viewDate = (0, hooks_2.useViewDate)(referenceDate, viewMode, viewShift);
81
88
  const [focus, setFocus] = (0, react_1.useState)(autofocus ? constants_1.AUTOFOCUS : undefined);
@@ -124,6 +131,16 @@ function CalendarBase(_a) {
124
131
  ctxLang
125
132
  }), [ctxLang, localeProp]);
126
133
  const firstNotDisableCell = (0, react_1.useRef)([0, 0]);
134
+ const presetsItems = (0, react_1.useMemo)(() => {
135
+ if ((presets === null || presets === void 0 ? void 0 : presets.items) && presets.items.length > 0) {
136
+ return presets.items;
137
+ }
138
+ return (0, utils_3.getDefaultPresets)(t, today);
139
+ }, [presets === null || presets === void 0 ? void 0 : presets.items, t, today]);
140
+ const arePeriodPresetsDisplayed = mode === 'range' && (presets === null || presets === void 0 ? void 0 : presets.enabled) && !buildCellProps; // TODO PDS-3139
141
+ const onPresetClick = (0, react_1.useCallback)(selectedPeriod => {
142
+ setValue(selectedPeriod);
143
+ }, [setValue]);
127
144
  return (0, jsx_runtime_1.jsx)("div", {
128
145
  className: (0, classnames_1.default)(styles_module_scss_1.default.calendarWrapper, className),
129
146
  "data-fit-to-container": fitToContainer || undefined,
@@ -174,7 +191,17 @@ function CalendarBase(_a) {
174
191
  className: (0, classnames_1.default)(styles_module_scss_1.default.dateWrapper, DATE_WRAPPER_SIZE_MAP[size]),
175
192
  "data-size": size,
176
193
  "data-show-footer": mode === constants_1.CALENDAR_MODE.DateTime && viewMode === 'month' || undefined,
177
- children: [(0, jsx_runtime_1.jsxs)("div", Object.assign({}, (0, utils_1.extractSupportProps)(rest), {
194
+ children: [arePeriodPresetsDisplayed && (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, {
195
+ children: [(0, jsx_runtime_1.jsx)(PeriodPresetsList_1.PeriodPresetsList, {
196
+ items: presetsItems,
197
+ onChange: onPresetClick,
198
+ showTitle: presets === null || presets === void 0 ? void 0 : presets.title,
199
+ "data-test-id": getTestId('presets')
200
+ }), (0, jsx_runtime_1.jsx)(divider_1.Divider, {
201
+ className: styles_module_scss_1.default.divider,
202
+ orientation: 'vertical'
203
+ })]
204
+ }), (0, jsx_runtime_1.jsxs)("div", Object.assign({}, (0, utils_1.extractSupportProps)(rest), {
178
205
  className: (0, classnames_1.default)(styles_module_scss_1.default.calendar, CALENDAR_SIZE_MAP[size]),
179
206
  style: style,
180
207
  "data-size": size,
@@ -94,4 +94,9 @@
94
94
  .rows[data-size=l]{
95
95
  padding-left:var(--space-calendar-container-l, 8px);
96
96
  padding-right:var(--space-calendar-container-l, 8px);
97
+ }
98
+
99
+ .divider{
100
+ flex-shrink:0;
101
+ height:auto;
97
102
  }
@@ -0,0 +1,16 @@
1
+ import { WithSupportProps } from '@snack-uikit/utils';
2
+ import { PresetItem, Range } from '../../types';
3
+ export type PresetsListProps = WithSupportProps<{
4
+ /** Действие при выборе пресета */
5
+ onChange(range: Range): void;
6
+ /** Список пресетов */
7
+ items: PresetItem[];
8
+ /**
9
+ * Скрытие заголовка списка
10
+ * @default true
11
+ */
12
+ showTitle?: boolean;
13
+ /** CSS-класс */
14
+ className?: string;
15
+ }>;
16
+ export declare function PeriodPresetsList({ items, onChange, showTitle, className, ...rest }: PresetsListProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+
3
+ var __rest = void 0 && (void 0).__rest || function (s, e) {
4
+ var t = {};
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p];
6
+ if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]];
8
+ }
9
+ return t;
10
+ };
11
+ var __importDefault = void 0 && (void 0).__importDefault || function (mod) {
12
+ return mod && mod.__esModule ? mod : {
13
+ "default": mod
14
+ };
15
+ };
16
+ Object.defineProperty(exports, "__esModule", {
17
+ value: true
18
+ });
19
+ exports.PeriodPresetsList = PeriodPresetsList;
20
+ const jsx_runtime_1 = require("react/jsx-runtime");
21
+ const classnames_1 = __importDefault(require("classnames"));
22
+ const react_1 = require("react");
23
+ const list_1 = require("@snack-uikit/list");
24
+ const locale_1 = require("@snack-uikit/locale");
25
+ const utils_1 = require("@snack-uikit/utils");
26
+ const CalendarContext_1 = require("../CalendarContext");
27
+ const styles_module_scss_1 = __importDefault(require('./styles.module.css'));
28
+ function PeriodPresetsList(_a) {
29
+ var {
30
+ items,
31
+ onChange,
32
+ showTitle = true,
33
+ className
34
+ } = _a,
35
+ rest = __rest(_a, ["items", "onChange", "showTitle", "className"]);
36
+ const {
37
+ t
38
+ } = (0, locale_1.useLocale)('Calendar');
39
+ const {
40
+ size,
41
+ getTestId
42
+ } = (0, react_1.useContext)(CalendarContext_1.CalendarContext);
43
+ const listItems = (0, react_1.useMemo)(() => items.map(item => ({
44
+ id: item.id,
45
+ content: {
46
+ option: item.label
47
+ },
48
+ onClick() {
49
+ onChange(item.range);
50
+ },
51
+ checked: false
52
+ })), [items, onChange]);
53
+ return (0, jsx_runtime_1.jsxs)("div", Object.assign({
54
+ className: (0, classnames_1.default)(styles_module_scss_1.default.wrapper, className)
55
+ }, (0, utils_1.extractSupportProps)(rest), {
56
+ children: [showTitle && (0, jsx_runtime_1.jsx)("div", {
57
+ className: styles_module_scss_1.default.header,
58
+ "data-size": size,
59
+ children: (0, jsx_runtime_1.jsx)("span", {
60
+ className: styles_module_scss_1.default.title,
61
+ "data-test-id": getTestId('presets-header'),
62
+ children: t('presets')
63
+ })
64
+ }), (0, jsx_runtime_1.jsx)(list_1.List, {
65
+ size: size,
66
+ items: listItems,
67
+ scroll: true,
68
+ selection: {
69
+ mode: 'single',
70
+ value: undefined
71
+ },
72
+ hasListInFocusChain: false
73
+ })]
74
+ }));
75
+ }
@@ -0,0 +1 @@
1
+ export * from './PeriodPresetsList';
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+
3
+ var __createBinding = void 0 && (void 0).__createBinding || (Object.create ? function (o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = {
8
+ enumerable: true,
9
+ get: function () {
10
+ return m[k];
11
+ }
12
+ };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ } : function (o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ });
19
+ var __exportStar = void 0 && (void 0).__exportStar || function (m, exports) {
20
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
21
+ };
22
+ Object.defineProperty(exports, "__esModule", {
23
+ value: true
24
+ });
25
+ __exportStar(require("./PeriodPresetsList"), exports);
@@ -0,0 +1,56 @@
1
+ .wrapper{
2
+ display:flex;
3
+ flex-direction:column;
4
+ }
5
+
6
+ .title{
7
+ display:flex;
8
+ align-items:center;
9
+ }
10
+
11
+ .header{
12
+ display:flex;
13
+ flex-grow:0;
14
+ flex-shrink:0;
15
+ align-items:center;
16
+ color:var(--sys-neutral-text-main, #41424e);
17
+ }
18
+ .header[data-size=s]{
19
+ padding-left:var(--space-calendar-container-s, 8px);
20
+ padding-right:var(--space-calendar-container-s, 8px);
21
+ font-family:var(--sans-label-m-font-family, SB Sans Interface);
22
+ font-weight:var(--sans-label-m-font-weight, Semibold);
23
+ line-height:var(--sans-label-m-line-height, 16px);
24
+ font-size:var(--sans-label-m-font-size, 12px);
25
+ letter-spacing:var(--sans-label-m-letter-spacing, 0px);
26
+ paragraph-spacing:var(--sans-label-m-paragraph-spacing, 6.6px);
27
+ }
28
+ .header[data-size=s] .title{
29
+ height:var(--size-calendar-container-header-lines-height-s, 32px);
30
+ }
31
+ .header[data-size=m]{
32
+ padding-left:var(--space-calendar-container-m, 8px);
33
+ padding-right:var(--space-calendar-container-m, 8px);
34
+ font-family:var(--sans-label-l-font-family, SB Sans Interface);
35
+ font-weight:var(--sans-label-l-font-weight, Semibold);
36
+ line-height:var(--sans-label-l-line-height, 20px);
37
+ font-size:var(--sans-label-l-font-size, 14px);
38
+ letter-spacing:var(--sans-label-l-letter-spacing, 0px);
39
+ paragraph-spacing:var(--sans-label-l-paragraph-spacing, 7.7px);
40
+ }
41
+ .header[data-size=m] .title{
42
+ height:var(--size-calendar-container-header-lines-height-m, 40px);
43
+ }
44
+ .header[data-size=l]{
45
+ padding-left:var(--space-calendar-container-l, 8px);
46
+ padding-right:var(--space-calendar-container-l, 8px);
47
+ font-family:var(--sans-label-l-font-family, SB Sans Interface);
48
+ font-weight:var(--sans-label-l-font-weight, Semibold);
49
+ line-height:var(--sans-label-l-line-height, 20px);
50
+ font-size:var(--sans-label-l-font-size, 14px);
51
+ letter-spacing:var(--sans-label-l-letter-spacing, 0px);
52
+ paragraph-spacing:var(--sans-label-l-paragraph-spacing, 7.7px);
53
+ }
54
+ .header[data-size=l] .title{
55
+ height:var(--size-calendar-container-header-lines-height-l, 48px);
56
+ }
@@ -0,0 +1,5 @@
1
+ import { useLocale } from '@snack-uikit/locale';
2
+ import { PresetItem } from '../../types';
3
+ type TFunction = ReturnType<typeof useLocale<'Calendar'>>['t'];
4
+ export declare function getDefaultPresets(t: TFunction, today?: Date): PresetItem[];
5
+ export {};
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getDefaultPresets = getDefaultPresets;
7
+ const dayInMs = 24 * 60 * 60 * 1000;
8
+ function getDefaultPresets(t, today) {
9
+ const now = today || new Date();
10
+ const nowInMs = now.getTime();
11
+ const calculatePeriodUpToNow = presetLimitInMs => {
12
+ const limit = new Date(now.getTime() + presetLimitInMs);
13
+ return nowInMs > limit.getTime() ? [limit, now] : [now, limit];
14
+ };
15
+ return [{
16
+ label: t('defaultPresets.lastWeek'),
17
+ id: 'week',
18
+ range: calculatePeriodUpToNow(dayInMs * -7)
19
+ }, {
20
+ label: t('defaultPresets.lastTwoWeeks'),
21
+ id: 'twoWeeks',
22
+ range: calculatePeriodUpToNow(dayInMs * -14)
23
+ }, {
24
+ label: t('defaultPresets.lastMonth'),
25
+ id: 'month',
26
+ range: calculatePeriodUpToNow(dayInMs * -30)
27
+ }, {
28
+ label: t('defaultPresets.lastQuarter'),
29
+ id: 'quarter',
30
+ range: calculatePeriodUpToNow(dayInMs * -90)
31
+ }, {
32
+ label: t('defaultPresets.lastThird'),
33
+ id: 'fourMonths',
34
+ range: calculatePeriodUpToNow(dayInMs * -120)
35
+ }, {
36
+ label: t('defaultPresets.lastYear'),
37
+ id: 'year',
38
+ range: calculatePeriodUpToNow(dayInMs * -365)
39
+ }, {
40
+ label: t('defaultPresets.lastTwoYears'),
41
+ id: 'twoYears',
42
+ range: calculatePeriodUpToNow(dayInMs * -365 * 2)
43
+ }];
44
+ }
@@ -43,3 +43,25 @@ export type DateAndTime = TimeValue & {
43
43
  month?: number;
44
44
  day?: number;
45
45
  };
46
+ export type PresetItem = {
47
+ /** Лейбл пресета */
48
+ label: string;
49
+ /** ID периода */
50
+ id: string;
51
+ /** Период */
52
+ range: Range;
53
+ };
54
+ export type PresetsOptions = {
55
+ /**
56
+ * Включение отображения секции с пресетами
57
+ * @default false
58
+ */
59
+ enabled?: boolean;
60
+ /**
61
+ * Отображение заголовка у секции с пресетами
62
+ * @default true
63
+ */
64
+ title?: boolean;
65
+ /** Кастомные пресеты быстрого выбора периода относительно текущего момента */
66
+ items?: PresetItem[];
67
+ };
@@ -1,7 +1,7 @@
1
1
  import { CSSProperties, RefObject } from 'react';
2
2
  import { WithSupportProps } from '@snack-uikit/utils';
3
3
  import { CALENDAR_MODE } from '../../constants';
4
- import { BuildCellPropsFunction, FocusDirection, Range, Size } from '../../types';
4
+ import { BuildCellPropsFunction, FocusDirection, PresetsOptions, Range, Size } from '../../types';
5
5
  type CommonCalendarProps = {
6
6
  /**
7
7
  * Размер
@@ -46,6 +46,8 @@ type CommonCalendarProps = {
46
46
  navigationStartRef?: RefObject<{
47
47
  focus(): void;
48
48
  }>;
49
+ /** Настройки секции с пресетами быстрого выбора периода. Доступны только при mode === 'range' и отсутствии buildCellProps (временно PDS-3139) */
50
+ presets?: PresetsOptions;
49
51
  };
50
52
  type DateCalendarProps = CommonCalendarProps & {
51
53
  /** Режим работы календаря: <br> - `date` - режим выбора даты */
@@ -15,7 +15,7 @@ import { CALENDAR_MODE } from '../../constants';
15
15
  import { CalendarBase } from '../../helperComponents/CalendarBase';
16
16
  import { getNormalizedValue } from './utils';
17
17
  export function Calendar(props) {
18
- const { className, onChangeValue, buildCellProps, mode } = props, rest = __rest(props, ["className", "onChangeValue", "buildCellProps", "mode"]);
18
+ const { onChangeValue, mode } = props, rest = __rest(props, ["onChangeValue", "mode"]);
19
19
  const changeValueHandler = useCallback((value) => {
20
20
  if (mode === CALENDAR_MODE.Date ||
21
21
  mode === CALENDAR_MODE.Month ||
@@ -27,5 +27,5 @@ export function Calendar(props) {
27
27
  }
28
28
  onChangeValue === null || onChangeValue === void 0 ? void 0 : onChangeValue(value);
29
29
  }, [onChangeValue, mode]);
30
- return (_jsx(CalendarBase, Object.assign({}, rest, { mode: mode, className: className, value: getNormalizedValue(props.value), defaultValue: getNormalizedValue(props.defaultValue), onChangeValue: changeValueHandler, buildCellProps: buildCellProps })));
30
+ return (_jsx(CalendarBase, Object.assign({}, rest, { mode: mode, value: getNormalizedValue(props.value), defaultValue: getNormalizedValue(props.defaultValue), onChangeValue: changeValueHandler })));
31
31
  }
@@ -1,6 +1,6 @@
1
1
  import { CSSProperties, RefObject } from 'react';
2
2
  import { WithSupportProps } from '@snack-uikit/utils';
3
- import { BuildCellPropsFunction, CalendarMode, FocusDirection, Range, Size } from '../../types';
3
+ import { BuildCellPropsFunction, CalendarMode, FocusDirection, PresetsOptions, Range, Size } from '../../types';
4
4
  export type CalendarBaseProps = WithSupportProps<{
5
5
  mode: CalendarMode;
6
6
  onChangeValue(value: Range): void;
@@ -20,5 +20,6 @@ export type CalendarBaseProps = WithSupportProps<{
20
20
  navigationStartRef?: RefObject<{
21
21
  focus(): void;
22
22
  }>;
23
+ presets?: PresetsOptions;
23
24
  }>;
24
- export declare function CalendarBase({ className, mode, size, autofocus, fitToContainer, value: valueProp, defaultValue, onChangeValue, today: todayProp, showHolidays, showSeconds, style, locale: localeProp, onFocusLeave, buildCellProps, 'data-test-id': testId, navigationStartRef, ...rest }: CalendarBaseProps): import("react/jsx-runtime").JSX.Element;
25
+ export declare function CalendarBase({ className, mode, size, autofocus, fitToContainer, value: valueProp, defaultValue, onChangeValue, today: todayProp, showHolidays, showSeconds, style, locale: localeProp, onFocusLeave, buildCellProps, 'data-test-id': testId, navigationStartRef, presets, ...rest }: CalendarBaseProps): import("react/jsx-runtime").JSX.Element;
@@ -9,10 +9,11 @@ var __rest = (this && this.__rest) || function (s, e) {
9
9
  }
10
10
  return t;
11
11
  };
12
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
12
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
13
13
  import cn from 'classnames';
14
14
  import { useCallback, useMemo, useRef, useState } from 'react';
15
15
  import { useUncontrolledProp } from 'uncontrollable';
16
+ import { Divider } from '@snack-uikit/divider';
16
17
  import { useLocale } from '@snack-uikit/locale';
17
18
  import { extractSupportProps } from '@snack-uikit/utils';
18
19
  import { AUTOFOCUS, CALENDAR_MODE, SIZE, VIEW_MODE } from '../../constants';
@@ -23,6 +24,8 @@ import { CalendarContext } from '../CalendarContext';
23
24
  import { CalendarNavigation } from '../CalendarNavigation';
24
25
  import { ColumnLabels } from '../ColumnLabels';
25
26
  import { Footer } from '../Footer';
27
+ import { PeriodPresetsList } from '../PeriodPresetsList';
28
+ import { getDefaultPresets } from '../PeriodPresetsList/utils';
26
29
  import { TimePickerBase } from '../TimePickerBase';
27
30
  import { useRange, useViewDate } from './hooks';
28
31
  import styles from './styles.module.css';
@@ -44,11 +47,12 @@ const CALENDAR_DEFAULT_MODE_MAP = {
44
47
  [CALENDAR_MODE.Year]: VIEW_MODE.Decade,
45
48
  };
46
49
  export function CalendarBase(_a) {
47
- var { className, mode, size = SIZE.M, autofocus, fitToContainer = true, value: valueProp, defaultValue, onChangeValue, today: todayProp, showHolidays = false, showSeconds = true, style, locale: localeProp, onFocusLeave, buildCellProps, 'data-test-id': testId, navigationStartRef } = _a, rest = __rest(_a, ["className", "mode", "size", "autofocus", "fitToContainer", "value", "defaultValue", "onChangeValue", "today", "showHolidays", "showSeconds", "style", "locale", "onFocusLeave", "buildCellProps", 'data-test-id', "navigationStartRef"]);
50
+ var { className, mode, size = SIZE.M, autofocus, fitToContainer = true, value: valueProp, defaultValue, onChangeValue, today: todayProp, showHolidays = false, showSeconds = true, style, locale: localeProp, onFocusLeave, buildCellProps, 'data-test-id': testId, navigationStartRef, presets } = _a, rest = __rest(_a, ["className", "mode", "size", "autofocus", "fitToContainer", "value", "defaultValue", "onChangeValue", "today", "showHolidays", "showSeconds", "style", "locale", "onFocusLeave", "buildCellProps", 'data-test-id', "navigationStartRef", "presets"]);
51
+ const { t } = useLocale('Calendar');
48
52
  const [viewMode, setViewMode] = useState(CALENDAR_DEFAULT_MODE_MAP[mode]);
49
53
  const [viewShift, setViewShift] = useState(0);
50
54
  const [value, setValueState] = useUncontrolledProp(valueProp, defaultValue, onChangeValue);
51
- const today = typeof todayProp === 'number' ? new Date(todayProp) : todayProp;
55
+ const today = useMemo(() => (typeof todayProp === 'number' ? new Date(todayProp) : todayProp), [todayProp]);
52
56
  const [referenceDate] = useState((value === null || value === void 0 ? void 0 : value[0]) || today || new Date());
53
57
  const viewDate = useViewDate(referenceDate, viewMode, viewShift);
54
58
  const [focus, setFocus] = useState(autofocus ? AUTOFOCUS : undefined);
@@ -69,6 +73,16 @@ export function CalendarBase(_a) {
69
73
  const { lang: ctxLang } = useLocale();
70
74
  const locale = useMemo(() => getLocale({ localeProp, ctxLang }), [ctxLang, localeProp]);
71
75
  const firstNotDisableCell = useRef([0, 0]);
76
+ const presetsItems = useMemo(() => {
77
+ if ((presets === null || presets === void 0 ? void 0 : presets.items) && presets.items.length > 0) {
78
+ return presets.items;
79
+ }
80
+ return getDefaultPresets(t, today);
81
+ }, [presets === null || presets === void 0 ? void 0 : presets.items, t, today]);
82
+ const arePeriodPresetsDisplayed = mode === 'range' && (presets === null || presets === void 0 ? void 0 : presets.enabled) && !buildCellProps; // TODO PDS-3139
83
+ const onPresetClick = useCallback((selectedPeriod) => {
84
+ setValue(selectedPeriod);
85
+ }, [setValue]);
72
86
  return (_jsx("div", { className: cn(styles.calendarWrapper, className), "data-fit-to-container": fitToContainer || undefined, "data-test-id": testId, children: _jsxs(CalendarContext.Provider, { value: {
73
87
  locale,
74
88
  size,
@@ -109,5 +123,5 @@ export function CalendarBase(_a) {
109
123
  hoursKeyboardNavigationRef,
110
124
  minutesKeyboardNavigationRef,
111
125
  secondsKeyboardNavigationRef,
112
- }, children: [_jsxs("div", { className: cn(styles.dateWrapper, DATE_WRAPPER_SIZE_MAP[size]), "data-size": size, "data-show-footer": (mode === CALENDAR_MODE.DateTime && viewMode === 'month') || undefined, children: [_jsxs("div", Object.assign({}, extractSupportProps(rest), { className: cn(styles.calendar, CALENDAR_SIZE_MAP[size]), style: style, "data-size": size, "data-fit-to-container": fitToContainer || undefined, children: [_jsxs("div", { className: styles.header, "data-size": size, children: [_jsx(CalendarNavigation, {}), _jsx(ColumnLabels, {})] }), _jsx("div", { className: styles.body, children: _jsx("div", { className: styles.rows, "data-size": size, children: _jsx(CalendarBody, {}) }) })] })), mode === CALENDAR_MODE.DateTime && viewMode === 'month' && _jsx(TimePickerBase, {})] }), _jsx(Footer, {})] }) }));
126
+ }, children: [_jsxs("div", { className: cn(styles.dateWrapper, DATE_WRAPPER_SIZE_MAP[size]), "data-size": size, "data-show-footer": (mode === CALENDAR_MODE.DateTime && viewMode === 'month') || undefined, children: [arePeriodPresetsDisplayed && (_jsxs(_Fragment, { children: [_jsx(PeriodPresetsList, { items: presetsItems, onChange: onPresetClick, showTitle: presets === null || presets === void 0 ? void 0 : presets.title, "data-test-id": getTestId('presets') }), _jsx(Divider, { className: styles.divider, orientation: 'vertical' })] })), _jsxs("div", Object.assign({}, extractSupportProps(rest), { className: cn(styles.calendar, CALENDAR_SIZE_MAP[size]), style: style, "data-size": size, "data-fit-to-container": fitToContainer || undefined, children: [_jsxs("div", { className: styles.header, "data-size": size, children: [_jsx(CalendarNavigation, {}), _jsx(ColumnLabels, {})] }), _jsx("div", { className: styles.body, children: _jsx("div", { className: styles.rows, "data-size": size, children: _jsx(CalendarBody, {}) }) })] })), mode === CALENDAR_MODE.DateTime && viewMode === 'month' && _jsx(TimePickerBase, {})] }), _jsx(Footer, {})] }) }));
113
127
  }
@@ -94,4 +94,9 @@
94
94
  .rows[data-size=l]{
95
95
  padding-left:var(--space-calendar-container-l, 8px);
96
96
  padding-right:var(--space-calendar-container-l, 8px);
97
+ }
98
+
99
+ .divider{
100
+ flex-shrink:0;
101
+ height:auto;
97
102
  }
@@ -0,0 +1,16 @@
1
+ import { WithSupportProps } from '@snack-uikit/utils';
2
+ import { PresetItem, Range } from '../../types';
3
+ export type PresetsListProps = WithSupportProps<{
4
+ /** Действие при выборе пресета */
5
+ onChange(range: Range): void;
6
+ /** Список пресетов */
7
+ items: PresetItem[];
8
+ /**
9
+ * Скрытие заголовка списка
10
+ * @default true
11
+ */
12
+ showTitle?: boolean;
13
+ /** CSS-класс */
14
+ className?: string;
15
+ }>;
16
+ export declare function PeriodPresetsList({ items, onChange, showTitle, className, ...rest }: PresetsListProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,35 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
+ import cn from 'classnames';
14
+ import { useContext, useMemo } from 'react';
15
+ import { List } from '@snack-uikit/list';
16
+ import { useLocale } from '@snack-uikit/locale';
17
+ import { extractSupportProps } from '@snack-uikit/utils';
18
+ import { CalendarContext } from '../CalendarContext';
19
+ import styles from './styles.module.css';
20
+ export function PeriodPresetsList(_a) {
21
+ var { items, onChange, showTitle = true, className } = _a, rest = __rest(_a, ["items", "onChange", "showTitle", "className"]);
22
+ const { t } = useLocale('Calendar');
23
+ const { size, getTestId } = useContext(CalendarContext);
24
+ const listItems = useMemo(() => items.map(item => ({
25
+ id: item.id,
26
+ content: {
27
+ option: item.label,
28
+ },
29
+ onClick() {
30
+ onChange(item.range);
31
+ },
32
+ checked: false,
33
+ })), [items, onChange]);
34
+ return (_jsxs("div", Object.assign({ className: cn(styles.wrapper, className) }, extractSupportProps(rest), { children: [showTitle && (_jsx("div", { className: styles.header, "data-size": size, children: _jsx("span", { className: styles.title, "data-test-id": getTestId('presets-header'), children: t('presets') }) })), _jsx(List, { size: size, items: listItems, scroll: true, selection: { mode: 'single', value: undefined }, hasListInFocusChain: false })] })));
35
+ }
@@ -0,0 +1 @@
1
+ export * from './PeriodPresetsList';
@@ -0,0 +1 @@
1
+ export * from './PeriodPresetsList';
@@ -0,0 +1,56 @@
1
+ .wrapper{
2
+ display:flex;
3
+ flex-direction:column;
4
+ }
5
+
6
+ .title{
7
+ display:flex;
8
+ align-items:center;
9
+ }
10
+
11
+ .header{
12
+ display:flex;
13
+ flex-grow:0;
14
+ flex-shrink:0;
15
+ align-items:center;
16
+ color:var(--sys-neutral-text-main, #41424e);
17
+ }
18
+ .header[data-size=s]{
19
+ padding-left:var(--space-calendar-container-s, 8px);
20
+ padding-right:var(--space-calendar-container-s, 8px);
21
+ font-family:var(--sans-label-m-font-family, SB Sans Interface);
22
+ font-weight:var(--sans-label-m-font-weight, Semibold);
23
+ line-height:var(--sans-label-m-line-height, 16px);
24
+ font-size:var(--sans-label-m-font-size, 12px);
25
+ letter-spacing:var(--sans-label-m-letter-spacing, 0px);
26
+ paragraph-spacing:var(--sans-label-m-paragraph-spacing, 6.6px);
27
+ }
28
+ .header[data-size=s] .title{
29
+ height:var(--size-calendar-container-header-lines-height-s, 32px);
30
+ }
31
+ .header[data-size=m]{
32
+ padding-left:var(--space-calendar-container-m, 8px);
33
+ padding-right:var(--space-calendar-container-m, 8px);
34
+ font-family:var(--sans-label-l-font-family, SB Sans Interface);
35
+ font-weight:var(--sans-label-l-font-weight, Semibold);
36
+ line-height:var(--sans-label-l-line-height, 20px);
37
+ font-size:var(--sans-label-l-font-size, 14px);
38
+ letter-spacing:var(--sans-label-l-letter-spacing, 0px);
39
+ paragraph-spacing:var(--sans-label-l-paragraph-spacing, 7.7px);
40
+ }
41
+ .header[data-size=m] .title{
42
+ height:var(--size-calendar-container-header-lines-height-m, 40px);
43
+ }
44
+ .header[data-size=l]{
45
+ padding-left:var(--space-calendar-container-l, 8px);
46
+ padding-right:var(--space-calendar-container-l, 8px);
47
+ font-family:var(--sans-label-l-font-family, SB Sans Interface);
48
+ font-weight:var(--sans-label-l-font-weight, Semibold);
49
+ line-height:var(--sans-label-l-line-height, 20px);
50
+ font-size:var(--sans-label-l-font-size, 14px);
51
+ letter-spacing:var(--sans-label-l-letter-spacing, 0px);
52
+ paragraph-spacing:var(--sans-label-l-paragraph-spacing, 7.7px);
53
+ }
54
+ .header[data-size=l] .title{
55
+ height:var(--size-calendar-container-header-lines-height-l, 48px);
56
+ }
@@ -0,0 +1,5 @@
1
+ import { useLocale } from '@snack-uikit/locale';
2
+ import { PresetItem } from '../../types';
3
+ type TFunction = ReturnType<typeof useLocale<'Calendar'>>['t'];
4
+ export declare function getDefaultPresets(t: TFunction, today?: Date): PresetItem[];
5
+ export {};
@@ -0,0 +1,46 @@
1
+ const dayInMs = 24 * 60 * 60 * 1000;
2
+ export function getDefaultPresets(t, today) {
3
+ const now = today || new Date();
4
+ const nowInMs = now.getTime();
5
+ const calculatePeriodUpToNow = (presetLimitInMs) => {
6
+ const limit = new Date(now.getTime() + presetLimitInMs);
7
+ return nowInMs > limit.getTime() ? [limit, now] : [now, limit];
8
+ };
9
+ return [
10
+ {
11
+ label: t('defaultPresets.lastWeek'),
12
+ id: 'week',
13
+ range: calculatePeriodUpToNow(dayInMs * -7),
14
+ },
15
+ {
16
+ label: t('defaultPresets.lastTwoWeeks'),
17
+ id: 'twoWeeks',
18
+ range: calculatePeriodUpToNow(dayInMs * -14),
19
+ },
20
+ {
21
+ label: t('defaultPresets.lastMonth'),
22
+ id: 'month',
23
+ range: calculatePeriodUpToNow(dayInMs * -30),
24
+ },
25
+ {
26
+ label: t('defaultPresets.lastQuarter'),
27
+ id: 'quarter',
28
+ range: calculatePeriodUpToNow(dayInMs * -90),
29
+ },
30
+ {
31
+ label: t('defaultPresets.lastThird'),
32
+ id: 'fourMonths',
33
+ range: calculatePeriodUpToNow(dayInMs * -120),
34
+ },
35
+ {
36
+ label: t('defaultPresets.lastYear'),
37
+ id: 'year',
38
+ range: calculatePeriodUpToNow(dayInMs * -365),
39
+ },
40
+ {
41
+ label: t('defaultPresets.lastTwoYears'),
42
+ id: 'twoYears',
43
+ range: calculatePeriodUpToNow(dayInMs * -365 * 2),
44
+ },
45
+ ];
46
+ }
@@ -43,3 +43,25 @@ export type DateAndTime = TimeValue & {
43
43
  month?: number;
44
44
  day?: number;
45
45
  };
46
+ export type PresetItem = {
47
+ /** Лейбл пресета */
48
+ label: string;
49
+ /** ID периода */
50
+ id: string;
51
+ /** Период */
52
+ range: Range;
53
+ };
54
+ export type PresetsOptions = {
55
+ /**
56
+ * Включение отображения секции с пресетами
57
+ * @default false
58
+ */
59
+ enabled?: boolean;
60
+ /**
61
+ * Отображение заголовка у секции с пресетами
62
+ * @default true
63
+ */
64
+ title?: boolean;
65
+ /** Кастомные пресеты быстрого выбора периода относительно текущего момента */
66
+ items?: PresetItem[];
67
+ };
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public"
5
5
  },
6
6
  "title": "Calendar",
7
- "version": "0.13.6",
7
+ "version": "0.13.7",
8
8
  "sideEffects": [
9
9
  "*.css",
10
10
  "*.woff",
@@ -48,5 +48,5 @@
48
48
  "peerDependencies": {
49
49
  "@snack-uikit/locale": "*"
50
50
  },
51
- "gitHead": "4b590b25862e58fae2addec0ca0ddbb56d6d39f5"
51
+ "gitHead": "6b989b5fe57383ddf2bd6c1186bf4e1652633da7"
52
52
  }
@@ -4,7 +4,7 @@ import { WithSupportProps } from '@snack-uikit/utils';
4
4
 
5
5
  import { CALENDAR_MODE } from '../../constants';
6
6
  import { CalendarBase } from '../../helperComponents/CalendarBase';
7
- import { BuildCellPropsFunction, FocusDirection, Range, Size } from '../../types';
7
+ import { BuildCellPropsFunction, FocusDirection, PresetsOptions, Range, Size } from '../../types';
8
8
  import { getNormalizedValue } from './utils';
9
9
 
10
10
  type CommonCalendarProps = {
@@ -49,6 +49,8 @@ type CommonCalendarProps = {
49
49
  onFocusLeave?(direction: FocusDirection): void;
50
50
  /** Ссылка на управление первым элементом навигации */
51
51
  navigationStartRef?: RefObject<{ focus(): void }>;
52
+ /** Настройки секции с пресетами быстрого выбора периода. Доступны только при mode === 'range' и отсутствии buildCellProps (временно PDS-3139) */
53
+ presets?: PresetsOptions;
52
54
  };
53
55
 
54
56
  type DateCalendarProps = CommonCalendarProps & {
@@ -113,7 +115,7 @@ export type CalendarProps = WithSupportProps<
113
115
  >;
114
116
 
115
117
  export function Calendar(props: CalendarProps) {
116
- const { className, onChangeValue, buildCellProps, mode, ...rest } = props;
118
+ const { onChangeValue, mode, ...rest } = props;
117
119
 
118
120
  const changeValueHandler = useCallback(
119
121
  (value: Range) => {
@@ -136,11 +138,9 @@ export function Calendar(props: CalendarProps) {
136
138
  <CalendarBase
137
139
  {...rest}
138
140
  mode={mode}
139
- className={className}
140
141
  value={getNormalizedValue(props.value)}
141
142
  defaultValue={getNormalizedValue(props.defaultValue)}
142
143
  onChangeValue={changeValueHandler}
143
- buildCellProps={buildCellProps}
144
144
  />
145
145
  );
146
146
  }
@@ -2,19 +2,30 @@ import cn from 'classnames';
2
2
  import { CSSProperties, RefObject, useCallback, useMemo, useRef, useState } from 'react';
3
3
  import { useUncontrolledProp } from 'uncontrollable';
4
4
 
5
+ import { Divider } from '@snack-uikit/divider';
5
6
  import { ListProps } from '@snack-uikit/list';
6
7
  import { useLocale } from '@snack-uikit/locale';
7
8
  import { extractSupportProps, WithSupportProps } from '@snack-uikit/utils';
8
9
 
9
10
  import { AUTOFOCUS, CALENDAR_MODE, SIZE, VIEW_MODE } from '../../constants';
10
11
  import { useDateAndTime } from '../../hooks';
11
- import { BuildCellPropsFunction, CalendarMode, FocusDirection, Range, Size, ViewMode } from '../../types';
12
+ import {
13
+ BuildCellPropsFunction,
14
+ CalendarMode,
15
+ FocusDirection,
16
+ PresetsOptions,
17
+ Range,
18
+ Size,
19
+ ViewMode,
20
+ } from '../../types';
12
21
  import { getEndOfTheDay, getLocale, getTestIdBuilder, sortDates } from '../../utils';
13
22
  import { CalendarBody } from '../CalendarBody';
14
23
  import { CalendarContext } from '../CalendarContext';
15
24
  import { CalendarNavigation } from '../CalendarNavigation';
16
25
  import { ColumnLabels } from '../ColumnLabels';
17
26
  import { Footer } from '../Footer';
27
+ import { PeriodPresetsList } from '../PeriodPresetsList';
28
+ import { getDefaultPresets } from '../PeriodPresetsList/utils';
18
29
  import { TimePickerBase } from '../TimePickerBase';
19
30
  import { useRange, useViewDate } from './hooks';
20
31
  import styles from './styles.module.scss';
@@ -36,6 +47,7 @@ export type CalendarBaseProps = WithSupportProps<{
36
47
  autofocus?: boolean;
37
48
  locale?: Intl.Locale;
38
49
  navigationStartRef?: RefObject<{ focus(): void }>;
50
+ presets?: PresetsOptions;
39
51
  }>;
40
52
 
41
53
  const DATE_WRAPPER_SIZE_MAP: Record<Size, string> = {
@@ -76,12 +88,15 @@ export function CalendarBase({
76
88
  buildCellProps,
77
89
  'data-test-id': testId,
78
90
  navigationStartRef,
91
+ presets,
79
92
  ...rest
80
93
  }: CalendarBaseProps) {
94
+ const { t } = useLocale('Calendar');
95
+
81
96
  const [viewMode, setViewMode] = useState<ViewMode>(CALENDAR_DEFAULT_MODE_MAP[mode]);
82
97
  const [viewShift, setViewShift] = useState<number>(0);
83
98
  const [value, setValueState] = useUncontrolledProp<Range | undefined>(valueProp, defaultValue, onChangeValue);
84
- const today = typeof todayProp === 'number' ? new Date(todayProp) : todayProp;
99
+ const today = useMemo(() => (typeof todayProp === 'number' ? new Date(todayProp) : todayProp), [todayProp]);
85
100
  const [referenceDate] = useState(value?.[0] || today || new Date());
86
101
  const viewDate = useViewDate(referenceDate, viewMode, viewShift);
87
102
  const [focus, setFocus] = useState<string | undefined>(autofocus ? AUTOFOCUS : undefined);
@@ -122,6 +137,23 @@ export function CalendarBase({
122
137
 
123
138
  const firstNotDisableCell = useRef<[number, number]>([0, 0]);
124
139
 
140
+ const presetsItems = useMemo(() => {
141
+ if (presets?.items && presets.items.length > 0) {
142
+ return presets.items;
143
+ }
144
+
145
+ return getDefaultPresets(t, today);
146
+ }, [presets?.items, t, today]);
147
+
148
+ const arePeriodPresetsDisplayed = mode === 'range' && presets?.enabled && !buildCellProps; // TODO PDS-3139
149
+
150
+ const onPresetClick = useCallback(
151
+ (selectedPeriod: Range) => {
152
+ setValue(selectedPeriod);
153
+ },
154
+ [setValue],
155
+ );
156
+
125
157
  return (
126
158
  <div
127
159
  className={cn(styles.calendarWrapper, className)}
@@ -176,6 +208,17 @@ export function CalendarBase({
176
208
  data-size={size}
177
209
  data-show-footer={(mode === CALENDAR_MODE.DateTime && viewMode === 'month') || undefined}
178
210
  >
211
+ {arePeriodPresetsDisplayed && (
212
+ <>
213
+ <PeriodPresetsList
214
+ items={presetsItems}
215
+ onChange={onPresetClick}
216
+ showTitle={presets?.title}
217
+ data-test-id={getTestId('presets')}
218
+ />
219
+ <Divider className={styles.divider} orientation='vertical' />
220
+ </>
221
+ )}
179
222
  <div
180
223
  {...extractSupportProps(rest)}
181
224
  className={cn(styles.calendar, CALENDAR_SIZE_MAP[size])}
@@ -91,3 +91,8 @@ $sizes: 's', 'm', 'l';
91
91
  }
92
92
  }
93
93
  }
94
+
95
+ .divider {
96
+ flex-shrink: 0;
97
+ height: auto;
98
+ }
@@ -0,0 +1,64 @@
1
+ import cn from 'classnames';
2
+ import React, { useContext, useMemo } from 'react';
3
+
4
+ import { List, ListProps } from '@snack-uikit/list';
5
+ import { useLocale } from '@snack-uikit/locale';
6
+ import { extractSupportProps, WithSupportProps } from '@snack-uikit/utils';
7
+
8
+ import { PresetItem, Range } from '../../types';
9
+ import { CalendarContext } from '../CalendarContext';
10
+ import styles from './styles.module.scss';
11
+
12
+ export type PresetsListProps = WithSupportProps<{
13
+ /** Действие при выборе пресета */
14
+ onChange(range: Range): void;
15
+ /** Список пресетов */
16
+ items: PresetItem[];
17
+ /**
18
+ * Скрытие заголовка списка
19
+ * @default true
20
+ */
21
+ showTitle?: boolean;
22
+ /** CSS-класс */
23
+ className?: string;
24
+ }>;
25
+
26
+ export function PeriodPresetsList({ items, onChange, showTitle = true, className, ...rest }: PresetsListProps) {
27
+ const { t } = useLocale('Calendar');
28
+
29
+ const { size, getTestId } = useContext(CalendarContext);
30
+
31
+ const listItems: ListProps['items'] = useMemo(
32
+ () =>
33
+ items.map(item => ({
34
+ id: item.id,
35
+ content: {
36
+ option: item.label,
37
+ },
38
+ onClick() {
39
+ onChange(item.range);
40
+ },
41
+ checked: false,
42
+ })),
43
+ [items, onChange],
44
+ );
45
+
46
+ return (
47
+ <div className={cn(styles.wrapper, className)} {...extractSupportProps(rest)}>
48
+ {showTitle && (
49
+ <div className={styles.header} data-size={size}>
50
+ <span className={styles.title} data-test-id={getTestId('presets-header')}>
51
+ {t('presets')}
52
+ </span>
53
+ </div>
54
+ )}
55
+ <List
56
+ size={size}
57
+ items={listItems}
58
+ scroll
59
+ selection={{ mode: 'single', value: undefined }}
60
+ hasListInFocusChain={false}
61
+ />
62
+ </div>
63
+ );
64
+ }
@@ -0,0 +1 @@
1
+ export * from './PeriodPresetsList';
@@ -0,0 +1,39 @@
1
+ @use '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-calendar';
2
+
3
+ $sizes: 's', 'm', 'l';
4
+
5
+ $presets-header-typography: (
6
+ 's': styles-tokens-calendar.$sans-label-m,
7
+ 'm': styles-tokens-calendar.$sans-label-l,
8
+ 'l': styles-tokens-calendar.$sans-label-l
9
+ ) ;
10
+
11
+ .wrapper {
12
+ display: flex;
13
+ flex-direction: column;
14
+ }
15
+
16
+ .title {
17
+ display: flex;
18
+ align-items: center;
19
+ }
20
+
21
+ .header {
22
+ display: flex;
23
+ flex-grow: 0;
24
+ flex-shrink: 0;
25
+ align-items: center;
26
+
27
+ color: styles-tokens-calendar.$sys-neutral-text-main;
28
+
29
+ @each $size in $sizes {
30
+ &[data-size='#{$size}'] {
31
+ @include styles-tokens-calendar.composite-var(styles-tokens-calendar.$calendar, 'time', 'header', $size);
32
+ @include styles-tokens-calendar.composite-var($presets-header-typography, $size);
33
+
34
+ .title {
35
+ @include styles-tokens-calendar.composite-var(styles-tokens-calendar.$calendar, 'time', 'header', 'lines-height', $size);
36
+ }
37
+ }
38
+ }
39
+ }
@@ -0,0 +1,54 @@
1
+ import { useLocale } from '@snack-uikit/locale';
2
+
3
+ import { PresetItem, Range } from '../../types';
4
+ type TFunction = ReturnType<typeof useLocale<'Calendar'>>['t'];
5
+
6
+ const dayInMs = 24 * 60 * 60 * 1000;
7
+
8
+ export function getDefaultPresets(t: TFunction, today?: Date): PresetItem[] {
9
+ const now = today || new Date();
10
+ const nowInMs = now.getTime();
11
+
12
+ const calculatePeriodUpToNow = (presetLimitInMs: number): Range => {
13
+ const limit = new Date(now.getTime() + presetLimitInMs);
14
+ return nowInMs > limit.getTime() ? [limit, now] : [now, limit];
15
+ };
16
+
17
+ return [
18
+ {
19
+ label: t('defaultPresets.lastWeek'),
20
+ id: 'week',
21
+ range: calculatePeriodUpToNow(dayInMs * -7),
22
+ },
23
+ {
24
+ label: t('defaultPresets.lastTwoWeeks'),
25
+ id: 'twoWeeks',
26
+ range: calculatePeriodUpToNow(dayInMs * -14),
27
+ },
28
+ {
29
+ label: t('defaultPresets.lastMonth'),
30
+ id: 'month',
31
+ range: calculatePeriodUpToNow(dayInMs * -30),
32
+ },
33
+ {
34
+ label: t('defaultPresets.lastQuarter'),
35
+ id: 'quarter',
36
+ range: calculatePeriodUpToNow(dayInMs * -90),
37
+ },
38
+ {
39
+ label: t('defaultPresets.lastThird'),
40
+ id: 'fourMonths',
41
+ range: calculatePeriodUpToNow(dayInMs * -120),
42
+ },
43
+ {
44
+ label: t('defaultPresets.lastYear'),
45
+ id: 'year',
46
+ range: calculatePeriodUpToNow(dayInMs * -365),
47
+ },
48
+ {
49
+ label: t('defaultPresets.lastTwoYears'),
50
+ id: 'twoYears',
51
+ range: calculatePeriodUpToNow(dayInMs * -365 * 2),
52
+ },
53
+ ];
54
+ }
package/src/types.ts CHANGED
@@ -51,3 +51,27 @@ export type DateAndTime = TimeValue & {
51
51
  month?: number;
52
52
  day?: number;
53
53
  };
54
+
55
+ export type PresetItem = {
56
+ /** Лейбл пресета */
57
+ label: string;
58
+ /** ID периода */
59
+ id: string;
60
+ /** Период */
61
+ range: Range;
62
+ };
63
+
64
+ export type PresetsOptions = {
65
+ /**
66
+ * Включение отображения секции с пресетами
67
+ * @default false
68
+ */
69
+ enabled?: boolean;
70
+ /**
71
+ * Отображение заголовка у секции с пресетами
72
+ * @default true
73
+ */
74
+ title?: boolean;
75
+ /** Кастомные пресеты быстрого выбора периода относительно текущего момента */
76
+ items?: PresetItem[];
77
+ };