@cloud-ru/ds-calendar 1.0.22 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +3 -0
- package/dist/cjs/components/TimePickerDropdown/TimePickerDropdown.d.ts +3 -0
- package/dist/cjs/constants.d.ts +4 -0
- package/dist/cjs/constants.js +5 -1
- package/dist/cjs/helperComponents/DesktopTimePickerDropdown/DesktopTimePickerDropdown.d.ts +1 -1
- package/dist/cjs/helperComponents/DesktopTimePickerDropdown/DesktopTimePickerDropdown.js +2 -2
- package/dist/cjs/helperComponents/Footer/Footer.d.ts +2 -1
- package/dist/cjs/helperComponents/Footer/Footer.js +15 -10
- package/dist/cjs/helperComponents/MobileTimePickerDropdown/MobileTimePickerDropdown.d.ts +1 -1
- package/dist/cjs/helperComponents/MobileTimePickerDropdown/MobileTimePickerDropdown.js +4 -2
- package/dist/cjs/helperComponents/TimePickerBase/TimePickerBase.js +2 -2
- package/dist/cjs/types.d.ts +2 -1
- package/dist/esm/components/TimePickerDropdown/TimePickerDropdown.d.ts +3 -0
- package/dist/esm/constants.d.ts +4 -0
- package/dist/esm/constants.js +4 -0
- package/dist/esm/helperComponents/DesktopTimePickerDropdown/DesktopTimePickerDropdown.d.ts +1 -1
- package/dist/esm/helperComponents/DesktopTimePickerDropdown/DesktopTimePickerDropdown.js +3 -3
- package/dist/esm/helperComponents/Footer/Footer.d.ts +2 -1
- package/dist/esm/helperComponents/Footer/Footer.js +15 -10
- package/dist/esm/helperComponents/MobileTimePickerDropdown/MobileTimePickerDropdown.d.ts +1 -1
- package/dist/esm/helperComponents/MobileTimePickerDropdown/MobileTimePickerDropdown.js +5 -3
- package/dist/esm/helperComponents/TimePickerBase/TimePickerBase.js +2 -2
- package/dist/esm/types.d.ts +2 -1
- package/dist/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/tsconfig.esm.tsbuildinfo +1 -1
- package/package.json +6 -6
- package/src/components/TimePickerDropdown/TimePickerDropdown.tsx +3 -0
- package/src/constants.ts +5 -0
- package/src/helperComponents/DesktopTimePickerDropdown/DesktopTimePickerDropdown.tsx +7 -2
- package/src/helperComponents/Footer/Footer.tsx +19 -10
- package/src/helperComponents/MobileTimePickerDropdown/MobileTimePickerDropdown.tsx +7 -2
- package/src/helperComponents/TimePickerBase/TimePickerBase.tsx +2 -2
- package/src/types.ts +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,16 @@
|
|
|
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
|
+
## 1.1.1 (2026-09-30)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @ds/calendar
|
|
9
|
+
|
|
10
|
+
# 1.1.0 (2026-09-30)
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
|
|
14
|
+
- **FF-8984:** add footerMode prop ([66fa2ce](https://github.com/cloud-ru-tech/snack-v2/commit/66fa2cefc77b12ebca3da54680fa5f3cb93cfff6))
|
|
15
|
+
|
|
6
16
|
## 1.0.22 (2026-09-29)
|
|
7
17
|
|
|
8
18
|
**Note:** Version bump only for package @ds/calendar
|
package/README.md
CHANGED
|
@@ -608,6 +608,7 @@ export function TimePickerDropdownPlacement() {
|
|
|
608
608
|
| `disableSpanWrapper` | `boolean` | — | Отключает для `isValidElement` внешнюю обертку триггера <br/> Пригодится для элементов с `position: absolute` <br/> Работает для триггеров, которые умеют отдать свою DOM-ноду: нативные элементы, `forwardRef`-компоненты <br/> и компоненты, помеченные `withInnerRefSupport` из `@cloud-ru/ds-utils`. Остальные всё равно получают `<span>` — <br/> без ноды поповеру не от чего считать позицию; в dev-режиме об этом печатается предупреждение. |
|
|
609
609
|
| `fallbackPlacements` | `Placement` | — | Цепочка расположений которая будет применяться к поповеру от первого к последнему если при текущем он не влезает. |
|
|
610
610
|
| `fitToContainer` | `boolean` | `true` | Отключает предустановленный размер, заставляя компонент подстраиваться к размеру контейнра: (width: 100%, height: 100%). |
|
|
611
|
+
| `footerMode` | `"apply-only"` \| `"current-time-and-apply"` | — | Режим футера. По умолчанию — current-time-and-apply |
|
|
611
612
|
| `hoverDelayClose` | `number` | — | Задержка закрытия по ховеру |
|
|
612
613
|
| `hoverDelayOpen` | `number` | — | Задержка открытия по ховеру |
|
|
613
614
|
| `navigationStartRef` | `RefObject<{ focus(): void; }>` | — | Ссылка на управление первым элементом навигации |
|
|
@@ -632,6 +633,8 @@ export function TimePickerDropdownPlacement() {
|
|
|
632
633
|
|
|
633
634
|
- `Size` = `"l"` \| `"m"` \| `"s"`
|
|
634
635
|
|
|
636
|
+
- `TimePickerFooterMode` = `"apply-only"` \| `"current-time-and-apply"`
|
|
637
|
+
|
|
635
638
|
**TimeValue**
|
|
636
639
|
|
|
637
640
|
| Prop | Type | Default | Description |
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DropdownProps } from '@cloud-ru/ds-dropdown';
|
|
2
2
|
import { WithSupportProps } from '@cloud-ru/ds-utils';
|
|
3
3
|
import { ReactNode } from 'react';
|
|
4
|
+
import { TimePickerFooterMode } from '../../types';
|
|
4
5
|
import { TimePickerProps } from '../TimePicker';
|
|
5
6
|
type DropdownBridgeProps = Pick<DropdownProps, 'triggerClassName' | 'trigger' | 'placement' | 'hoverDelayOpen' | 'hoverDelayClose' | 'closeOnEscapeKey' | 'triggerClickByKeys' | 'triggerRef' | 'outsideClick' | 'fallbackPlacements' | 'disableSpanWrapper' | 'closeOnPopstate' | 'open' | 'onOpenChange'>;
|
|
6
7
|
/**
|
|
@@ -11,6 +12,8 @@ type DropdownBridgeProps = Pick<DropdownProps, 'triggerClassName' | 'trigger' |
|
|
|
11
12
|
export type TimePickerDropdownProps = WithSupportProps<TimePickerProps & DropdownBridgeProps & {
|
|
12
13
|
/** Контент триггера открытия dropdown */
|
|
13
14
|
children?: ReactNode;
|
|
15
|
+
/** Режим футера. По умолчанию — current-time-and-apply */
|
|
16
|
+
footerMode?: TimePickerFooterMode;
|
|
14
17
|
/** Закрыть dropdown после нажатия кнопки Apply */
|
|
15
18
|
closeOnApply?: boolean;
|
|
16
19
|
/** Колбек по нажатию Apply */
|
package/dist/cjs/constants.d.ts
CHANGED
|
@@ -12,6 +12,10 @@ export declare const CALENDAR_MODE: {
|
|
|
12
12
|
readonly Year: "year";
|
|
13
13
|
readonly YearRange: "year-range";
|
|
14
14
|
};
|
|
15
|
+
export declare const TIME_PICKER_FOOTER_MODE: {
|
|
16
|
+
readonly CurrentTimeAndApply: "current-time-and-apply";
|
|
17
|
+
readonly ApplyOnly: "apply-only";
|
|
18
|
+
};
|
|
15
19
|
export declare const RANGE_POSITION: {
|
|
16
20
|
readonly Out: "out";
|
|
17
21
|
readonly Start: "start";
|
package/dist/cjs/constants.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SECONDS = exports.MINUTES = exports.HOURS = exports.TEST_IDS = exports.AUTOFOCUS = exports.GRID_SIZE = exports.SIZE = exports.RANGE_POSITION = exports.CALENDAR_MODE = exports.VIEW_MODE = void 0;
|
|
3
|
+
exports.SECONDS = exports.MINUTES = exports.HOURS = exports.TEST_IDS = exports.AUTOFOCUS = exports.GRID_SIZE = exports.SIZE = exports.RANGE_POSITION = exports.TIME_PICKER_FOOTER_MODE = exports.CALENDAR_MODE = exports.VIEW_MODE = void 0;
|
|
4
4
|
exports.VIEW_MODE = {
|
|
5
5
|
Month: 'month',
|
|
6
6
|
Year: 'year',
|
|
@@ -15,6 +15,10 @@ exports.CALENDAR_MODE = {
|
|
|
15
15
|
Year: 'year',
|
|
16
16
|
YearRange: 'year-range',
|
|
17
17
|
};
|
|
18
|
+
exports.TIME_PICKER_FOOTER_MODE = {
|
|
19
|
+
CurrentTimeAndApply: 'current-time-and-apply',
|
|
20
|
+
ApplyOnly: 'apply-only',
|
|
21
|
+
};
|
|
18
22
|
exports.RANGE_POSITION = {
|
|
19
23
|
Out: 'out',
|
|
20
24
|
Start: 'start',
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { TimePickerDropdownProps } from '../../components/TimePickerDropdown/TimePickerDropdown';
|
|
2
|
-
export declare function DesktopTimePickerDropdown({ className, size, fitToContainer, value: valueProp, defaultValue, onChangeValue, onFocusLeave, 'data-test-id': testId, navigationStartRef, showSeconds, today: todayProp, children, closeOnApply, onApply, onCurrent, open: openProp, onOpenChange, triggerClassName, trigger, placement, hoverDelayOpen, hoverDelayClose, closeOnEscapeKey, triggerClickByKeys, triggerRef, outsideClick, fallbackPlacements, disableSpanWrapper, closeOnPopstate, }: TimePickerDropdownProps): import("react/jsx-runtime").JSX.Element;
|
|
2
|
+
export declare function DesktopTimePickerDropdown({ className, size, fitToContainer, value: valueProp, defaultValue, onChangeValue, onFocusLeave, 'data-test-id': testId, navigationStartRef, showSeconds, today: todayProp, children, closeOnApply, footerMode, onApply, onCurrent, open: openProp, onOpenChange, triggerClassName, trigger, placement, hoverDelayOpen, hoverDelayClose, closeOnEscapeKey, triggerClickByKeys, triggerRef, outsideClick, fallbackPlacements, disableSpanWrapper, closeOnPopstate, }: TimePickerDropdownProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -20,7 +20,7 @@ const Footer_1 = require("../Footer");
|
|
|
20
20
|
const TimePickerBase_1 = require("../TimePickerBase");
|
|
21
21
|
const stubFunc = () => { };
|
|
22
22
|
const stubBuildCellProps = () => ({ isDisabled: false, isHoliday: false });
|
|
23
|
-
function DesktopTimePickerDropdown({ className, size = constants_2.SIZE.M, fitToContainer = false, value: valueProp, defaultValue, onChangeValue, onFocusLeave, 'data-test-id': testId, navigationStartRef, showSeconds = true, today: todayProp, children, closeOnApply = false, onApply, onCurrent, open: openProp, onOpenChange, triggerClassName, trigger = 'click', placement = 'bottom-start', hoverDelayOpen, hoverDelayClose, closeOnEscapeKey, triggerClickByKeys, triggerRef, outsideClick, fallbackPlacements, disableSpanWrapper, closeOnPopstate, }) {
|
|
23
|
+
function DesktopTimePickerDropdown({ className, size = constants_2.SIZE.M, fitToContainer = false, value: valueProp, defaultValue, onChangeValue, onFocusLeave, 'data-test-id': testId, navigationStartRef, showSeconds = true, today: todayProp, children, closeOnApply = false, footerMode = constants_2.TIME_PICKER_FOOTER_MODE.CurrentTimeAndApply, onApply, onCurrent, open: openProp, onOpenChange, triggerClassName, trigger = 'click', placement = 'bottom-start', hoverDelayOpen, hoverDelayClose, closeOnEscapeKey, triggerClickByKeys, triggerRef, outsideClick, fallbackPlacements, disableSpanWrapper, closeOnPopstate, }) {
|
|
24
24
|
const [open, setOpen] = (0, utils_1.useUncontrolledProp)(openProp, false, onOpenChange);
|
|
25
25
|
const [focus, setFocus] = (0, react_1.useState)(undefined);
|
|
26
26
|
const today = (0, react_1.useMemo)(() => (typeof todayProp === 'number' ? new Date(todayProp) : todayProp), [todayProp]);
|
|
@@ -112,6 +112,6 @@ function DesktopTimePickerDropdown({ className, size = constants_2.SIZE.M, fitTo
|
|
|
112
112
|
// Футер вынесен в `bottomBar` дропдауна (`footer` + `footerDivider`) — divider и
|
|
113
113
|
// паддинги даёт сам Dropdown. Провайдер контекста дублируется здесь, т.к. `footer` —
|
|
114
114
|
// отдельный от `content` регион.
|
|
115
|
-
const footer = ((0, jsx_runtime_1.jsx)(CalendarContext_1.CalendarContext.Provider, { value: calendarContextValue, children: (0, jsx_runtime_1.jsx)(Footer_1.Footer, { onApply: handleApply, onCurrent: onCurrent }) }));
|
|
115
|
+
const footer = ((0, jsx_runtime_1.jsx)(CalendarContext_1.CalendarContext.Provider, { value: calendarContextValue, children: (0, jsx_runtime_1.jsx)(Footer_1.Footer, { onApply: handleApply, onCurrent: onCurrent, showCurrent: footerMode === constants_2.TIME_PICKER_FOOTER_MODE.CurrentTimeAndApply }) }));
|
|
116
116
|
return ((0, jsx_runtime_1.jsx)(dropdown_1.Dropdown, { className: styles_module_scss_1.default.dropdown, "data-test-id": testId, triggerClassName: triggerClassName, trigger: trigger, placement: placement, hoverDelayOpen: hoverDelayOpen, hoverDelayClose: hoverDelayClose, closeOnEscapeKey: closeOnEscapeKey, triggerClickByKeys: triggerClickByKeys, triggerRef: triggerRef, outsideClick: outsideClick, fallbackPlacements: fallbackPlacements, disableSpanWrapper: disableSpanWrapper, closeOnPopstate: closeOnPopstate, open: open, onOpenChange: setOpen, content: content, footer: footer, footerDivider: true, bodyPadding: false, widthStrategy: 'gte', children: children }));
|
|
117
117
|
}
|
|
@@ -10,6 +10,7 @@ export type FooterProps = {
|
|
|
10
10
|
* где обвязку даёт сам Dropdown.
|
|
11
11
|
*/
|
|
12
12
|
inline?: boolean;
|
|
13
|
+
showCurrent?: boolean;
|
|
13
14
|
};
|
|
14
15
|
/**
|
|
15
16
|
* Футер календарных дропдаунов (Apply / Current). Кнопки собираются общим
|
|
@@ -17,4 +18,4 @@ export type FooterProps = {
|
|
|
17
18
|
* modal / drawer / bottom-sheet (Figma `popupDropdownFooter`). Читает состояние из
|
|
18
19
|
* `CalendarContext`, поэтому рендерится только внутри провайдера контекста.
|
|
19
20
|
*/
|
|
20
|
-
export declare function Footer({ onApply, onCurrent, inline }: FooterProps): import("react/jsx-runtime").JSX.Element | null;
|
|
21
|
+
export declare function Footer({ onApply, onCurrent, inline, showCurrent }: FooterProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -20,7 +20,7 @@ const styles_module_scss_1 = __importDefault(require('./styles.module.css'));
|
|
|
20
20
|
* modal / drawer / bottom-sheet (Figma `popupDropdownFooter`). Читает состояние из
|
|
21
21
|
* `CalendarContext`, поэтому рендерится только внутри провайдера контекста.
|
|
22
22
|
*/
|
|
23
|
-
function Footer({ onApply, onCurrent, inline = false }) {
|
|
23
|
+
function Footer({ onApply, onCurrent, inline = false, showCurrent = true }) {
|
|
24
24
|
const { viewMode, mode, today, setValue, dateAndTime, isTimeFilled, isDateAndTimeFilled, onDateAndTimeChange, applyButtonRef, currentButtonRef, hoursKeyboardNavigationRef, minutesKeyboardNavigationRef, secondsKeyboardNavigationRef, showSeconds, getTestId, referenceDate, setViewShift, onFocusLeave, size, } = (0, hooks_1.useCalendarContext)();
|
|
25
25
|
const { t } = locale_1.calendarLocale.useTranslations();
|
|
26
26
|
if (![constants_1.CALENDAR_MODE.DateTime, 'time'].includes(mode) || viewMode !== 'month') {
|
|
@@ -46,6 +46,9 @@ function Footer({ onApply, onCurrent, inline = false }) {
|
|
|
46
46
|
}
|
|
47
47
|
};
|
|
48
48
|
const handleApplyKeyDown = event => {
|
|
49
|
+
if (event.key === 'Tab' && event.shiftKey && !showCurrent) {
|
|
50
|
+
handleCurrentKeyDown(event);
|
|
51
|
+
}
|
|
49
52
|
if (event.key === 'Tab' && !event.shiftKey) {
|
|
50
53
|
onFocusLeave?.('next');
|
|
51
54
|
}
|
|
@@ -80,15 +83,17 @@ function Footer({ onApply, onCurrent, inline = false }) {
|
|
|
80
83
|
innerRef: applyButtonRef,
|
|
81
84
|
onClick: handleApplySelection,
|
|
82
85
|
onKeyDown: handleApplyKeyDown,
|
|
83
|
-
}, additionalButton:
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
86
|
+
}, additionalButton: showCurrent
|
|
87
|
+
? {
|
|
88
|
+
label: t('current'),
|
|
89
|
+
// `popupDropdownFooter` держит третью кнопку на `function`, а не на дефолтном
|
|
90
|
+
// из `FooterActions` `simple`: у `simple` остаётся горизонтальный паддинг.
|
|
91
|
+
view: button_1.VIEW.Function,
|
|
92
|
+
innerRef: currentButtonRef,
|
|
93
|
+
onClick: handleCurrentClick,
|
|
94
|
+
onKeyDown: handleCurrentKeyDown,
|
|
95
|
+
}
|
|
96
|
+
: undefined, testIds: {
|
|
92
97
|
approve: getTestId('apply-button') ?? '',
|
|
93
98
|
additional: getTestId('current-button') ?? '',
|
|
94
99
|
cancel: '',
|
|
@@ -3,4 +3,4 @@ import { TimePickerDropdownProps } from '../../components/TimePickerDropdown/Tim
|
|
|
3
3
|
* Черновик + Apply модель: правки берутся в локальный черновик, коммитятся наружу только по Apply.
|
|
4
4
|
* Закрытие/свайп = отмена (убирает авто-коммит после каждого оседания).
|
|
5
5
|
*/
|
|
6
|
-
export declare function MobileTimePickerDropdown({ children, size, value, defaultValue, onChangeValue, showSeconds, today: todayProp, closeOnApply, onApply, onCurrent, open: openProp, onOpenChange, closeOnPopstate, 'data-test-id': testId, ...rest }: TimePickerDropdownProps): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
export declare function MobileTimePickerDropdown({ children, size, value, defaultValue, onChangeValue, showSeconds, today: todayProp, closeOnApply, footerMode, onApply, onCurrent, open: openProp, onOpenChange, closeOnPopstate, 'data-test-id': testId, ...rest }: TimePickerDropdownProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -13,7 +13,7 @@ const TimePickerDrum_1 = require("../TimePickerDrum");
|
|
|
13
13
|
* Черновик + Apply модель: правки берутся в локальный черновик, коммитятся наружу только по Apply.
|
|
14
14
|
* Закрытие/свайп = отмена (убирает авто-коммит после каждого оседания).
|
|
15
15
|
*/
|
|
16
|
-
function MobileTimePickerDropdown({ children, size = constants_1.SIZE.M, value, defaultValue, onChangeValue, showSeconds = true, today: todayProp, closeOnApply = false, onApply, onCurrent, open: openProp, onOpenChange, closeOnPopstate, 'data-test-id': testId, ...rest }) {
|
|
16
|
+
function MobileTimePickerDropdown({ children, size = constants_1.SIZE.M, value, defaultValue, onChangeValue, showSeconds = true, today: todayProp, closeOnApply = false, footerMode = constants_1.TIME_PICKER_FOOTER_MODE.CurrentTimeAndApply, onApply, onCurrent, open: openProp, onOpenChange, closeOnPopstate, 'data-test-id': testId, ...rest }) {
|
|
17
17
|
const { t } = locale_1.calendarLocale.useTranslations();
|
|
18
18
|
const [open, setOpen] = (0, utils_1.useValueControl)({ value: openProp, defaultValue: false, onChange: onOpenChange });
|
|
19
19
|
const today = (0, react_1.useMemo)(() => (typeof todayProp === 'number' ? new Date(todayProp) : todayProp), [todayProp]);
|
|
@@ -65,5 +65,7 @@ function MobileTimePickerDropdown({ children, size = constants_1.SIZE.M, value,
|
|
|
65
65
|
}
|
|
66
66
|
}, [dateAndTime, showSeconds, commit, onApply, closeOnApply, handleClose]);
|
|
67
67
|
const trigger = (0, hooks_1.useOpenTrigger)(children, handleOpen);
|
|
68
|
-
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [trigger, (0, jsx_runtime_1.jsx)(bottom_sheet_1.BottomSheet, { withDividers: false, open: open ?? false, onClose: handleClose, onBackButtonClick: handleClose, title: t('time'), closeOnPopstate: closeOnPopstate, approveButton: { label: t('apply'), disabled: !isTimeFilled(), onClick: handleApply }, additionalButton:
|
|
68
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [trigger, (0, jsx_runtime_1.jsx)(bottom_sheet_1.BottomSheet, { withDividers: false, open: open ?? false, onClose: handleClose, onBackButtonClick: handleClose, title: t('time'), closeOnPopstate: closeOnPopstate, approveButton: { label: t('apply'), disabled: !isTimeFilled(), onClick: handleApply }, additionalButton: footerMode === constants_1.TIME_PICKER_FOOTER_MODE.CurrentTimeAndApply
|
|
69
|
+
? { label: t('current'), view: 'function', onClick: handleCurrent }
|
|
70
|
+
: undefined, footerTestIds: { approve: constants_1.TEST_IDS.timePickerMobileApply, additional: constants_1.TEST_IDS.timePickerMobileCurrent }, content: (0, jsx_runtime_1.jsx)(TimePickerDrum_1.TimePickerDrum, { ref: drumRef, size: size, showSeconds: showSeconds, hours: dateAndTime?.hours ?? 0, minutes: dateAndTime?.minutes ?? 0, seconds: dateAndTime?.seconds ?? 0, ...drumHandlers }), ...(0, utils_1.extractSupportProps)(rest), "data-test-id": testId })] }));
|
|
69
71
|
}
|
|
@@ -71,7 +71,7 @@ function TimePickerBase({ className, fixedWidth }) {
|
|
|
71
71
|
secondsKeyboardNavigationRef.current?.focusItem((0, list_1.getDefaultItemId)(seconds ?? 0));
|
|
72
72
|
}
|
|
73
73
|
else {
|
|
74
|
-
currentButtonRef.current?.focus();
|
|
74
|
+
(currentButtonRef.current ?? applyButtonRef.current)?.focus();
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
break;
|
|
@@ -104,7 +104,7 @@ function TimePickerBase({ className, fixedWidth }) {
|
|
|
104
104
|
minutesKeyboardNavigationRef.current?.focusItem((0, list_1.getDefaultItemId)(minutes ?? 0));
|
|
105
105
|
}
|
|
106
106
|
else {
|
|
107
|
-
currentButtonRef.current?.focus();
|
|
107
|
+
(currentButtonRef.current ?? applyButtonRef.current)?.focus();
|
|
108
108
|
}
|
|
109
109
|
break;
|
|
110
110
|
case 'Enter':
|
package/dist/cjs/types.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ValueOf } from '@cloud-ru/ds-utils';
|
|
2
2
|
import { KeyboardEventHandler } from 'react';
|
|
3
|
-
import { CALENDAR_MODE, RANGE_POSITION, SIZE, VIEW_MODE } from './constants';
|
|
3
|
+
import { CALENDAR_MODE, RANGE_POSITION, SIZE, TIME_PICKER_FOOTER_MODE, VIEW_MODE } from './constants';
|
|
4
4
|
export type RangePosition = ValueOf<typeof RANGE_POSITION>;
|
|
5
5
|
export type ViewMode = ValueOf<typeof VIEW_MODE>;
|
|
6
6
|
export type Size = ValueOf<typeof SIZE>;
|
|
@@ -48,6 +48,7 @@ export type BuildCellProps = {
|
|
|
48
48
|
isHoliday?: boolean;
|
|
49
49
|
};
|
|
50
50
|
export type BuildCellPropsFunction = (date: Date, viewMode: ViewMode) => BuildCellProps;
|
|
51
|
+
export type TimePickerFooterMode = ValueOf<typeof TIME_PICKER_FOOTER_MODE>;
|
|
51
52
|
export type TimeValue = {
|
|
52
53
|
hours?: number;
|
|
53
54
|
minutes?: number;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DropdownProps } from '@cloud-ru/ds-dropdown';
|
|
2
2
|
import { WithSupportProps } from '@cloud-ru/ds-utils';
|
|
3
3
|
import { ReactNode } from 'react';
|
|
4
|
+
import { TimePickerFooterMode } from '../../types.js';
|
|
4
5
|
import { TimePickerProps } from '../TimePicker/index.js';
|
|
5
6
|
type DropdownBridgeProps = Pick<DropdownProps, 'triggerClassName' | 'trigger' | 'placement' | 'hoverDelayOpen' | 'hoverDelayClose' | 'closeOnEscapeKey' | 'triggerClickByKeys' | 'triggerRef' | 'outsideClick' | 'fallbackPlacements' | 'disableSpanWrapper' | 'closeOnPopstate' | 'open' | 'onOpenChange'>;
|
|
6
7
|
/**
|
|
@@ -11,6 +12,8 @@ type DropdownBridgeProps = Pick<DropdownProps, 'triggerClassName' | 'trigger' |
|
|
|
11
12
|
export type TimePickerDropdownProps = WithSupportProps<TimePickerProps & DropdownBridgeProps & {
|
|
12
13
|
/** Контент триггера открытия dropdown */
|
|
13
14
|
children?: ReactNode;
|
|
15
|
+
/** Режим футера. По умолчанию — current-time-and-apply */
|
|
16
|
+
footerMode?: TimePickerFooterMode;
|
|
14
17
|
/** Закрыть dropdown после нажатия кнопки Apply */
|
|
15
18
|
closeOnApply?: boolean;
|
|
16
19
|
/** Колбек по нажатию Apply */
|
package/dist/esm/constants.d.ts
CHANGED
|
@@ -12,6 +12,10 @@ export declare const CALENDAR_MODE: {
|
|
|
12
12
|
readonly Year: "year";
|
|
13
13
|
readonly YearRange: "year-range";
|
|
14
14
|
};
|
|
15
|
+
export declare const TIME_PICKER_FOOTER_MODE: {
|
|
16
|
+
readonly CurrentTimeAndApply: "current-time-and-apply";
|
|
17
|
+
readonly ApplyOnly: "apply-only";
|
|
18
|
+
};
|
|
15
19
|
export declare const RANGE_POSITION: {
|
|
16
20
|
readonly Out: "out";
|
|
17
21
|
readonly Start: "start";
|
package/dist/esm/constants.js
CHANGED
|
@@ -12,6 +12,10 @@ export const CALENDAR_MODE = {
|
|
|
12
12
|
Year: 'year',
|
|
13
13
|
YearRange: 'year-range',
|
|
14
14
|
};
|
|
15
|
+
export const TIME_PICKER_FOOTER_MODE = {
|
|
16
|
+
CurrentTimeAndApply: 'current-time-and-apply',
|
|
17
|
+
ApplyOnly: 'apply-only',
|
|
18
|
+
};
|
|
15
19
|
export const RANGE_POSITION = {
|
|
16
20
|
Out: 'out',
|
|
17
21
|
Start: 'start',
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { TimePickerDropdownProps } from '../../components/TimePickerDropdown/TimePickerDropdown.js';
|
|
2
|
-
export declare function DesktopTimePickerDropdown({ className, size, fitToContainer, value: valueProp, defaultValue, onChangeValue, onFocusLeave, 'data-test-id': testId, navigationStartRef, showSeconds, today: todayProp, children, closeOnApply, onApply, onCurrent, open: openProp, onOpenChange, triggerClassName, trigger, placement, hoverDelayOpen, hoverDelayClose, closeOnEscapeKey, triggerClickByKeys, triggerRef, outsideClick, fallbackPlacements, disableSpanWrapper, closeOnPopstate, }: TimePickerDropdownProps): import("react/jsx-runtime").JSX.Element;
|
|
2
|
+
export declare function DesktopTimePickerDropdown({ className, size, fitToContainer, value: valueProp, defaultValue, onChangeValue, onFocusLeave, 'data-test-id': testId, navigationStartRef, showSeconds, today: todayProp, children, closeOnApply, footerMode, onApply, onCurrent, open: openProp, onOpenChange, triggerClassName, trigger, placement, hoverDelayOpen, hoverDelayClose, closeOnEscapeKey, triggerClickByKeys, triggerRef, outsideClick, fallbackPlacements, disableSpanWrapper, closeOnPopstate, }: TimePickerDropdownProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -6,7 +6,7 @@ import cn from 'classnames';
|
|
|
6
6
|
import { useCallback, useMemo, useRef, useState } from 'react';
|
|
7
7
|
import { DEFAULT_LOCALE } from '../../components/TimePicker/constants.js';
|
|
8
8
|
import styles from '../../components/TimePickerDropdown/styles.module.css';
|
|
9
|
-
import { SIZE } from '../../constants.js';
|
|
9
|
+
import { SIZE, TIME_PICKER_FOOTER_MODE } from '../../constants.js';
|
|
10
10
|
import { useTimePickerValueBridge } from '../../hooks/index.js';
|
|
11
11
|
import { getLocale, getTestIdBuilder } from '../../utils.js';
|
|
12
12
|
import { CalendarContext } from '../CalendarContext/index.js';
|
|
@@ -14,7 +14,7 @@ import { Footer } from '../Footer/index.js';
|
|
|
14
14
|
import { TimePickerBase } from '../TimePickerBase/index.js';
|
|
15
15
|
const stubFunc = () => { };
|
|
16
16
|
const stubBuildCellProps = () => ({ isDisabled: false, isHoliday: false });
|
|
17
|
-
export function DesktopTimePickerDropdown({ className, size = SIZE.M, fitToContainer = false, value: valueProp, defaultValue, onChangeValue, onFocusLeave, 'data-test-id': testId, navigationStartRef, showSeconds = true, today: todayProp, children, closeOnApply = false, onApply, onCurrent, open: openProp, onOpenChange, triggerClassName, trigger = 'click', placement = 'bottom-start', hoverDelayOpen, hoverDelayClose, closeOnEscapeKey, triggerClickByKeys, triggerRef, outsideClick, fallbackPlacements, disableSpanWrapper, closeOnPopstate, }) {
|
|
17
|
+
export function DesktopTimePickerDropdown({ className, size = SIZE.M, fitToContainer = false, value: valueProp, defaultValue, onChangeValue, onFocusLeave, 'data-test-id': testId, navigationStartRef, showSeconds = true, today: todayProp, children, closeOnApply = false, footerMode = TIME_PICKER_FOOTER_MODE.CurrentTimeAndApply, onApply, onCurrent, open: openProp, onOpenChange, triggerClassName, trigger = 'click', placement = 'bottom-start', hoverDelayOpen, hoverDelayClose, closeOnEscapeKey, triggerClickByKeys, triggerRef, outsideClick, fallbackPlacements, disableSpanWrapper, closeOnPopstate, }) {
|
|
18
18
|
const [open, setOpen] = useUncontrolledProp(openProp, false, onOpenChange);
|
|
19
19
|
const [focus, setFocus] = useState(undefined);
|
|
20
20
|
const today = useMemo(() => (typeof todayProp === 'number' ? new Date(todayProp) : todayProp), [todayProp]);
|
|
@@ -106,6 +106,6 @@ export function DesktopTimePickerDropdown({ className, size = SIZE.M, fitToConta
|
|
|
106
106
|
// Футер вынесен в `bottomBar` дропдауна (`footer` + `footerDivider`) — divider и
|
|
107
107
|
// паддинги даёт сам Dropdown. Провайдер контекста дублируется здесь, т.к. `footer` —
|
|
108
108
|
// отдельный от `content` регион.
|
|
109
|
-
const footer = (_jsx(CalendarContext.Provider, { value: calendarContextValue, children: _jsx(Footer, { onApply: handleApply, onCurrent: onCurrent }) }));
|
|
109
|
+
const footer = (_jsx(CalendarContext.Provider, { value: calendarContextValue, children: _jsx(Footer, { onApply: handleApply, onCurrent: onCurrent, showCurrent: footerMode === TIME_PICKER_FOOTER_MODE.CurrentTimeAndApply }) }));
|
|
110
110
|
return (_jsx(Dropdown, { className: styles.dropdown, "data-test-id": testId, triggerClassName: triggerClassName, trigger: trigger, placement: placement, hoverDelayOpen: hoverDelayOpen, hoverDelayClose: hoverDelayClose, closeOnEscapeKey: closeOnEscapeKey, triggerClickByKeys: triggerClickByKeys, triggerRef: triggerRef, outsideClick: outsideClick, fallbackPlacements: fallbackPlacements, disableSpanWrapper: disableSpanWrapper, closeOnPopstate: closeOnPopstate, open: open, onOpenChange: setOpen, content: content, footer: footer, footerDivider: true, bodyPadding: false, widthStrategy: 'gte', children: children }));
|
|
111
111
|
}
|
|
@@ -10,6 +10,7 @@ export type FooterProps = {
|
|
|
10
10
|
* где обвязку даёт сам Dropdown.
|
|
11
11
|
*/
|
|
12
12
|
inline?: boolean;
|
|
13
|
+
showCurrent?: boolean;
|
|
13
14
|
};
|
|
14
15
|
/**
|
|
15
16
|
* Футер календарных дропдаунов (Apply / Current). Кнопки собираются общим
|
|
@@ -17,4 +18,4 @@ export type FooterProps = {
|
|
|
17
18
|
* modal / drawer / bottom-sheet (Figma `popupDropdownFooter`). Читает состояние из
|
|
18
19
|
* `CalendarContext`, поэтому рендерится только внутри провайдера контекста.
|
|
19
20
|
*/
|
|
20
|
-
export declare function Footer({ onApply, onCurrent, inline }: FooterProps): import("react/jsx-runtime").JSX.Element | null;
|
|
21
|
+
export declare function Footer({ onApply, onCurrent, inline, showCurrent }: FooterProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -14,7 +14,7 @@ import styles from './styles.module.css';
|
|
|
14
14
|
* modal / drawer / bottom-sheet (Figma `popupDropdownFooter`). Читает состояние из
|
|
15
15
|
* `CalendarContext`, поэтому рендерится только внутри провайдера контекста.
|
|
16
16
|
*/
|
|
17
|
-
export function Footer({ onApply, onCurrent, inline = false }) {
|
|
17
|
+
export function Footer({ onApply, onCurrent, inline = false, showCurrent = true }) {
|
|
18
18
|
const { viewMode, mode, today, setValue, dateAndTime, isTimeFilled, isDateAndTimeFilled, onDateAndTimeChange, applyButtonRef, currentButtonRef, hoursKeyboardNavigationRef, minutesKeyboardNavigationRef, secondsKeyboardNavigationRef, showSeconds, getTestId, referenceDate, setViewShift, onFocusLeave, size, } = useCalendarContext();
|
|
19
19
|
const { t } = calendarLocale.useTranslations();
|
|
20
20
|
if (![CALENDAR_MODE.DateTime, 'time'].includes(mode) || viewMode !== 'month') {
|
|
@@ -40,6 +40,9 @@ export function Footer({ onApply, onCurrent, inline = false }) {
|
|
|
40
40
|
}
|
|
41
41
|
};
|
|
42
42
|
const handleApplyKeyDown = event => {
|
|
43
|
+
if (event.key === 'Tab' && event.shiftKey && !showCurrent) {
|
|
44
|
+
handleCurrentKeyDown(event);
|
|
45
|
+
}
|
|
43
46
|
if (event.key === 'Tab' && !event.shiftKey) {
|
|
44
47
|
onFocusLeave?.('next');
|
|
45
48
|
}
|
|
@@ -74,15 +77,17 @@ export function Footer({ onApply, onCurrent, inline = false }) {
|
|
|
74
77
|
innerRef: applyButtonRef,
|
|
75
78
|
onClick: handleApplySelection,
|
|
76
79
|
onKeyDown: handleApplyKeyDown,
|
|
77
|
-
}, additionalButton:
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
80
|
+
}, additionalButton: showCurrent
|
|
81
|
+
? {
|
|
82
|
+
label: t('current'),
|
|
83
|
+
// `popupDropdownFooter` держит третью кнопку на `function`, а не на дефолтном
|
|
84
|
+
// из `FooterActions` `simple`: у `simple` остаётся горизонтальный паддинг.
|
|
85
|
+
view: VIEW.Function,
|
|
86
|
+
innerRef: currentButtonRef,
|
|
87
|
+
onClick: handleCurrentClick,
|
|
88
|
+
onKeyDown: handleCurrentKeyDown,
|
|
89
|
+
}
|
|
90
|
+
: undefined, testIds: {
|
|
86
91
|
approve: getTestId('apply-button') ?? '',
|
|
87
92
|
additional: getTestId('current-button') ?? '',
|
|
88
93
|
cancel: '',
|
|
@@ -3,4 +3,4 @@ import { TimePickerDropdownProps } from '../../components/TimePickerDropdown/Tim
|
|
|
3
3
|
* Черновик + Apply модель: правки берутся в локальный черновик, коммитятся наружу только по Apply.
|
|
4
4
|
* Закрытие/свайп = отмена (убирает авто-коммит после каждого оседания).
|
|
5
5
|
*/
|
|
6
|
-
export declare function MobileTimePickerDropdown({ children, size, value, defaultValue, onChangeValue, showSeconds, today: todayProp, closeOnApply, onApply, onCurrent, open: openProp, onOpenChange, closeOnPopstate, 'data-test-id': testId, ...rest }: TimePickerDropdownProps): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
export declare function MobileTimePickerDropdown({ children, size, value, defaultValue, onChangeValue, showSeconds, today: todayProp, closeOnApply, footerMode, onApply, onCurrent, open: openProp, onOpenChange, closeOnPopstate, 'data-test-id': testId, ...rest }: TimePickerDropdownProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import { BottomSheet } from '@cloud-ru/ds-bottom-sheet';
|
|
3
3
|
import { extractSupportProps, useLayoutEffect, useUncontrolledProp, useValueControl } from '@cloud-ru/ds-utils';
|
|
4
4
|
import { useCallback, useMemo, useRef } from 'react';
|
|
5
|
-
import { SIZE, TEST_IDS } from '../../constants.js';
|
|
5
|
+
import { SIZE, TEST_IDS, TIME_PICKER_FOOTER_MODE } from '../../constants.js';
|
|
6
6
|
import { useDateAndTime, useOpenTrigger, useTimeDrumHandlers } from '../../hooks/index.js';
|
|
7
7
|
import { calendarLocale } from '../../locale/index.js';
|
|
8
8
|
import { TimePickerDrum } from '../TimePickerDrum/index.js';
|
|
@@ -10,7 +10,7 @@ import { TimePickerDrum } from '../TimePickerDrum/index.js';
|
|
|
10
10
|
* Черновик + Apply модель: правки берутся в локальный черновик, коммитятся наружу только по Apply.
|
|
11
11
|
* Закрытие/свайп = отмена (убирает авто-коммит после каждого оседания).
|
|
12
12
|
*/
|
|
13
|
-
export function MobileTimePickerDropdown({ children, size = SIZE.M, value, defaultValue, onChangeValue, showSeconds = true, today: todayProp, closeOnApply = false, onApply, onCurrent, open: openProp, onOpenChange, closeOnPopstate, 'data-test-id': testId, ...rest }) {
|
|
13
|
+
export function MobileTimePickerDropdown({ children, size = SIZE.M, value, defaultValue, onChangeValue, showSeconds = true, today: todayProp, closeOnApply = false, footerMode = TIME_PICKER_FOOTER_MODE.CurrentTimeAndApply, onApply, onCurrent, open: openProp, onOpenChange, closeOnPopstate, 'data-test-id': testId, ...rest }) {
|
|
14
14
|
const { t } = calendarLocale.useTranslations();
|
|
15
15
|
const [open, setOpen] = useValueControl({ value: openProp, defaultValue: false, onChange: onOpenChange });
|
|
16
16
|
const today = useMemo(() => (typeof todayProp === 'number' ? new Date(todayProp) : todayProp), [todayProp]);
|
|
@@ -62,5 +62,7 @@ export function MobileTimePickerDropdown({ children, size = SIZE.M, value, defau
|
|
|
62
62
|
}
|
|
63
63
|
}, [dateAndTime, showSeconds, commit, onApply, closeOnApply, handleClose]);
|
|
64
64
|
const trigger = useOpenTrigger(children, handleOpen);
|
|
65
|
-
return (_jsxs(_Fragment, { children: [trigger, _jsx(BottomSheet, { withDividers: false, open: open ?? false, onClose: handleClose, onBackButtonClick: handleClose, title: t('time'), closeOnPopstate: closeOnPopstate, approveButton: { label: t('apply'), disabled: !isTimeFilled(), onClick: handleApply }, additionalButton:
|
|
65
|
+
return (_jsxs(_Fragment, { children: [trigger, _jsx(BottomSheet, { withDividers: false, open: open ?? false, onClose: handleClose, onBackButtonClick: handleClose, title: t('time'), closeOnPopstate: closeOnPopstate, approveButton: { label: t('apply'), disabled: !isTimeFilled(), onClick: handleApply }, additionalButton: footerMode === TIME_PICKER_FOOTER_MODE.CurrentTimeAndApply
|
|
66
|
+
? { label: t('current'), view: 'function', onClick: handleCurrent }
|
|
67
|
+
: undefined, footerTestIds: { approve: TEST_IDS.timePickerMobileApply, additional: TEST_IDS.timePickerMobileCurrent }, content: _jsx(TimePickerDrum, { ref: drumRef, size: size, showSeconds: showSeconds, hours: dateAndTime?.hours ?? 0, minutes: dateAndTime?.minutes ?? 0, seconds: dateAndTime?.seconds ?? 0, ...drumHandlers }), ...extractSupportProps(rest), "data-test-id": testId })] }));
|
|
66
68
|
}
|
|
@@ -65,7 +65,7 @@ export function TimePickerBase({ className, fixedWidth }) {
|
|
|
65
65
|
secondsKeyboardNavigationRef.current?.focusItem(getDefaultItemId(seconds ?? 0));
|
|
66
66
|
}
|
|
67
67
|
else {
|
|
68
|
-
currentButtonRef.current?.focus();
|
|
68
|
+
(currentButtonRef.current ?? applyButtonRef.current)?.focus();
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
break;
|
|
@@ -98,7 +98,7 @@ export function TimePickerBase({ className, fixedWidth }) {
|
|
|
98
98
|
minutesKeyboardNavigationRef.current?.focusItem(getDefaultItemId(minutes ?? 0));
|
|
99
99
|
}
|
|
100
100
|
else {
|
|
101
|
-
currentButtonRef.current?.focus();
|
|
101
|
+
(currentButtonRef.current ?? applyButtonRef.current)?.focus();
|
|
102
102
|
}
|
|
103
103
|
break;
|
|
104
104
|
case 'Enter':
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ValueOf } from '@cloud-ru/ds-utils';
|
|
2
2
|
import { KeyboardEventHandler } from 'react';
|
|
3
|
-
import { CALENDAR_MODE, RANGE_POSITION, SIZE, VIEW_MODE } from './constants.js';
|
|
3
|
+
import { CALENDAR_MODE, RANGE_POSITION, SIZE, TIME_PICKER_FOOTER_MODE, VIEW_MODE } from './constants.js';
|
|
4
4
|
export type RangePosition = ValueOf<typeof RANGE_POSITION>;
|
|
5
5
|
export type ViewMode = ValueOf<typeof VIEW_MODE>;
|
|
6
6
|
export type Size = ValueOf<typeof SIZE>;
|
|
@@ -48,6 +48,7 @@ export type BuildCellProps = {
|
|
|
48
48
|
isHoliday?: boolean;
|
|
49
49
|
};
|
|
50
50
|
export type BuildCellPropsFunction = (date: Date, viewMode: ViewMode) => BuildCellProps;
|
|
51
|
+
export type TimePickerFooterMode = ValueOf<typeof TIME_PICKER_FOOTER_MODE>;
|
|
51
52
|
export type TimeValue = {
|
|
52
53
|
hours?: number;
|
|
53
54
|
minutes?: number;
|