@sberbusiness/triplex-next-mcp-server 0.24.0-beta1
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 +124 -0
- package/bundle/.gitkeep +0 -0
- package/bundle/components/AbstractTree.md +198 -0
- package/bundle/components/AccordionBase.md +227 -0
- package/bundle/components/Alert.md +317 -0
- package/bundle/components/Amount.md +225 -0
- package/bundle/components/AmountField.md +289 -0
- package/bundle/components/Avatar.md +209 -0
- package/bundle/components/Badge.md +277 -0
- package/bundle/components/Body.md +167 -0
- package/bundle/components/Button.md +230 -0
- package/bundle/components/ButtonDropdown.md +159 -0
- package/bundle/components/ButtonIcon.md +122 -0
- package/bundle/components/Calendar.md +445 -0
- package/bundle/components/Card.md +374 -0
- package/bundle/components/CarouselExtended.md +250 -0
- package/bundle/components/Checkbox.md +126 -0
- package/bundle/components/CheckboxTree.md +280 -0
- package/bundle/components/CheckboxTreeExtended.md +321 -0
- package/bundle/components/CheckboxXGroup.md +68 -0
- package/bundle/components/CheckboxYGroup.md +67 -0
- package/bundle/components/Chip.md +404 -0
- package/bundle/components/ChipGroup.md +179 -0
- package/bundle/components/Col.md +221 -0
- package/bundle/components/CollapsableTree.md +255 -0
- package/bundle/components/Confirm.md +343 -0
- package/bundle/components/DateField.md +371 -0
- package/bundle/components/DatePickerExtended.md +342 -0
- package/bundle/components/DateRange.md +275 -0
- package/bundle/components/DesignTokens.md +385 -0
- package/bundle/components/Divider.md +196 -0
- package/bundle/components/DocumentNumberEdit.md +280 -0
- package/bundle/components/Dropdown.md +433 -0
- package/bundle/components/Ellipsis.md +208 -0
- package/bundle/components/EmptyView.md +99 -0
- package/bundle/components/ExpandAnimation.md +281 -0
- package/bundle/components/Footer.md +339 -0
- package/bundle/components/FormField.md +326 -0
- package/bundle/components/FormGroup.md +99 -0
- package/bundle/components/Gap.md +113 -0
- package/bundle/components/Header.md +280 -0
- package/bundle/components/HelpBox.md +154 -0
- package/bundle/components/IconWrapper.md +145 -0
- package/bundle/components/Island.md +197 -0
- package/bundle/components/IslandAccordion.md +230 -0
- package/bundle/components/IslandWidget.md +233 -0
- package/bundle/components/KeyDownListener.md +157 -0
- package/bundle/components/LightBox.md +322 -0
- package/bundle/components/Link.md +161 -0
- package/bundle/components/List.md +247 -0
- package/bundle/components/ListMaster.md +204 -0
- package/bundle/components/Loader.md +161 -0
- package/bundle/components/LoaderScreen.md +134 -0
- package/bundle/components/Marker.md +127 -0
- package/bundle/components/MarkerStatus.md +148 -0
- package/bundle/components/MediaWidth.md +169 -0
- package/bundle/components/MobileView.md +120 -0
- package/bundle/components/ModalWindow.md +308 -0
- package/bundle/components/MonthYearField.md +168 -0
- package/bundle/components/MultiselectField.md +213 -0
- package/bundle/components/Notification.md +233 -0
- package/bundle/components/NumberField.md +178 -0
- package/bundle/components/OrderedList.md +169 -0
- package/bundle/components/Overlay.md +237 -0
- package/bundle/components/Page.md +246 -0
- package/bundle/components/Pagination.md +298 -0
- package/bundle/components/Portal.md +119 -0
- package/bundle/components/Radio.md +174 -0
- package/bundle/components/Row.md +125 -0
- package/bundle/components/SMSField.md +160 -0
- package/bundle/components/SegmentedControl.md +235 -0
- package/bundle/components/SelectExtendedField.md +322 -0
- package/bundle/components/SelectField.md +213 -0
- package/bundle/components/Skeleton.md +129 -0
- package/bundle/components/Slider.md +157 -0
- package/bundle/components/SliderExtended.md +210 -0
- package/bundle/components/SliderRange.md +160 -0
- package/bundle/components/SmallInput.md +101 -0
- package/bundle/components/Spoiler.md +121 -0
- package/bundle/components/StatusTracker.md +151 -0
- package/bundle/components/Step.md +121 -0
- package/bundle/components/Stepper.md +163 -0
- package/bundle/components/Suggest.md +160 -0
- package/bundle/components/SuggestField.md +157 -0
- package/bundle/components/SwipeableArea.md +144 -0
- package/bundle/components/Table.md +199 -0
- package/bundle/components/Tabs.md +152 -0
- package/bundle/components/TabsExtended.md +273 -0
- package/bundle/components/TabsLine.md +189 -0
- package/bundle/components/Tag.md +135 -0
- package/bundle/components/TagColor.md +121 -0
- package/bundle/components/TagGroup.md +100 -0
- package/bundle/components/TextField.md +152 -0
- package/bundle/components/TextareaField.md +124 -0
- package/bundle/components/ThemeProvider.md +181 -0
- package/bundle/components/Tooltip.md +254 -0
- package/bundle/components/TopOverlay.md +145 -0
- package/bundle/components/TreeView.md +224 -0
- package/bundle/components/Triggers.md +121 -0
- package/bundle/guides/codestyle.md +205 -0
- package/bundle/guides/commits.md +166 -0
- package/bundle/guides/context.md +277 -0
- package/bundle/guides/stories.md +618 -0
- package/bundle/guides/template.md +154 -0
- package/bundle/guides/tests.md +301 -0
- package/bundle/icons.json +4156 -0
- package/bundle/manifest.json +796 -0
- package/bundle/release-notes/1.1.0.mdx +26 -0
- package/bundle/release-notes/1.1.1.mdx +14 -0
- package/bundle/release-notes/1.10.0.mdx +43 -0
- package/bundle/release-notes/1.11.0.mdx +19 -0
- package/bundle/release-notes/1.12.0.mdx +21 -0
- package/bundle/release-notes/1.13.0.mdx +43 -0
- package/bundle/release-notes/1.14.0.mdx +29 -0
- package/bundle/release-notes/1.15.0.mdx +56 -0
- package/bundle/release-notes/1.16.0.mdx +55 -0
- package/bundle/release-notes/1.17.0.mdx +15 -0
- package/bundle/release-notes/1.18.0.mdx +24 -0
- package/bundle/release-notes/1.19.0.mdx +48 -0
- package/bundle/release-notes/1.19.1.mdx +9 -0
- package/bundle/release-notes/1.2.0.mdx +80 -0
- package/bundle/release-notes/1.20.0.mdx +48 -0
- package/bundle/release-notes/1.21.0.mdx +66 -0
- package/bundle/release-notes/1.22.0.mdx +75 -0
- package/bundle/release-notes/1.23.0.mdx +50 -0
- package/bundle/release-notes/1.24.0.mdx +59 -0
- package/bundle/release-notes/1.25.0.mdx +52 -0
- package/bundle/release-notes/1.26.0.mdx +11 -0
- package/bundle/release-notes/1.3.0.mdx +21 -0
- package/bundle/release-notes/1.4.0.mdx +37 -0
- package/bundle/release-notes/1.5.0.mdx +24 -0
- package/bundle/release-notes/1.6.0.mdx +37 -0
- package/bundle/release-notes/1.6.1.mdx +11 -0
- package/bundle/release-notes/1.7.0.mdx +29 -0
- package/bundle/release-notes/1.8.0.mdx +38 -0
- package/bundle/release-notes/1.9.0.mdx +28 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +324 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: DateField
|
|
3
|
+
category: Form
|
|
4
|
+
figma-node: ""
|
|
5
|
+
figma-file: ""
|
|
6
|
+
related: [DatePickerExtended, Calendar, MaskedField, MonthYearField, DateRange, Tooltip, MobileView]
|
|
7
|
+
tokens: []
|
|
8
|
+
stories: stories/DateField/DateField.stories.tsx
|
|
9
|
+
version: "1.0"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# DateField
|
|
13
|
+
|
|
14
|
+
## Назначение
|
|
15
|
+
|
|
16
|
+
Поле ввода даты с **маской** (`ДД.ММ.ГГГГ`) + выпадающим календарём
|
|
17
|
+
(`DatePickerExtended`). Объединяет текстовый ввод с пикером, валидирует
|
|
18
|
+
дату (`moment`), синхронизирует состояние input ↔ calendar ↔ внешний
|
|
19
|
+
`value`, показывает подсказку-tooltip при невалидном вводе на десктопе.
|
|
20
|
+
|
|
21
|
+
На мобильных (`MobileView`) ведёт себя как `Dropdown` с масочным input'ом
|
|
22
|
+
в заголовке (`DropdownMobileMaskedInput`), без tooltip'а.
|
|
23
|
+
|
|
24
|
+
`DateField` — **controlled-only**. `value: string` в формате `format`
|
|
25
|
+
(default `YYYY-MM-DD`), `onChange(value: string)` — реакция на изменение.
|
|
26
|
+
|
|
27
|
+
Используй когда: нужно однократное поле даты в форме с возможностью
|
|
28
|
+
ввести руками **или** выбрать в календаре.
|
|
29
|
+
Не используй когда: нужен диапазон дат — бери `DateRange`; нужен выбор
|
|
30
|
+
только месяца/года — `MonthYearField`.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Файловая структура
|
|
35
|
+
|
|
36
|
+
```text
|
|
37
|
+
src/components/DateField/
|
|
38
|
+
├── DateField.tsx # forwardRef<HTMLDivElement>, root FC
|
|
39
|
+
├── DateFieldTarget.tsx # FC, target для DatePickerExtended (MaskedField + иконка календаря + clear)
|
|
40
|
+
├── DateFieldContext.ts # React.Context с inputFocusedRef, onChange, triggerChangeFromInput
|
|
41
|
+
├── types.ts # IDateFieldProps
|
|
42
|
+
├── utils.ts # DateFieldUtils (getPickerValues, getCalendarDate, isAvailableDate)
|
|
43
|
+
├── constants.ts # inputDateFormat = "DD.MM.YYYY"
|
|
44
|
+
├── index.ts # Barrel
|
|
45
|
+
└── __tests__/DateField.test.tsx
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Особенности:
|
|
49
|
+
- **`displayName = "DatePicker"`** (не `"DateField"`) — известная
|
|
50
|
+
особенность, исторически. Не переименовывать без согласования — может
|
|
51
|
+
сломать snapshot-тесты потребителей.
|
|
52
|
+
- **Нет собственных стилей** — вся визуалка от `DatePickerExtended` +
|
|
53
|
+
`MaskedField`.
|
|
54
|
+
- **Нет собственных токенов** — наследуется от формы и календаря.
|
|
55
|
+
- Есть `__tests__/DateField.test.tsx` — unit-тесты ввода/валидации.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Варианты и props
|
|
60
|
+
|
|
61
|
+
### `IDateFieldProps`
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
extends Omit<IDatePickerExtendedProps,
|
|
65
|
+
"pickedDate" | "onDateChange" | "renderTarget" | "renderDropdownHeaderTarget">,
|
|
66
|
+
Pick<IMaskedFieldProps, "size" | "status" | "label">
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Основные:**
|
|
70
|
+
|
|
71
|
+
| Prop | Тип | Default | Описание |
|
|
72
|
+
|---|---|---|---|
|
|
73
|
+
| `value` | `string` | — | **Controlled** — дата в формате `format`. Пустая строка = нет даты. |
|
|
74
|
+
| `onChange` | `(value: string) => void` | — | Вызывается при валидном вводе/выборе. `value` может быть пустой строкой (очистка). |
|
|
75
|
+
| `format` | `string` | `dateFormatYYYYMMDD` (`"YYYY-MM-DD"`) | moment-формат для `value`. **Не путать с `inputDateFormat` = `"DD.MM.YYYY"`** (формат отображения в маске). |
|
|
76
|
+
| `limitRange` | `IDateLimitRange` | `globalLimitRange` | `{ dateFrom, dateTo }` — допустимый диапазон. |
|
|
77
|
+
| `disabledDays` | `string[]` | — | Массив дат в формате `format`, которые нельзя выбрать. |
|
|
78
|
+
| `placeholderMask` | `string` | — | Плейсхолдер-символы для пустых позиций в маске (обычно `"дд.мм.гггг"`). |
|
|
79
|
+
| `invalidDateHint` | `ReactNode` | — | Текст в тултипе при невалидной введённой дате. |
|
|
80
|
+
| `size` | `EComponentSize` | `MD` | Размер MaskedField (SM/MD/LG). |
|
|
81
|
+
| `status` | `EFormFieldStatus` | — | Валидация + disabled. |
|
|
82
|
+
| `label` | `ReactNode` | — | Label над полем. |
|
|
83
|
+
| `aria-label` / `aria-labelledby` | `string` | — | Прокидываются в MaskedInput. |
|
|
84
|
+
| `targetProps` | `DeepPartial<IMaskedFieldProps>` | — | Глубокий частичный override для MaskedField. Позволяет точечно перебить. |
|
|
85
|
+
| `onDropdownOpen` | `() => void` | — | Coincidence callback при открытии календаря. |
|
|
86
|
+
| `onDropdownClose` | `() => void` | — | Callback при закрытии (запускает trigger, если input дирти). |
|
|
87
|
+
| `...IDatePickerExtendedProps` | — | — | Остальные пропсы пикера (минус 4 omitted). |
|
|
88
|
+
|
|
89
|
+
### `DateFieldContext` — внутренний
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
interface IDateFieldContext {
|
|
93
|
+
inputFocusedRef: MutableRefObject<boolean | null>;
|
|
94
|
+
onChange: (value: string) => void;
|
|
95
|
+
triggerChangeFromInput: () => void;
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Контекст существует **только для передачи callback'ов в `DateFieldTarget`**,
|
|
100
|
+
который живёт внутри `DatePickerExtended` (другая ветка дерева). Публично
|
|
101
|
+
не экспортируется.
|
|
102
|
+
|
|
103
|
+
### `DateFieldUtils`
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
{
|
|
107
|
+
getPickerValues(value, format, limitRange, disabledDays): { inputString, calendarDate }
|
|
108
|
+
getCalendarDate(value, format, limitRange, disabledDays): Moment | null
|
|
109
|
+
isAvailableDate(date, value, limitRange, disabledDays): boolean
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Конвертирует внешний `value` (в `format`) → внутреннее состояние
|
|
114
|
+
(`inputString` в `inputDateFormat` + `calendarDate` Moment|null).
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Ключевые особенности реализации
|
|
119
|
+
|
|
120
|
+
### Два формата: `format` (внешний) vs `inputDateFormat` (внутренний)
|
|
121
|
+
|
|
122
|
+
- **Внешний `value`** и `onChange` — в moment-формате, по умолчанию
|
|
123
|
+
`YYYY-MM-DD`. Настраивается через пропс `format`.
|
|
124
|
+
- **Внутренний ввод (маска)** — всегда `"DD.MM.YYYY"`. Захардкожен
|
|
125
|
+
в `constants.ts`.
|
|
126
|
+
|
|
127
|
+
**Почему:** визуальный формат — фирменный российский (дд.мм.гггг), а
|
|
128
|
+
внутренний API — ISO для backend. Конвертация через `moment(date,
|
|
129
|
+
format)` / `date.format(inputDateFormat)`.
|
|
130
|
+
|
|
131
|
+
### Локальное состояние `pickerValues = { inputString, calendarDate }`
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
const [pickerValues, setPickerValues] = useState(
|
|
135
|
+
DateFieldUtils.getPickerValues(value, format, limitRange, disabledDays),
|
|
136
|
+
);
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Хранит **и** строку для input'а, **и** moment-дату для календаря.
|
|
140
|
+
Синхронизируется с внешним `value` через `useEffect`:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
useEffect(() => {
|
|
144
|
+
const newPickerValues = DateFieldUtils.getPickerValues(...);
|
|
145
|
+
if (newPickerValues.inputString !== pickerValues.inputString) {
|
|
146
|
+
setPickerValues(newPickerValues);
|
|
147
|
+
}
|
|
148
|
+
lastValidPickerValuesRef.current = newPickerValues;
|
|
149
|
+
}, [value, format, limitRange, disabledDays]);
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**Важно:** сравнение по `inputString`, не по `value`. Причина — если
|
|
153
|
+
пользователь частично отредактировал строку (`"12.05."`, невалидно),
|
|
154
|
+
внешний value не менялся, новый эффект пришёл бы с тем же value, и
|
|
155
|
+
мы **не должны** сбрасывать ввод пользователя. `lastValidPickerValuesRef`
|
|
156
|
+
хранит последнее валидное состояние для отката.
|
|
157
|
+
|
|
158
|
+
### Откат при невалидном вводе
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
const triggerChangeFromInput = () => {
|
|
162
|
+
if (pickerValues.inputString.length === 0 && value.length !== 0) {
|
|
163
|
+
return onChange(pickerValues.inputString); // очистка
|
|
164
|
+
}
|
|
165
|
+
const date = moment(pickerValues.inputString, inputDateFormat, true);
|
|
166
|
+
if (date.isValid()) {
|
|
167
|
+
const newValue = date.format(format);
|
|
168
|
+
if (newValue === value) return;
|
|
169
|
+
if (DateFieldUtils.isAvailableDate(...)) {
|
|
170
|
+
return onChange(newValue);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// Откат к последнему валидному
|
|
174
|
+
if (pickerValues.inputString !== lastValidPickerValuesRef.current.inputString) {
|
|
175
|
+
tooltipOpened.current = false;
|
|
176
|
+
setPickerValues(lastValidPickerValuesRef.current);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Триггерится при blur input'а (если dropdown закрыт) и при закрытии
|
|
182
|
+
dropdown'а (если input был расфокусирован и грязный). **Логика "не дать
|
|
183
|
+
пользователю повесить невалидную дату"** — UX-решение.
|
|
184
|
+
|
|
185
|
+
### Tooltip с `invalidDateHint` через ref
|
|
186
|
+
|
|
187
|
+
```tsx
|
|
188
|
+
<MobileView
|
|
189
|
+
fallback={
|
|
190
|
+
<Tooltip targetRef={tooltipTargetRef} size={SM} isOpen={tooltipOpened.current}>
|
|
191
|
+
<Tooltip.Body>{invalidDateHint}</Tooltip.Body>
|
|
192
|
+
<Tooltip.Target>{renderDatePickerExtended()}</Tooltip.Target>
|
|
193
|
+
</Tooltip>
|
|
194
|
+
}
|
|
195
|
+
>
|
|
196
|
+
{renderDatePickerExtended()}
|
|
197
|
+
</MobileView>
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Tooltip появляется **только на десктопе** и **только при `tooltipOpened.current === true`**
|
|
201
|
+
(= длина ввода = длина маски, но дата невалидна). На мобильных — нет
|
|
202
|
+
tooltip'а, нет подсказки; валидация через собственный UI потребителя.
|
|
203
|
+
|
|
204
|
+
### Mobile-ветка: `DropdownMobileMaskedInput`
|
|
205
|
+
|
|
206
|
+
```tsx
|
|
207
|
+
const renderDropdownHeaderTarget = () => (
|
|
208
|
+
<DropdownMobileMaskedInput
|
|
209
|
+
value={pickerValues.inputString}
|
|
210
|
+
mask={DropdownMobileMaskedInput.presets.masks.date}
|
|
211
|
+
placeholderMask={placeholderMask}
|
|
212
|
+
onChange={handleInputChange}
|
|
213
|
+
autoFocus={true}
|
|
214
|
+
/>
|
|
215
|
+
);
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
На мобильных `DatePickerExtended` открывается как Dropdown с **инпутом
|
|
219
|
+
в заголовке** (не отдельным target'ом). `autoFocus={true}` ставит курсор
|
|
220
|
+
в поле при открытии.
|
|
221
|
+
|
|
222
|
+
### `DateFieldTarget` живёт в чужой ветке дерева
|
|
223
|
+
|
|
224
|
+
`DateFieldTarget` рендерится внутри `DatePickerExtended` (target-prop).
|
|
225
|
+
Он не прямой потомок `DateField`, поэтому **обмен с корнем идёт через
|
|
226
|
+
`DateFieldContext`** (inputFocusedRef, onChange, triggerChangeFromInput).
|
|
227
|
+
|
|
228
|
+
**Это известная архитектурная особенность**: Target — это HOC над
|
|
229
|
+
MaskedField + calendar icon + clear button + keyboard (`Enter`/`Space`
|
|
230
|
+
открывают dropdown).
|
|
231
|
+
|
|
232
|
+
### `setRef` — double-duty ref
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
const setRef = (instance: HTMLDivElement | null) => {
|
|
236
|
+
tooltipTargetRef.current = instance;
|
|
237
|
+
if (typeof ref === "function") ref(instance);
|
|
238
|
+
else if (ref) ref.current = instance;
|
|
239
|
+
};
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Один и тот же DOM-элемент используется:
|
|
243
|
+
1. Как target для Tooltip (`tooltipTargetRef`).
|
|
244
|
+
2. Как forwarded ref для потребителя.
|
|
245
|
+
|
|
246
|
+
### `dropdownClosedByCalendarRef` — флаг "закрыто по выбору даты"
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
const dropdownClosedByCalendarRef = useRef(false);
|
|
250
|
+
// ...
|
|
251
|
+
const handleDateChange = (date: moment.Moment) => {
|
|
252
|
+
dropdownClosedByCalendarRef.current = true;
|
|
253
|
+
// ...
|
|
254
|
+
};
|
|
255
|
+
const handleDropdownClose = () => {
|
|
256
|
+
if (dropdownClosedByCalendarRef.current) {
|
|
257
|
+
dropdownClosedByCalendarRef.current = false; // reset
|
|
258
|
+
} else if (!inputFocusedRef.current && dirty) {
|
|
259
|
+
triggerChangeFromInput(); // откат/commit
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
**Защита от двойной обработки**: если dropdown закрылся потому что
|
|
265
|
+
пользователь выбрал дату в календаре, не дергать `triggerChangeFromInput`
|
|
266
|
+
— значение уже применено через `onChange` в `handleDateChange`.
|
|
267
|
+
|
|
268
|
+
### DateFieldTarget: клавиатурная навигация
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
if (isKey(event.code, "ENTER")) setDropdownOpen(!dropdownOpen);
|
|
272
|
+
else if (isKey(event.code, "SPACE")) {
|
|
273
|
+
event.preventDefault();
|
|
274
|
+
setDropdownOpen(!dropdownOpen);
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
**Enter / Space** открывают/закрывают dropdown. `preventDefault` на
|
|
279
|
+
Space нужен, чтобы не ввести пробел в input (маска бы его съела,
|
|
280
|
+
но всё равно нежелательно).
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## Инварианты
|
|
285
|
+
|
|
286
|
+
- **`forwardRef`** на root — обязателен (HTMLDivElement).
|
|
287
|
+
- **`displayName = "DatePicker"`** — исторический, не менять.
|
|
288
|
+
- **`value` — controlled only** — нет uncontrolled-ветки. Пустая строка
|
|
289
|
+
= нет даты.
|
|
290
|
+
- **`format` ≠ `inputDateFormat`** — не смешивать. Внешний формат =
|
|
291
|
+
`format`, внутренний (маска) = всегда `"DD.MM.YYYY"`.
|
|
292
|
+
- **`lastValidPickerValuesRef` и откат** — если удалить, пользователь
|
|
293
|
+
сможет оставить невалидную дату в поле.
|
|
294
|
+
- **`dropdownClosedByCalendarRef`** флаг — защита от двойного
|
|
295
|
+
`triggerChangeFromInput`. Не удалять.
|
|
296
|
+
- **`DateFieldContext`** — только внутренний, не экспортировать публично.
|
|
297
|
+
- **Tooltip только на десктопе** — мобильный fallback без tooltip'а
|
|
298
|
+
(MobileView отсекает). Не переносить tooltip в мобильную ветку без
|
|
299
|
+
дизайн-согласования.
|
|
300
|
+
- **`Enter`/`Space` в input** — открывают dropdown. Не добавлять другие
|
|
301
|
+
keys без согласования с DatePickerExtended.
|
|
302
|
+
- **Иконка календаря меняется по size** (CalendarStrokeSrvIcon16 для SM,
|
|
303
|
+
CalendarStrokeSrvIcon20 для MD/LG) — map в `sizeToCalendarIconMap`.
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## Accessibility
|
|
308
|
+
|
|
309
|
+
- **`aria-label` / `aria-labelledby`** передаются в MaskedInput. Если
|
|
310
|
+
есть `label` — он уже создаёт `<label>`-связь; label-only обычно
|
|
311
|
+
достаточно.
|
|
312
|
+
- **Клавиатура:**
|
|
313
|
+
- Tab — фокус на input, следующий Tab — на иконку календаря (ButtonIcon).
|
|
314
|
+
- Enter/Space на input — открыть dropdown.
|
|
315
|
+
- Дальнейшая навигация по календарю — см. `DatePickerExtended`/`Calendar`.
|
|
316
|
+
- **Tooltip `invalidDateHint`** — не имеет ARIA-связи с input (не
|
|
317
|
+
`aria-describedby`). Если нужна screen-reader подсказка — передавать
|
|
318
|
+
через `status` + `hint` на `MaskedField` (их стандартный механизм).
|
|
319
|
+
- **FormFieldClear** кнопка — имеет `aria-label` внутри (см. FormField
|
|
320
|
+
документацию).
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## Дизайн-токены
|
|
325
|
+
|
|
326
|
+
**Нет собственных.** Все визуальные токены — от `MaskedField` (fonts,
|
|
327
|
+
colors, borders, focus-ring) и `DatePickerExtended` (dropdown, calendar).
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
## Связанные компоненты
|
|
332
|
+
|
|
333
|
+
- `DatePickerExtended` (`src/components/DatePickerExtended`) — сам
|
|
334
|
+
пикер-обёртка вокруг Dropdown + Calendar. DateField его настраивает.
|
|
335
|
+
- `Calendar` (`src/components/Calendar`) — low-level выбор даты, живёт
|
|
336
|
+
внутри DatePickerExtended.
|
|
337
|
+
- `MaskedField` (`src/components/TextField`) — input с маской. Основа
|
|
338
|
+
`DateFieldTarget`.
|
|
339
|
+
- `MonthYearField` (`src/components/MonthYearField`) — близнец для
|
|
340
|
+
выбора месяца/года.
|
|
341
|
+
- `DateRange` (`src/components/DateRange`) — выбор диапазона дат.
|
|
342
|
+
- `Tooltip` (`src/components/Tooltip`) — подсказка `invalidDateHint`
|
|
343
|
+
на десктопе.
|
|
344
|
+
- `MobileView` (`src/components/MobileView`) — разветвление mobile/desktop.
|
|
345
|
+
- `DropdownMobileMaskedInput` — мобильный input в заголовке Dropdown'а.
|
|
346
|
+
- `FormFieldClear` — кнопка-крестик очистки поля.
|
|
347
|
+
- `ButtonIcon` — кнопка-иконка календаря.
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## Stories
|
|
352
|
+
|
|
353
|
+
`stories/DateField/DateField.stories.tsx`
|
|
354
|
+
Примеры: `stories/DateField/examples/`
|
|
355
|
+
|
|
356
|
+
Типовые stories:
|
|
357
|
+
- `Default` — базовое поле
|
|
358
|
+
- `WithLabel` — с label
|
|
359
|
+
- `Sizes` — SM/MD/LG
|
|
360
|
+
- `Statuses` — default/error/success/disabled
|
|
361
|
+
- `LimitRange` — ограниченный диапазон
|
|
362
|
+
- `DisabledDays` — выборочно отключённые даты
|
|
363
|
+
- `Playground` — интерактив
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## История изменений
|
|
368
|
+
|
|
369
|
+
| Дата | Изменение |
|
|
370
|
+
|---|---|
|
|
371
|
+
| 2026-04-18 | Создан документ |
|