@sberbusiness/triplex-next-mcp-server 1.43.0-beta → 1.45.0-beta
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/README.md +7 -6
- package/bundle/components/AlertContext.md +11 -8
- package/bundle/components/AlertProcess.md +16 -12
- package/bundle/components/Badge.md +6 -3
- package/bundle/components/BodyPage.md +72 -4
- package/bundle/components/Button.md +46 -43
- package/bundle/components/ButtonDropdownExtended.md +3 -2
- package/bundle/components/ButtonIcon.md +7 -4
- package/bundle/components/Calendar.md +62 -59
- package/bundle/components/Caption.md +26 -23
- package/bundle/components/Checkbox.md +32 -29
- package/bundle/components/CheckboxTreeExtended.md +9 -5
- package/bundle/components/Chip.md +44 -41
- package/bundle/components/CodeText.md +26 -23
- package/bundle/components/Col.md +1 -2
- package/bundle/components/CollapsibleTree.md +8 -5
- package/bundle/components/CollapsibleTreeExtended.md +6 -1
- package/bundle/components/DatePickerExtended.md +409 -262
- package/bundle/components/Dropdown.md +33 -28
- package/bundle/components/EmptyView.md +12 -5
- package/bundle/components/ExpandAnimation.md +263 -218
- package/bundle/components/Footer.md +235 -278
- package/bundle/components/FooterPage.md +6 -2
- package/bundle/components/FormField.md +44 -41
- package/bundle/components/FormGroup.md +192 -52
- package/bundle/components/Header.md +491 -211
- package/bundle/components/HeaderPage.md +6 -2
- package/bundle/components/ImageGallery.md +33 -34
- package/bundle/components/ImageGalleryExtended.md +33 -34
- package/bundle/components/Island.md +52 -14
- package/bundle/components/LightBox.md +25 -18
- package/bundle/components/Link.md +211 -96
- package/bundle/components/ListActionItem.md +7 -4
- package/bundle/components/ListItem.md +11 -8
- package/bundle/components/ListItemControlsButton.md +16 -13
- package/bundle/components/ListItemControlsButtonDropdown.md +9 -6
- package/bundle/components/ListItemSelectable.md +7 -4
- package/bundle/components/ListMasterFooter.md +8 -5
- package/bundle/components/ListMasterHeader.md +8 -5
- package/bundle/components/ListSortableItem.md +11 -8
- package/bundle/components/ListTableItem.md +7 -4
- package/bundle/components/LoaderMiddle.md +7 -4
- package/bundle/components/LoaderScreen.md +11 -8
- package/bundle/components/LoaderSmall.md +7 -4
- package/bundle/components/Marker.md +184 -69
- package/bundle/components/MarkerStatus.md +206 -88
- package/bundle/components/MaskedField.md +2 -4
- package/bundle/components/MasterTable.md +602 -0
- package/bundle/components/ModalWindow.md +7 -4
- package/bundle/components/MultiselectField.md +2183 -132
- package/bundle/components/Notification.md +12 -9
- package/bundle/components/Overlay.md +33 -5
- package/bundle/components/Page.md +2 -2
- package/bundle/components/Pagination.md +27 -21
- package/bundle/components/Row.md +1 -1
- package/bundle/components/SelectExtendedField.md +3 -3
- package/bundle/components/SelectField.md +466 -148
- package/bundle/components/SliderExtended.md +39 -36
- package/bundle/components/SmallInput.md +194 -51
- package/bundle/components/Suggest.md +535 -99
- package/bundle/components/SwipeableArea.md +428 -79
- package/bundle/components/TableBasic.md +2302 -0
- package/bundle/components/TabsExtended.md +24 -21
- package/bundle/components/Tag.md +263 -78
- package/bundle/components/Text.md +28 -25
- package/bundle/components/TextField.md +3 -4
- package/bundle/components/ThemeProvider.md +50 -1
- package/bundle/components/Title.md +26 -23
- package/bundle/components/Tooltip.md +24 -21
- package/bundle/components/TopOverlay.md +368 -89
- package/bundle/components/TreeView.md +343 -166
- package/bundle/components/TreeViewNode.md +436 -0
- package/bundle/guides/codestyle.md +3 -0
- package/bundle/guides/commits.md +106 -26
- package/bundle/guides/context.md +84 -12
- package/bundle/guides/guidelines.md +321 -0
- package/bundle/guides/template.md +22 -12
- package/bundle/manifest.json +54 -20
- package/bundle/release-notes/1.44.0.mdx +113 -0
- package/bundle/release-notes/1.45.0.mdx +124 -0
- package/bundle/release-notes/1.46.0.mdx +7 -0
- package/bundle/tokens.json +6362 -0
- package/dist/index.js +177 -28
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
component: DatePickerExtended
|
|
3
|
-
category:
|
|
4
|
-
|
|
5
|
-
figma-file: ""
|
|
6
|
-
related: [DateField, DateRange, MonthYearField, Calendar, Dropdown, FocusTrap]
|
|
3
|
+
category: Date components
|
|
4
|
+
related: [Calendar, Dropdown, DateField, MonthYearField, ChipDatePicker]
|
|
7
5
|
tokens: []
|
|
8
|
-
stories:
|
|
6
|
+
stories: stories/DatePickerExtended/DatePickerExtended.stories.tsx
|
|
9
7
|
version: "1.0"
|
|
10
8
|
---
|
|
11
9
|
|
|
@@ -13,330 +11,479 @@ version: "1.0"
|
|
|
13
11
|
|
|
14
12
|
## Назначение
|
|
15
13
|
|
|
16
|
-
|
|
17
|
-
(с desktop + mobile веткой), `Calendar` (внутри dropdown'а) и
|
|
18
|
-
`FocusTrap` (клавиатурная навигация). Управляет открытием/закрытием
|
|
19
|
-
dropdown'а (controlled/uncontrolled внутри себя — state), ловит
|
|
20
|
-
`Esc` для закрытия, отличает "мышиное" взаимодействие от "клавиатурного"
|
|
21
|
-
(флаг `mouseUsedRef`) для настройки `FocusTrap`.
|
|
14
|
+
Связка «произвольный целевой элемент + выпадающий календарь». Компонент рендерит целевой элемент (render-prop `renderTarget`) и `Calendar` внутри `Dropdown`, хранит состояние открытости календаря и отдаёт его целевому элементу через `DatePickerExtendedContext`. На мобильной ширине экрана вместо десктопного дропдауна открывается полноэкранный `DropdownMobile` с заголовком из `renderDropdownHeaderTarget`.
|
|
22
15
|
|
|
23
|
-
|
|
24
|
-
`MonthYearField`, но **render-props-интерфейс** (`renderTarget`,
|
|
25
|
-
`renderDropdownHeaderTarget`) делает его сложным для прямого
|
|
26
|
-
использования в приложениях — обычно его оборачивают.
|
|
16
|
+
Используй когда: строишь собственный компонент выбора даты с нестандартным триггером (поле, чип, кнопка) и хочешь переиспользовать готовую механику дропдауна с календарём. Именно так устроены `DateField`, `MonthYearField` и `ChipDatePicker`.
|
|
27
17
|
|
|
28
|
-
|
|
29
|
-
и своей валидацией — DatePickerExtended даёт dropdown + calendar +
|
|
30
|
-
focus management бесплатно.
|
|
31
|
-
Не используй как замену `DateField`/`DateRange`/`MonthYearField` —
|
|
32
|
-
бери эти готовые обёртки.
|
|
18
|
+
Не используй когда: подходит готовый компонент — поле ввода даты (`DateField`), выбор месяца и года (`MonthYearField`), диапазон (`DateRange`, `MonthYearRange`), чип-фильтр (`ChipDatePicker`); или календарь нужен без дропдауна — тогда бери `Calendar` напрямую.
|
|
33
19
|
|
|
34
20
|
---
|
|
35
21
|
|
|
36
|
-
##
|
|
22
|
+
## Варианты и props
|
|
37
23
|
|
|
38
|
-
|
|
39
|
-
src/components/DatePickerExtended/
|
|
40
|
-
├── DatePickerExtended.tsx # forwardRef, root
|
|
41
|
-
├── DatePickerExtendedContext.tsx # Context { dropdownOpen, mouseUsedRef, setDropdownOpen }
|
|
42
|
-
├── DatePickerExtendedDropdown.tsx # forwardRef, обёртка Dropdown + FocusTrap + mobile ветка
|
|
43
|
-
└── styles/DatePickerExtended.module.less # Только .datePickerExtendedMobileDropdown { max-height: 100vh }
|
|
44
|
-
```
|
|
24
|
+
`IDatePickerExtendedProps` собирается из трёх источников: собственные props (ниже), props календаря (`ICalendarProps` без `adaptiveMode`, включая `ICalendarNestedProps`) и `alignment` из `IDropdownProps`. Плюс `React.HTMLAttributes<HTMLDivElement>` без `onChange` — они уходят на корневой `div`.
|
|
45
25
|
|
|
46
|
-
|
|
47
|
-
- **Нет `index.ts`** — импортируется как `import { DatePickerExtended } from ".../DatePickerExtended/DatePickerExtended"`.
|
|
48
|
-
Это намеренно — компонент считается "полупубличным" (используется
|
|
49
|
-
внутри библиотеки и потребителями при необходимости, но без красивого
|
|
50
|
-
barrel-a).
|
|
51
|
-
- **Нет `__tests__`** — покрывается через тесты `DateField`.
|
|
52
|
-
- **Нет собственных stories** — только через обёртки.
|
|
53
|
-
- **Почти нет LESS** — один класс для мобильного max-height.
|
|
26
|
+
### Обязательные props
|
|
54
27
|
|
|
55
|
-
|
|
28
|
+
| Prop | Тип | Описание |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| `renderTarget` | `() => ReactNode` | Рендер целевого элемента. Вызывается внутри `DatePickerExtendedContext.Provider`, поэтому возвращённое дерево может читать и менять открытость календаря через контекст. |
|
|
31
|
+
| `renderDropdownHeaderTarget` | `() => ReactNode` | Рендер целевого элемента в заголовке мобильного дропдауна (`DropdownMobileHeader`). Обычно — `DropdownMobileInput` с текущим значением. |
|
|
32
|
+
| `pickedDate` | `string \| Moment \| null` | Выбранная дата (prop `Calendar`). Компонент неуправляемый только в части открытости — выбранную дату хранит потребитель. |
|
|
33
|
+
| `onDateChange` | `(date: Moment) => void` | Вызывается при выборе даты в календаре. Компонент дополнительно закрывает дропдаун. |
|
|
56
34
|
|
|
57
|
-
|
|
35
|
+
### Собственные опциональные props
|
|
58
36
|
|
|
59
|
-
|
|
37
|
+
| Prop | Тип | По умолчанию | Описание |
|
|
38
|
+
|---|---|---|---|
|
|
39
|
+
| `dropdownTargetRef` | `RefObject<HTMLElement>` | корневой элемент | Элемент, относительно которого позиционируется дропдаун. Нужен, когда целевой элемент занимает не всю ширину корневого `div` (так делает `DateField`). |
|
|
40
|
+
| `alignment` | `EDropdownAlignment` | `LEFT` | Выравнивание дропдауна относительно целевого элемента. |
|
|
41
|
+
| `onDropdownOpen` | `() => void` | — | Дропдаун открылся (проброшен в `Dropdown.onOpen`). |
|
|
42
|
+
| `onDropdownClose` | `() => void` | — | Дропдаун закрылся (проброшен в `Dropdown.onClose`). |
|
|
43
|
+
| `focusTrapProps` | `FocusTrapProps` | — | Props `FocusTrap` (`focus-trap-react`) вокруг десктопного календаря. Переданный `focusTrapOptions` объединяется с внутренним: пользовательские ключи имеют приоритет. |
|
|
60
44
|
|
|
61
|
-
|
|
62
|
-
extends Omit<React.HTMLAttributes<HTMLDivElement>, "onChange">,
|
|
63
|
-
Pick<IDropdownProps, "alignment">,
|
|
64
|
-
ICalendarProps
|
|
65
|
-
```
|
|
45
|
+
### Props календаря
|
|
66
46
|
|
|
67
|
-
|
|
47
|
+
Пробрасываются в `Calendar` как есть: `pickType`, `format`, `defaultViewDate`, `limitRange`, `markedDays`, `disabledDays`, `reversedPick`, `onPageChange`, `onViewChange`, `dayHtmlAttributes`, `monthHtmlAttributes`, `yearHtmlAttributes`, `prevButtonProps`, `nextButtonProps`, `viewButtonProps`, `yesterdayButtonProps`, `todayButtonProps`, `tomorrowButtonProps`. Семантика — в `Calendar-ai.md`.
|
|
68
48
|
|
|
69
|
-
|
|
70
|
-
|---|---|---|
|
|
71
|
-
| `renderTarget` | `() => React.ReactNode` | Target (поле ввода) на десктопе. |
|
|
72
|
-
| `renderDropdownHeaderTarget` | `() => React.ReactNode` | Target в заголовке dropdown'а в мобильном режиме (обычно тот же input, но в другом контейнере). |
|
|
49
|
+
Исключение — `adaptiveMode`: он входит в `ICalendarProps`, но исключён из `IDatePickerExtendedProps` через `Omit`. Режим определяет версия дропдауна (десктопный — `false`, мобильный — `true`), задать его снаружи нельзя.
|
|
73
50
|
|
|
74
|
-
|
|
51
|
+
Футер календаря (`yesterdayButtonProps`, `todayButtonProps`, `tomorrowButtonProps`) рендерится только при переданном `todayButtonProps` — без него «Вчера» и «Завтра» не появятся. «Вчера» и «Завтра» дополнительно требуют открытой страницы текущего месяца в режиме `DAYS`.
|
|
75
52
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
| `focusTrapProps` | `FocusTrapProps` | — | Прокидывается в `FocusTrap` (`focus-trap-react`). |
|
|
80
|
-
| `onDropdownOpen` | `() => void` | — | Callback при открытии. |
|
|
81
|
-
| `onDropdownClose` | `() => void` | — | Callback при закрытии. |
|
|
82
|
-
|
|
83
|
-
**Calendar (все пропсы `ICalendarProps` форвардятся):** `pickType`,
|
|
84
|
-
`format`, `defaultViewDate`, `pickedDate`, `limitRange`, `markedDays`,
|
|
85
|
-
`disabledDays`, `reversedPick`, `dayHtmlAttributes`, `monthHtmlAttributes`,
|
|
86
|
-
`yearHtmlAttributes`, `prevButtonProps`, `nextButtonProps`, `viewButtonProps`,
|
|
87
|
-
`todayButtonProps`, `onDateChange`, `onPageChange`, `onViewChange`.
|
|
88
|
-
|
|
89
|
-
**HTMLDivAttributes:** все, кроме `onChange` (конфликт с Calendar).
|
|
90
|
-
|
|
91
|
-
### `DatePickerExtendedContext`
|
|
92
|
-
|
|
93
|
-
```ts
|
|
94
|
-
interface IDatePickerExtendedContext {
|
|
95
|
-
dropdownOpen: boolean;
|
|
96
|
-
mouseUsedRef: React.MutableRefObject<boolean>;
|
|
97
|
-
setDropdownOpen: (open: boolean) => void;
|
|
98
|
-
}
|
|
99
|
-
```
|
|
53
|
+
### Что уходит на корневой элемент
|
|
54
|
+
|
|
55
|
+
Всё, что не перечислено выше (`className`, `style`, `id`, `data-*`, `aria-*`, обработчики), спредится на корневой `div`. Собственные `onKeyDown` и `onMouseDown` компонента вызываются первыми, пользовательские — после них (не отменяются).
|
|
100
56
|
|
|
101
|
-
|
|
102
|
-
дерева). `DateFieldTarget` и аналоги используют `useContext` для
|
|
103
|
-
управления dropdown'ом из input'а.
|
|
57
|
+
---
|
|
104
58
|
|
|
105
|
-
|
|
59
|
+
## Дизайн-токены
|
|
106
60
|
|
|
107
|
-
`
|
|
108
|
-
`renderCalendar`, `renderHeaderTarget`, `focusTrapProps`.
|
|
109
|
-
Не экспортируется публично.
|
|
61
|
+
Собственных CSS-токенов нет. Единственный стиль компонента — `datePickerExtendedMobileDropdown` (`max-height: 100vh` для мобильного дропдауна). Внешний вид определяется `Calendar`, `Dropdown` и целевым элементом.
|
|
110
62
|
|
|
111
63
|
---
|
|
112
64
|
|
|
113
|
-
##
|
|
65
|
+
## Инварианты
|
|
114
66
|
|
|
115
|
-
|
|
67
|
+
- `forwardRef` на компоненте — не убирать. Ref ведёт на корневой `div` и одновременно используется внутри как `containerRef` (позиционирование дропдауна и определение кликов «снаружи»), поэтому менять ref-target нельзя.
|
|
68
|
+
- Публичный API (`DatePickerExtended`, `IDatePickerExtendedProps`, `DatePickerExtendedContext`, `IDatePickerExtendedContext`) экспортируется через `src/components/DatePickerExtended/index.ts` — переименования props и смена их типов недопустимы.
|
|
69
|
+
- **Открыть календарь снаружи можно только через `DatePickerExtendedContext`** — управляемого prop открытости у компонента нет. Контекст экспортируется из barrel, поэтому целевой элемент пишется и внутри библиотеки (`DateField`, `MonthYearField`, `ChipDatePicker`), и в коде потребителя.
|
|
70
|
+
- Форма контекста (`dropdownOpen`, `mouseUsedRef`, `setDropdownOpen`) — часть публичного API и одновременно контракт трёх внутренних потребителей. Её изменение ломает `DateFieldTarget`, `MonthYearFieldTarget` и `ChipDatePickerTarget` — править только вместе с ними.
|
|
71
|
+
- `mouseUsedRef` — флаг «взаимодействие было мышью». Он выставляется на `mousedown` по корневому элементу и сбрасывается при закрытии дропдауна; от него зависят `initialFocus` и `returnFocusOnDeactivate` у `FocusTrap`. Не заменять на state: значение читается во время рендера дропдауна и не должно вызывать перерисовку.
|
|
72
|
+
- `DatePickerExtendedDropdown` в barrel не экспортируется.
|
|
73
|
+
- Размер дропдауна зафиксирован (`EComponentSize.MD`) и наружу не выведен.
|
|
74
|
+
- Компонент не хардкодит текст: подписи кнопок календаря и `aria-label` целевого элемента задаёт потребитель. Библиотека мультиязычная.
|
|
75
|
+
- React 17-совместимость (ветка `release-0`): не переводить на `useId` и другие React 18-only API.
|
|
116
76
|
|
|
117
|
-
|
|
118
|
-
const mouseUsedRef = useRef(false);
|
|
77
|
+
---
|
|
119
78
|
|
|
120
|
-
|
|
121
|
-
mouseUsedRef.current = true;
|
|
122
|
-
onMouseDown?.(event);
|
|
123
|
-
};
|
|
124
|
-
```
|
|
79
|
+
## Accessibility
|
|
125
80
|
|
|
126
|
-
|
|
127
|
-
|
|
81
|
+
- Дропдаун имеет `role="dialog"` и `aria-modal="true"`. Доступное имя диалога компонент не задаёт, и передать его через props нельзя: `...restProps` уходят на корневой `div`, а в `DatePickerExtendedDropdown` передаются только явно перечисленные props. Aria-атрибуты, переданные в `DatePickerExtended`, окажутся на корневом элементе, а не на диалоге.
|
|
82
|
+
- Десктопный календарь обёрнут в `FocusTrap` (`focus-trap-react`):
|
|
83
|
+
- при открытии с клавиатуры фокус уходит внутрь календаря, при закрытии возвращается на целевой элемент;
|
|
84
|
+
- при открытии мышью (`mouseUsedRef`) фокус не перехватывается и не возвращается — иначе после клика мышью фокус «прыгал» бы в календарь;
|
|
85
|
+
- `clickOutsideDeactivates: true`; `fallbackFocus` — контейнер календаря внутри ловушки (`tabIndex={-1}`), поэтому при отсутствии tabbable-элементов фокус остаётся внутри диалога. Задаётся ленивой функцией, т.к. на момент рендера ref ещё не заполнен; целевой элемент и `document.body` оставлены запасными вариантами, потому что focus-trap бросает исключение на пустом fallback.
|
|
86
|
+
- `Escape` на корневом элементе закрывает календарь (только когда он открыт), после чего вызывается пользовательский `onKeyDown`.
|
|
87
|
+
- `mousedown` вне корневого элемента и вне дропдауна закрывает календарь; клик внутри календаря — нет.
|
|
88
|
+
- Открытие календаря — ответственность целевого элемента: он же должен нести `aria-haspopup="dialog"` и `aria-expanded` (значение берётся из контекста).
|
|
89
|
+
- Клавиатурная навигация внутри сетки календаря — на стороне `Calendar`.
|
|
128
90
|
|
|
129
|
-
|
|
130
|
-
<FocusTrap
|
|
131
|
-
focusTrapOptions={{
|
|
132
|
-
initialFocus: !mouseUsedRef.current && undefined,
|
|
133
|
-
returnFocusOnDeactivate: !mouseUsedRef.current,
|
|
134
|
-
}}
|
|
135
|
-
>
|
|
136
|
-
```
|
|
91
|
+
---
|
|
137
92
|
|
|
138
|
-
|
|
139
|
-
программный initialFocus внутрь dropdown'а (пользователь не хочет,
|
|
140
|
-
чтобы фокус "прыгал") и не возвращать фокус на target при закрытии.
|
|
141
|
-
Если клавиатурой (Enter/Space на input) → focus-trap работает, по Esc
|
|
142
|
-
фокус вернётся на input.
|
|
143
|
-
|
|
144
|
-
**Это UX-тонкость**, нельзя просто удалить `mouseUsedRef` — пострадает
|
|
145
|
-
клавиатурная доступность.
|
|
146
|
-
|
|
147
|
-
### Outside mousedown → закрытие dropdown'а
|
|
148
|
-
|
|
149
|
-
```ts
|
|
150
|
-
useEffect(() => {
|
|
151
|
-
const handleOutsideMouseDown = (event: MouseEvent) => {
|
|
152
|
-
if (
|
|
153
|
-
!containerRef.current?.contains(event.target as Node) &&
|
|
154
|
-
!dropdownRef.current?.contains(event.target as Node)
|
|
155
|
-
) {
|
|
156
|
-
setDropdownOpen(false);
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
if (dropdownOpen) {
|
|
160
|
-
document.addEventListener("mousedown", handleOutsideMouseDown);
|
|
161
|
-
return () => document.removeEventListener("mousedown", handleOutsideMouseDown);
|
|
162
|
-
} else {
|
|
163
|
-
mouseUsedRef.current = false;
|
|
164
|
-
}
|
|
165
|
-
}, [dropdownOpen]);
|
|
166
|
-
```
|
|
93
|
+
## Связанные компоненты
|
|
167
94
|
|
|
168
|
-
|
|
169
|
-
`
|
|
170
|
-
`
|
|
171
|
-
|
|
95
|
+
- `Calendar` — календарная сетка, рендерится внутри дропдауна; её props входят в `IDatePickerExtendedProps`, кроме `adaptiveMode`.
|
|
96
|
+
- `Dropdown` — выпадающее меню; из его props наружу выведен только `alignment`, мобильная версия конфигурируется внутри.
|
|
97
|
+
- `DateField` — поле ввода даты; передаёт `dropdownTargetRef` и открывает календарь из `DateFieldTarget` через контекст.
|
|
98
|
+
- `MonthYearField` — выбор месяца и года; конфигурирует `pickType={ECalendarPickType.MONTH_YEAR}`.
|
|
99
|
+
- `ChipDatePicker` — чип-фильтр с календарём; открывает дропдаун из `ChipDatePickerTarget` через контекст.
|
|
100
|
+
- `DatePickerExtendedContext` — контекст открытости дропдауна для целевого элемента. Экспортируется из barrel вместе с типом `IDatePickerExtendedContext`, отдельного AI.md не имеет.
|
|
172
101
|
|
|
173
|
-
|
|
174
|
-
флаг становится `false`, чтобы следующее открытие корректно решало,
|
|
175
|
-
как вести себя с focus trap'ом.
|
|
102
|
+
Внутренние части (в barrel не экспортируются, отдельного AI.md не имеют):
|
|
176
103
|
|
|
177
|
-
|
|
104
|
+
- `DatePickerExtendedDropdown` — дропдаун с `FocusTrap` вокруг календаря и мобильной версткой (`DropdownMobileHeader` + `DropdownMobileBody`).
|
|
178
105
|
|
|
179
|
-
|
|
180
|
-
const handleKeyDown = (event) => {
|
|
181
|
-
if (isKey(key, "ESCAPE")) {
|
|
182
|
-
if (dropdownOpen) setDropdownOpen(false);
|
|
183
|
-
}
|
|
184
|
-
onKeyDown?.(event);
|
|
185
|
-
};
|
|
186
|
-
```
|
|
106
|
+
---
|
|
187
107
|
|
|
188
|
-
|
|
189
|
-
внутри input'а (фокус в target) → закрыть dropdown. Не фильтруется
|
|
190
|
-
`event.defaultPrevented` — если потребитель уже обработал Esc, он
|
|
191
|
-
всё равно закроет dropdown. Это компромисс.
|
|
108
|
+
## Stories
|
|
192
109
|
|
|
193
|
-
|
|
110
|
+
Основные истории: `stories/DatePickerExtended/DatePickerExtended.stories.tsx`
|
|
111
|
+
Файлы примеров: `stories/DatePickerExtended/examples/`
|
|
194
112
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
)
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
```
|
|
113
|
+
| Story | Example file | Что демонстрирует |
|
|
114
|
+
|---|---|---|
|
|
115
|
+
| `Playground` | `Playground.tsx` | Интерактивный контроль `alignment`, `pickType`, `reversedPick` и кнопок футера |
|
|
116
|
+
| `Default` | `Default.tsx` | Минимальный пример: кнопка-триггер открывает календарь через контекст |
|
|
117
|
+
| `Alignments` | `Alignments.tsx` | Выравнивание дропдауна LEFT / RIGHT |
|
|
118
|
+
| `PickTypes` | `PickTypes.tsx` | Выбор дня (DATE) и месяца с годом (MONTH_YEAR) |
|
|
119
|
+
| `WithMarkedAndDisabledDays` | `WithMarkedAndDisabledDays.tsx` | Отмеченные и недоступные дни, ограничение периода |
|
|
120
|
+
| `WithFooterButtons` | `WithFooterButtons.tsx` | Кнопки футера «Вчера» / «Сегодня» / «Завтра» |
|
|
121
|
+
| `VisualTests` | `VisualTests.tsx` | Скриншот-регрессия: раскрытый календарь с отметками, ограничением периода и футером (на xs — мобильный дропдаун) |
|
|
122
|
+
| `VisualTestsMonthYear` | `VisualTestsMonthYear.tsx` | Скриншот-регрессия: раскрытый календарь в режиме MONTH_YEAR |
|
|
123
|
+
| `VisualTestsAlignmentRight` | `VisualTestsAlignmentRight.tsx` | Скриншот-регрессия: раскрытый календарь с выравниванием RIGHT |
|
|
124
|
+
|
|
125
|
+
Примеры импортируют `DatePickerExtendedContext` из `@sberbusiness/triplex-next` наравне с самим компонентом — без контекста целевой элемент не может открыть календарь, см. «Инварианты».
|
|
209
126
|
|
|
210
|
-
|
|
211
|
-
другие размеры и поведение).
|
|
212
|
-
**`adaptiveMode=false`** — десктопный компактный календарь в dropdown'е.
|
|
127
|
+
---
|
|
213
128
|
|
|
214
|
-
|
|
129
|
+
## История изменений
|
|
130
|
+
|
|
131
|
+
| Дата | Изменение |
|
|
132
|
+
|---|---|
|
|
133
|
+
| 2026-08-24 | `DatePickerExtendedContext` и `IDatePickerExtendedContext` экспортируются из barrel — целевой элемент можно написать снаружи библиотеки. `adaptiveMode` исключён из `IDatePickerExtendedProps` (ломающее изменение, затрагивает также `DateField`, `MonthYearField`, `ChipDatePicker`) |
|
|
134
|
+
| 2026-08-20 | Создан документ. AI-рефакторинг, unit-тесты (`__tests__/DatePickerExtended.test.tsx`), stories по modern pattern. Исправлено: `yesterdayButtonProps` / `tomorrowButtonProps` пробрасываются в `Calendar`, `adaptiveMode` больше не попадает в DOM |
|
|
135
|
+
|
|
136
|
+
---
|
|
215
137
|
|
|
216
|
-
|
|
138
|
+
## Примеры использования
|
|
139
|
+
|
|
140
|
+
### Default
|
|
141
|
+
|
|
142
|
+
`stories/DatePickerExtended/examples/Default.tsx`
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
import React, { useContext, useState } from "react";
|
|
146
|
+
import moment from "moment";
|
|
147
|
+
import {
|
|
148
|
+
Button,
|
|
149
|
+
DatePickerExtended,
|
|
150
|
+
DatePickerExtendedContext,
|
|
151
|
+
DropdownMobileInput,
|
|
152
|
+
EButtonTheme,
|
|
153
|
+
EComponentSize,
|
|
154
|
+
} from "@sberbusiness/triplex-next";
|
|
155
|
+
|
|
156
|
+
const DATE_FORMAT = "YYYYMMDD";
|
|
157
|
+
const DISPLAY_FORMAT = "DD.MM.YYYY";
|
|
158
|
+
|
|
159
|
+
interface ITargetProps {
|
|
160
|
+
/** Отображаемое значение даты. */
|
|
161
|
+
children: React.ReactNode;
|
|
162
|
+
}
|
|
217
163
|
|
|
218
|
-
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
164
|
+
const Target = ({ children }: ITargetProps) => {
|
|
165
|
+
const { dropdownOpen, setDropdownOpen } = useContext(DatePickerExtendedContext);
|
|
166
|
+
|
|
167
|
+
return (
|
|
168
|
+
<Button
|
|
169
|
+
theme={EButtonTheme.SECONDARY}
|
|
170
|
+
size={EComponentSize.MD}
|
|
171
|
+
aria-haspopup="dialog"
|
|
172
|
+
aria-expanded={dropdownOpen}
|
|
173
|
+
onClick={() => setDropdownOpen(!dropdownOpen)}
|
|
174
|
+
>
|
|
175
|
+
{children}
|
|
176
|
+
</Button>
|
|
177
|
+
);
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
export const Default = () => {
|
|
181
|
+
const [value, setValue] = useState("19700115");
|
|
182
|
+
const displayedValue = moment(value, DATE_FORMAT).format(DISPLAY_FORMAT);
|
|
183
|
+
|
|
184
|
+
return (
|
|
185
|
+
<DatePickerExtended
|
|
186
|
+
pickedDate={value}
|
|
187
|
+
format={DATE_FORMAT}
|
|
188
|
+
onDateChange={(date) => setValue(date.format(DATE_FORMAT))}
|
|
189
|
+
renderTarget={() => <Target>{displayedValue}</Target>}
|
|
190
|
+
renderDropdownHeaderTarget={() => <DropdownMobileInput value={displayedValue} readOnly />}
|
|
191
|
+
/>
|
|
192
|
+
);
|
|
222
193
|
};
|
|
223
194
|
```
|
|
224
195
|
|
|
225
|
-
|
|
226
|
-
поведение, потребитель не может отключить. Если нужно "мультидневный
|
|
227
|
-
выбор" — Calendar не подходит, нужен другой компонент.
|
|
196
|
+
### Alignments
|
|
228
197
|
|
|
229
|
-
|
|
198
|
+
`stories/DatePickerExtended/examples/Alignments.tsx`
|
|
230
199
|
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
200
|
+
```tsx
|
|
201
|
+
import React, { useContext, useState } from "react";
|
|
202
|
+
import moment from "moment";
|
|
203
|
+
import {
|
|
204
|
+
Button,
|
|
205
|
+
DatePickerExtended,
|
|
206
|
+
DatePickerExtendedContext,
|
|
207
|
+
DropdownMobileInput,
|
|
208
|
+
EButtonTheme,
|
|
209
|
+
EComponentSize,
|
|
210
|
+
EDropdownAlignment,
|
|
211
|
+
} from "@sberbusiness/triplex-next";
|
|
212
|
+
|
|
213
|
+
const DATE_FORMAT = "YYYYMMDD";
|
|
214
|
+
const DISPLAY_FORMAT = "DD.MM.YYYY";
|
|
215
|
+
|
|
216
|
+
const ALIGNMENTS = Object.values(EDropdownAlignment);
|
|
217
|
+
|
|
218
|
+
interface ITargetProps {
|
|
219
|
+
/** Отображаемое значение даты. */
|
|
220
|
+
children: React.ReactNode;
|
|
221
|
+
}
|
|
238
222
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
223
|
+
const Target = ({ children }: ITargetProps) => {
|
|
224
|
+
const { dropdownOpen, setDropdownOpen } = useContext(DatePickerExtendedContext);
|
|
225
|
+
|
|
226
|
+
return (
|
|
227
|
+
<Button
|
|
228
|
+
theme={EButtonTheme.SECONDARY}
|
|
229
|
+
size={EComponentSize.MD}
|
|
230
|
+
aria-haspopup="dialog"
|
|
231
|
+
aria-expanded={dropdownOpen}
|
|
232
|
+
onClick={() => setDropdownOpen(!dropdownOpen)}
|
|
233
|
+
>
|
|
234
|
+
{children}
|
|
235
|
+
</Button>
|
|
236
|
+
);
|
|
237
|
+
};
|
|
246
238
|
|
|
247
|
-
|
|
239
|
+
interface IAlignmentItemProps {
|
|
240
|
+
/** Выравнивание выпадающего календаря относительно целевого элемента. */
|
|
241
|
+
alignment: EDropdownAlignment;
|
|
242
|
+
}
|
|
248
243
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
244
|
+
const AlignmentItem = ({ alignment }: IAlignmentItemProps) => {
|
|
245
|
+
const [value, setValue] = useState("19700115");
|
|
246
|
+
const displayedValue = moment(value, DATE_FORMAT).format(DISPLAY_FORMAT);
|
|
247
|
+
|
|
248
|
+
return (
|
|
249
|
+
<div>
|
|
250
|
+
<div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>{alignment.toUpperCase()}</div>
|
|
251
|
+
<DatePickerExtended
|
|
252
|
+
alignment={alignment}
|
|
253
|
+
pickedDate={value}
|
|
254
|
+
format={DATE_FORMAT}
|
|
255
|
+
onDateChange={(date) => setValue(date.format(DATE_FORMAT))}
|
|
256
|
+
renderTarget={() => <Target>{displayedValue}</Target>}
|
|
257
|
+
renderDropdownHeaderTarget={() => <DropdownMobileInput value={displayedValue} readOnly />}
|
|
258
|
+
/>
|
|
259
|
+
</div>
|
|
260
|
+
);
|
|
261
|
+
};
|
|
254
262
|
|
|
255
|
-
|
|
263
|
+
export const Alignments = () => (
|
|
264
|
+
<div style={{ display: "flex", gap: "48px", flexWrap: "wrap" }}>
|
|
265
|
+
{ALIGNMENTS.map((alignment) => (
|
|
266
|
+
<AlignmentItem key={alignment} alignment={alignment} />
|
|
267
|
+
))}
|
|
268
|
+
</div>
|
|
269
|
+
);
|
|
270
|
+
```
|
|
256
271
|
|
|
257
|
-
|
|
272
|
+
### PickTypes
|
|
258
273
|
|
|
259
|
-
|
|
274
|
+
`stories/DatePickerExtended/examples/PickTypes.tsx`
|
|
260
275
|
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
276
|
+
```tsx
|
|
277
|
+
import React, { useContext, useState } from "react";
|
|
278
|
+
import moment from "moment";
|
|
279
|
+
import {
|
|
280
|
+
Button,
|
|
281
|
+
DatePickerExtended,
|
|
282
|
+
DatePickerExtendedContext,
|
|
283
|
+
DropdownMobileInput,
|
|
284
|
+
ECalendarPickType,
|
|
285
|
+
EButtonTheme,
|
|
286
|
+
EComponentSize,
|
|
287
|
+
} from "@sberbusiness/triplex-next";
|
|
288
|
+
|
|
289
|
+
const DATE_FORMAT = "YYYYMMDD";
|
|
290
|
+
|
|
291
|
+
/** Формат отображения даты для каждого варианта выбора. */
|
|
292
|
+
const PICK_TYPE_TO_DISPLAY_FORMAT = {
|
|
293
|
+
[ECalendarPickType.DATE]: "DD.MM.YYYY",
|
|
294
|
+
[ECalendarPickType.MONTH_YEAR]: "MMMM YYYY",
|
|
266
295
|
};
|
|
267
|
-
```
|
|
268
296
|
|
|
269
|
-
|
|
297
|
+
const PICK_TYPES = Object.values(ECalendarPickType);
|
|
270
298
|
|
|
271
|
-
|
|
299
|
+
interface ITargetProps {
|
|
300
|
+
/** Отображаемое значение даты. */
|
|
301
|
+
children: React.ReactNode;
|
|
302
|
+
}
|
|
272
303
|
|
|
273
|
-
|
|
304
|
+
const Target = ({ children }: ITargetProps) => {
|
|
305
|
+
const { dropdownOpen, setDropdownOpen } = useContext(DatePickerExtendedContext);
|
|
306
|
+
|
|
307
|
+
return (
|
|
308
|
+
<Button
|
|
309
|
+
theme={EButtonTheme.SECONDARY}
|
|
310
|
+
size={EComponentSize.MD}
|
|
311
|
+
aria-haspopup="dialog"
|
|
312
|
+
aria-expanded={dropdownOpen}
|
|
313
|
+
onClick={() => setDropdownOpen(!dropdownOpen)}
|
|
314
|
+
>
|
|
315
|
+
{children}
|
|
316
|
+
</Button>
|
|
317
|
+
);
|
|
318
|
+
};
|
|
274
319
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
- **`mouseUsedRef.current = false` в else-ветке useEffect** — сбросить
|
|
280
|
-
флаг при каждом закрытии, иначе следующий цикл не получит корректный
|
|
281
|
-
initialFocus.
|
|
282
|
-
- **`role="dialog" aria-modal="true"`** на Dropdown — обязательно.
|
|
283
|
-
- **`clickOutsideDeactivates: true`** в FocusTrap — иначе outside-click
|
|
284
|
-
не закрывает dropdown (FocusTrap борется).
|
|
285
|
-
- **`setDropdownOpen(false)` в `handleChangeDate`** — выбор даты
|
|
286
|
-
закрывает dropdown. Неопциональное поведение.
|
|
287
|
-
- **Esc → закрытие независимо от defaultPrevented** — осознанное
|
|
288
|
-
решение. Не фильтровать.
|
|
289
|
-
- **Render-props `renderTarget` и `renderDropdownHeaderTarget`** —
|
|
290
|
-
единственный способ вставить target. Не принимать children.
|
|
320
|
+
interface IPickTypeItemProps {
|
|
321
|
+
/** Вариант выбора даты. */
|
|
322
|
+
pickType: ECalendarPickType;
|
|
323
|
+
}
|
|
291
324
|
|
|
292
|
-
|
|
325
|
+
const PickTypeItem = ({ pickType }: IPickTypeItemProps) => {
|
|
326
|
+
const [value, setValue] = useState("19700115");
|
|
327
|
+
const displayedValue = moment(value, DATE_FORMAT).format(PICK_TYPE_TO_DISPLAY_FORMAT[pickType]);
|
|
328
|
+
|
|
329
|
+
return (
|
|
330
|
+
<div>
|
|
331
|
+
<div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>{pickType.toUpperCase()}</div>
|
|
332
|
+
<DatePickerExtended
|
|
333
|
+
pickType={pickType}
|
|
334
|
+
pickedDate={value}
|
|
335
|
+
format={DATE_FORMAT}
|
|
336
|
+
onDateChange={(date) => setValue(date.format(DATE_FORMAT))}
|
|
337
|
+
renderTarget={() => <Target>{displayedValue}</Target>}
|
|
338
|
+
renderDropdownHeaderTarget={() => <DropdownMobileInput value={displayedValue} readOnly />}
|
|
339
|
+
/>
|
|
340
|
+
</div>
|
|
341
|
+
);
|
|
342
|
+
};
|
|
293
343
|
|
|
294
|
-
|
|
344
|
+
export const PickTypes = () => (
|
|
345
|
+
<div style={{ display: "flex", gap: "48px", flexWrap: "wrap" }}>
|
|
346
|
+
{PICK_TYPES.map((pickType) => (
|
|
347
|
+
<PickTypeItem key={pickType} pickType={pickType} />
|
|
348
|
+
))}
|
|
349
|
+
</div>
|
|
350
|
+
);
|
|
351
|
+
```
|
|
295
352
|
|
|
296
|
-
|
|
297
|
-
внутри dropdown'а, Tab ходит по кнопкам календаря.
|
|
298
|
-
- **`role="dialog" aria-modal="true"`** на Dropdown — семантика модала.
|
|
299
|
-
- **Esc** — закрывает dropdown.
|
|
300
|
-
- **`initialFocus`/`returnFocusOnDeactivate`** — условно, только для
|
|
301
|
-
клавиатурного открытия. При мышином открытии фокус не трогается
|
|
302
|
-
(UX-паттерн).
|
|
303
|
-
- **`<div role="presentation">`** вокруг календаря — убирает лишнюю
|
|
304
|
-
семантику. Сам Calendar имеет свою ARIA.
|
|
305
|
-
- **Mobile** — собственная разметка с `DropdownMobileHeader`/`Body`,
|
|
306
|
-
включая кнопку закрытия (`DropdownMobileClose`).
|
|
353
|
+
### WithMarkedAndDisabledDays
|
|
307
354
|
|
|
308
|
-
|
|
355
|
+
`stories/DatePickerExtended/examples/WithMarkedAndDisabledDays.tsx`
|
|
309
356
|
|
|
310
|
-
|
|
357
|
+
```tsx
|
|
358
|
+
import React, { useContext, useState } from "react";
|
|
359
|
+
import moment from "moment";
|
|
360
|
+
import {
|
|
361
|
+
Button,
|
|
362
|
+
DatePickerExtended,
|
|
363
|
+
DatePickerExtendedContext,
|
|
364
|
+
DropdownMobileInput,
|
|
365
|
+
ECalendarDateMarkType,
|
|
366
|
+
EButtonTheme,
|
|
367
|
+
EComponentSize,
|
|
368
|
+
} from "@sberbusiness/triplex-next";
|
|
369
|
+
|
|
370
|
+
const DATE_FORMAT = "YYYYMMDD";
|
|
371
|
+
const DISPLAY_FORMAT = "DD.MM.YYYY";
|
|
372
|
+
|
|
373
|
+
/** Отмеченные дни: тип отметки задаёт цвет маркера под числом. */
|
|
374
|
+
const MARKED_DAYS = {
|
|
375
|
+
"19700108": ECalendarDateMarkType.STANDARD,
|
|
376
|
+
"19700109": ECalendarDateMarkType.ATTENTION,
|
|
377
|
+
"19700112": ECalendarDateMarkType.CRITICAL,
|
|
378
|
+
};
|
|
311
379
|
|
|
312
|
-
|
|
313
|
-
|
|
380
|
+
/** Дни, недоступные для выбора. */
|
|
381
|
+
const DISABLED_DAYS = ["19700120", "19700121"];
|
|
314
382
|
|
|
315
|
-
|
|
383
|
+
/** Ограничение выбираемого периода. */
|
|
384
|
+
const LIMIT_RANGE = {
|
|
385
|
+
dateFrom: moment("19700105", DATE_FORMAT),
|
|
386
|
+
dateTo: moment("19700125", DATE_FORMAT),
|
|
387
|
+
};
|
|
316
388
|
|
|
317
|
-
|
|
389
|
+
interface ITargetProps {
|
|
390
|
+
/** Отображаемое значение даты. */
|
|
391
|
+
children: React.ReactNode;
|
|
392
|
+
}
|
|
318
393
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
-
|
|
327
|
-
|
|
394
|
+
const Target = ({ children }: ITargetProps) => {
|
|
395
|
+
const { dropdownOpen, setDropdownOpen } = useContext(DatePickerExtendedContext);
|
|
396
|
+
|
|
397
|
+
return (
|
|
398
|
+
<Button
|
|
399
|
+
theme={EButtonTheme.SECONDARY}
|
|
400
|
+
size={EComponentSize.MD}
|
|
401
|
+
aria-haspopup="dialog"
|
|
402
|
+
aria-expanded={dropdownOpen}
|
|
403
|
+
onClick={() => setDropdownOpen(!dropdownOpen)}
|
|
404
|
+
>
|
|
405
|
+
{children}
|
|
406
|
+
</Button>
|
|
407
|
+
);
|
|
408
|
+
};
|
|
328
409
|
|
|
329
|
-
|
|
410
|
+
export const WithMarkedAndDisabledDays = () => {
|
|
411
|
+
const [value, setValue] = useState("19700115");
|
|
412
|
+
const displayedValue = moment(value, DATE_FORMAT).format(DISPLAY_FORMAT);
|
|
413
|
+
|
|
414
|
+
return (
|
|
415
|
+
<DatePickerExtended
|
|
416
|
+
pickedDate={value}
|
|
417
|
+
format={DATE_FORMAT}
|
|
418
|
+
limitRange={LIMIT_RANGE}
|
|
419
|
+
markedDays={MARKED_DAYS}
|
|
420
|
+
disabledDays={DISABLED_DAYS}
|
|
421
|
+
onDateChange={(date) => setValue(date.format(DATE_FORMAT))}
|
|
422
|
+
renderTarget={() => <Target>{displayedValue}</Target>}
|
|
423
|
+
renderDropdownHeaderTarget={() => <DropdownMobileInput value={displayedValue} readOnly />}
|
|
424
|
+
/>
|
|
425
|
+
);
|
|
426
|
+
};
|
|
427
|
+
```
|
|
330
428
|
|
|
331
|
-
|
|
429
|
+
### WithFooterButtons
|
|
332
430
|
|
|
333
|
-
|
|
334
|
-
`DateField`/`DateRange`/`MonthYearField`.
|
|
431
|
+
`stories/DatePickerExtended/examples/WithFooterButtons.tsx`
|
|
335
432
|
|
|
336
|
-
|
|
433
|
+
```tsx
|
|
434
|
+
import React, { useContext, useState } from "react";
|
|
435
|
+
import moment from "moment";
|
|
436
|
+
import {
|
|
437
|
+
Button,
|
|
438
|
+
DatePickerExtended,
|
|
439
|
+
DatePickerExtendedContext,
|
|
440
|
+
DropdownMobileInput,
|
|
441
|
+
EButtonTheme,
|
|
442
|
+
EComponentSize,
|
|
443
|
+
} from "@sberbusiness/triplex-next";
|
|
444
|
+
|
|
445
|
+
const DATE_FORMAT = "YYYYMMDD";
|
|
446
|
+
const DISPLAY_FORMAT = "DD.MM.YYYY";
|
|
447
|
+
|
|
448
|
+
interface ITargetProps {
|
|
449
|
+
/** Отображаемое значение даты. */
|
|
450
|
+
children: React.ReactNode;
|
|
451
|
+
}
|
|
337
452
|
|
|
338
|
-
|
|
453
|
+
const Target = ({ children }: ITargetProps) => {
|
|
454
|
+
const { dropdownOpen, setDropdownOpen } = useContext(DatePickerExtendedContext);
|
|
455
|
+
|
|
456
|
+
return (
|
|
457
|
+
<Button
|
|
458
|
+
theme={EButtonTheme.SECONDARY}
|
|
459
|
+
size={EComponentSize.MD}
|
|
460
|
+
aria-haspopup="dialog"
|
|
461
|
+
aria-expanded={dropdownOpen}
|
|
462
|
+
onClick={() => setDropdownOpen(!dropdownOpen)}
|
|
463
|
+
>
|
|
464
|
+
{children}
|
|
465
|
+
</Button>
|
|
466
|
+
);
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
// Кнопки «Вчера» и «Завтра» календарь показывает только на странице текущего месяца,
|
|
470
|
+
// кнопку «Сегодня» — всегда. Текст кнопок задаёт потребитель.
|
|
471
|
+
export const WithFooterButtons = () => {
|
|
472
|
+
const [value, setValue] = useState(moment().format(DATE_FORMAT));
|
|
473
|
+
const displayedValue = moment(value, DATE_FORMAT).format(DISPLAY_FORMAT);
|
|
474
|
+
|
|
475
|
+
return (
|
|
476
|
+
<DatePickerExtended
|
|
477
|
+
pickedDate={value}
|
|
478
|
+
format={DATE_FORMAT}
|
|
479
|
+
yesterdayButtonProps={{ children: "Вчера" }}
|
|
480
|
+
todayButtonProps={{ children: "Сегодня" }}
|
|
481
|
+
tomorrowButtonProps={{ children: "Завтра" }}
|
|
482
|
+
onDateChange={(date) => setValue(date.format(DATE_FORMAT))}
|
|
483
|
+
renderTarget={() => <Target>{displayedValue}</Target>}
|
|
484
|
+
renderDropdownHeaderTarget={() => <DropdownMobileInput value={displayedValue} readOnly />}
|
|
485
|
+
/>
|
|
486
|
+
);
|
|
487
|
+
};
|
|
488
|
+
```
|
|
339
489
|
|
|
340
|
-
| Дата | Изменение |
|
|
341
|
-
|---|---|
|
|
342
|
-
| 2026-04-18 | Создан документ |
|